commit c0316ba764a14aec6b3fa0420f22a6ecab9f7199 from: Lukas Henkel date: Sun May 12 10:28:31 2024 UTC Update packages commit - f5a0d8b30f0ae25eb84371d17a4bc4c7ccc4e160 commit + c0316ba764a14aec6b3fa0420f22a6ecab9f7199 blob - a32127ed03b991b105f2007d19cd170b4423728b (mode 644) blob + /dev/null --- elpa/consult-1.4/CHANGELOG.org +++ /dev/null @@ -1,410 +0,0 @@ -#+title: consult.el - Changelog -#+author: Daniel Mendler -#+language: en - -* Version 1.4 (2024-03-08) - -- Bugfix: File preview: Ensure that binary files are not previewed partially. - Otherwise ~pdf-view-mode~ may observe corrupted PDF files. -- ~consult--async-refresh-timer~: Optimize timer reuse and efficiency. This change - improves the performance of commands like ~consult-ripgrep~ for small values of - ~consult-async-refresh-delay~. -- ~consult-completion-in-region~: Remove ~:cycle-threshold~ and ~:completion-styles~ - customization options. - -* Version 1.3 (2024-02-23) - -- ~consult-bookmark-narrow~: More flexible grouping which supports multiple - bookmark handlers per group. -- Bugfix: Ensure that preview is always executed in a non-minibuffer window. -- Bugfix: File preview: Do not preview ~hexl-mode~ buffers. -- Bugfix: File preview: use ~error-message-string~ to access error string. -- Bugfix: Buffer preview: Retrieve original window correctly. -- Bugfix: Fix ~consult-global-mark~ for ~embark-export~. - -* Version 1.2 (2024-01-23) - -- =consult-buffer=: Bugfix. Ensure that null completion works properly. -- File preview: Add indication if previewed file got truncated. - -* Version 1.1 (2023-12-27) - -- Bugfixes: - + ~consult-xref~: Do not error for an empty location list. - + ~consult--read~: Catch null completion if require-match is non-nil. - + ~consult--multi~: Ensure that :new action is invoked on visible source. -- File preview: Check for long lines when previewing files partially. -- Use ~minibuffer-local-filename-syntax~ and ~read-file-name-completion-ignore-case~ - for directory prompt of the ~consult-grep~ and ~consult--find~ family of commands. -- Remove obsolete variables ~consult-preview-max-size~ and - ~consult-preview-raw-size~. - -* Version 1.0 (2023-12-01) - -- Bugfixes. -- Preview large files partially. Add new customization variables - =consult-preview-partial-chunk= and =consult-preview-partial-limit=. This new - feature is experimental. Please report any issues you observe. -- Obsoleted =consult-preview-max-size= and =consult-preview-raw-size=. -- =consult-buffer-other-tab=: New command. -- =consult-fd=: New command based on the fast =fd/fdfind= search utility. -- =consult-outline=: New prefix argument to specify initial narrowing level. -- =consult-org-heading=: Specify category =org-heading= such that Embark provides - appropriate Org heading actions. -- =consult-org-heading=: Add annotation. -- =consult-locate=: Split input into multiple words. -- Remove unreliable =consult--maybe-recenter=. -- Save input history even when using =embark-export= or when aborting from a - command via C-g. This change affects commands like =consult-line= and - =consult-grep=. -- Unify history of =consult-line=, =consult-keep-lines= and =consult-focus-lines=. - -* Version 0.35 (2023-07-02) - -- Bugfixes. -- =consult--read= now accepts programmable completion tables as argument, e.g., - =completion-table-dynamic= or =completion--file-name-table=. This allows you to - reuse existing completion tables to write completion commands enhanced with - Consult candidate preview. -- Replace =consult-preview-cursor= face with =cursor-highlight-mark=. -- Change calling convention of =consult-focus-lines= and =consult-keep-lines=. -- The regexps in =consult-buffer-filter= are matched case sensitively now. - Similarly, the =INCLUDE= and =EXCLUDE= arguments of =consult--buffer-query= are also - case sensitive. -- Do not preview remote files by default, see =consult-preview-excluded-files=. -- Use =consult--maybe-recenter= instead of =recenter= in =consult-after-jump-hook=. -- =consult-goto-line=: Support =line:column= input. - -* Version 0.34 (2023-04-21) - -- Bugfixes. -- =consult-org-heading=: Support tag inheritance. -- Use pure =consult--fast-abbreviate-file-name= function to abbreviate file names - in =consult-buffer= and =consult-recent-file=. This ensures that abbreviation does - not access the file system (or worse remote hosts via Tramp) and is always - fast. The downside is that some paths may not get abbreviated. -- Introduce buffer sources =consult--source-project-buffer-hidden= and - =consult--source-project-recent-file-hidden=. Set the buffer sources of - =consult-project= to =consult--source-project-buffer= and - =consult--source-project-recent-file= to ease customization. -- =consult-buffer=: Explicitly save =window-next-buffers= and =window-prev-buffers=. -- When previewing files literally (=consult-preview-raw-size=), set the multi byte - flag of the previewed buffer, such that UTF-8 buffers are not garbled. -- Do not create preview cursor overlay. Instead display the actual point by - ensuring that =cursor-in-non-selected-windows= is set. - -* Version 0.33 (2023-03-11) - -- BREAKING: The key convention has been updated. The old key convention is not - supported anymore. Keys must now be strings valid according to =key-valid-p=. - This changes affects the keys =consult-narrow-key=, =consult-widen-key=, - =consult-preview-key= and the =:preview-key= of sources and passed as keyword - argument to =consult--read=. See the example configurations in the manual. -- BREAKING: Remove the "." argument from =consult-grep-args= and - =consult-ripgrep-args=, since directories or files to search are appended by the - command line builder. Take this change into account, when you use a customized - version of those variables. -- =consult-grep=: Add support for grep and find over multiple files or directory. - If the prefix argument DIR is a single C-u, prompt for comma separated - directories or files to search recursively via =completing-read-multiple=. -- =consult-buffer= and =consult-isearch-history=: Align annotations dynamically - depending on candidate width, instead of computing the alignment beforehand. -- Add the full path as =help-echo= property to abbreviated directory paths and - project names. Enable =tooltip-mode= and hover with the mouse over the - abbreviated directory path to see the full path. -- =consult-grep/find/etc=: Print first line of stderr output if command failed. - -* Version 0.32 (2023-02-06) - -- Bugfixes -- Deprecate the old key convention. Keys must now be strings valid according to - =key-valid-p=. This changes affects the keys =consult-narrow-key=, - =consult-widen-key=, =consult-preview-key= and the =:preview-key= of sources and - passed as keyword argument to =consult--read=. See the example configurations in - the manual. -- Add =consult-info= command (#634, #727). -- =consult-buffer=: Always select the first candidate when narrowing (#714). -- =consult-locate-args=: Remove =--existing=, which is not supported by =plocate= on - Debian stable. -- =consult-ripgrep-args=: Add =--search-zip= option to automatically search through - compressed files. This will allow you to search Elisp files bundled with your - Emacs installation. Move to an Elisp library via =find-library=, then invoke - =consult-ripgrep=. -- Drop obsolete =consult-apropos=. Alternatives: =describe-symbol= in combination - with =embark-export=. See also =consult-info= and =consult-ripgrep= to search - through info manuals and Elisp source code. -- Drop obsolete =consult-multi-occur=. Alternative: Built-in =multi-occur=, - =multi-occur-in-matching-buffers= or =consult-line-multi=. -- Drop obsolete =consult-file-externally=. The command has been moved to Embark - under the name =embark-open-externally=. - -* Version 0.31 (2023-01-06) - -- Version bump to update the Compat package dependency (29.1.0.1) - -* Version 0.30 (2023-01-02) - -- Bugfixes -- Drop Selectrum support -- Deprecate =consult-file-externally= in favor of =embark-open-externally= -- Deprecate =consult-multi-occur=. The =multi-occur= command should be improved - upstream to take advantage of =completing-read-multiple=. Consult provides the - command =consult-line-multi= as an alternative. -- =consult-history=: Use input as initial completion input - -* Version 0.29 (2022-12-03) - -- Bugfixes -- =consult-line-multi= has been rewritten completely. The candidates are computed - on demand based on the input. This reduces startup speed greatly. The command - behaves like =consult-grep=, but operates on buffers instead of files. -- Add =consult--source-file-register=, and make the registers available in - =consult-buffer=. Registers are often used as quick access keys for files, e.g., - =(add-to-list 'register-alist '(?i file . "~/.emacs.d/init.el")))=. -- Remove obsolete =consult-line-point-placement= -- =consult-grep/find=: Always show directory in the prompt -- Add variable =consult-yank-rotate=, =consult-yank-from-kill-ring= rotates kill ring -- Emacs 29: =consult-register= supports =buffer= register type -- Emacs 29: Support =outline-search-function= -- Org 9.6: Support new =org-fold-core= API (both overlays and text-properties) -- Support abbreviated file names in =recentf-list=, see =recentf-filename-handler=. -- Deprecate =consult-apropos= - -* Version 0.20 (2022-10-16) - -- Bugfixes -- Allow =consult-*-args= to be a string, or a list of strings or expressions. -- Introduce face =consult-highlight-match= to highlight grep matches in the - completion buffer. -- Highlight full matches in =consult-line=, =consult-outline=, =consult-*grep= and - =consult-flymake=. -- Remove face =consult-preview-error=. -- Deprecate =consult-line-point-placement= in favor of more general - =consult-point-placement=, which is also used by the =consult-*grep= commands. -- =consult-imenu=: Support imenu-after-jump-hook and non-default - =imenu-default-goto-function= -- =consult-history=: Add support for history index variables, which are updated - after selection. -- Deprecate support for Selectrum in favor of Vertico. If you use Selectrum - consider switching to Vertico, Icomplete, Mct or default completion. - -* Version 0.19 (2022-09-09) - -- Bugfixes -- Allow =consult-flymake= to work across all buffers in a project -- Remove deprecated =consult-completing-read-multiple= -- =consult-grep/git-grep/ripgrep=: Add =--fixed-strings= support -- =consult-grep=: Respect =grep-find-ignored-directories/files= -- =consult-org-heading=: Add tags to completion candidates -- Add =consult-preview-excluded-files= -- =consult-themes=: Support regexps - -* Version 0.18 (2022-05-25) - -- Bugfixes -- Removed obsolete =consult-recent-file-filter= and =consult-preview-excluded-hooks= -- Deprecate =consult-completing-read-multiple=. See #567 for details. -- Add =consult--source-modified-buffer= - -* Version 0.17 (2022-04-22) - -- Bugfixes -- Drop Emacs 26 support. -- =consult-goto-line=: Use =goto-line-history= on Emacs 28. -- =consult-customize=: Evaluate settings at runtime. This change makes it possible - to use =thing-at-point= to overwrite the =:initial= and =:add-history= settings. -- Rename =consult--read-config= to =consult--customize-alist= and change the format. - The configuration is an alist. The car must be a command symbol. The cdr must - be a plist of keys and expressions, where the expressions evaluate to the - actual configuration values. -- Mode hooks in previewed file buffers are delayed. The buffer is only fully - initialized when leaving the minibuffer for recursive editing. -- Increase =consult-preview-raw-size=. -- Replace =consult-preview-excluded-hooks= by =consult-preview-allowed-hooks=. -- Add =consult-preview-variables= to bind variables for file preview. -- BREAKING API CHANGE of =consult--read=, =consult--prompt=, =consult--multi=: The - state function protocol changed. The function gets notified of more completion - state changes. See the docstring of =consult--with-preview= for details. -- BREAKING API CHANGE of =consult--read=: The lookup function protocol changed. - The function must now accept four or more arguments. -- Remove unused =consult-preview-map=. -- Remove unnecessary =consult-recent-file-filter=. Use =recentf-exclude= instead. -- =consult--multi= sources can have a =:new= function to create candidates. - When narrowed to a source, new candidates will be created by calling the - respective =:new= function. -- =consult--multi= returns =:match= information. =:match= can be nil, t, or new, - depending on if the candidate does not exist, exists or has been created. -- =consult-locate= treats the input literally to take advantage of the db index. - -* Version 0.16 (2022-03-08) - -- Bugfixes -- Deprecate =consult-project-root-function= in favor of =consult-project-function=. -- Preconfigure =consult-project-function= with a default function based - on project.el. -- Add =consult-project-buffer=, a variant of =consult-buffer= restricted to the - current project. -- Add =consult-register-prefix= option. -- Introduced a generic and extensible =consult-register= implementation. -- Lazy marker creation in =consult-line/outline= (performance improvements) - -* Version 0.15 (2022-01-31) - -- Bugfixes -- =consult-xref=: Prettify the group titles, use =xref--group-name-for-display= - if available. -- =consult-focus-lines=: Thanks to @jdtsmith, the command is much faster and - actually useable in large files. -- Added Mct integration, auto refreshing of asynchronous Consult commands. - -* Version 0.14 (2021-12-31) - -- Bugfixes -- Add =consult-recent-file-filter= -- Rename =consult--source-(project-)file= to =consult-source-(project-)recent-file= -- =consult-keep-lines= makes read-only buffers temporarily writable if confirmed - -* Version 0.13 (2021-11-12) - -- Bugfixes -- =consult-register=: Add support for file register values. -- Rename =consult-isearch= to =consult-isearch-history=. The command is a history - browsing command and not a replacement for Isearch. -- =consult-grep= support -[ABC] grep options -- Add =consult-grep-context= face - -* Version 0.12 (2021-10-11) - -- Bugfixes -- Removed obsolete =consult-project-imenu= and =consult-x-command= variables -- =consult-grep=: Use ~--null~ argument to support file names with colons - -* Version 0.11 (2021-08-18) - -- Bugfixes only - -* Version 0.10 (2021-08-11) - -- =consult-mark=, =consult-global-mark=: Add optional marker list argument -- =consult-completing-read-multiple=: New function -- Rename =consult-project-imenu= to =consult-imenu-multi= -- Add =consult-line-multi= to search multiple buffers -- Removed obsolete =consult-yank=, =consult-async-default-split=, =consult-config= -- =consult-ripgrep=: Use =--smart-case= -- =consult-grep/git-grep=: Use =--ignore-case= -- Deprecate =consult--command= in favor of =consult--config.= -- =consult-find=: Use regular expressions instead of globbing/wildcards by default. - Due to the changes to =consult-find= it is not possible anymore to configure - =fd= as backend for =consult-find=. A replacement is documented in the wiki. -- =consult-find/locate/man=: Add highlighting to the matching file/man page names. -- =consult-grep/git-grep/ripgrep/find/locate=: Add support for multiple unordered - patterns. Each of the input patterns must be matched. For example, - =consult-find= transforms the input "first second third" to "first -and second - -and third". -- =consult-grep/git-grep/ripgrep=: Compute the highlighting based on the input, - instead of relying on the ANSI-escaped output. This works better with multiple - patterns, but may occasionally produce false highlighting. -- Deprecate =consult-x-command= configuration variables in favor of =consult-x-args=. - The variables have been renamed since the configuration format changed. -- =consult-async-split-styles-alist=: Remove the =space= splitting style, since - it has been obsoleted by the support for multiple unordered patterns. - -* Version 0.9 (2021-06-22) - -- Add =consult-preview-excluded-hooks= -- =consult--read/consult--prompt=: Add =:inherit-input-method= argument -- Add debouncing support for preview - -* Version 0.8 (2021-05-30) - -- Async commands: Do not fix vertical height in Selectrum. -- =consult-imenu=: Deduplicate items (some imenu backends generate duplicates). -- =consult-org-heading=: Deduplicate items. -- =consult-buffer-filter=: Hide more buffers. -- =consult-line=: Matching line preview overlay only in the selected window. -- =consult-yank/completion-in-region=: Insertion preview only in selected window. -- =consult-yank=: Rename to =consult-yank-from-kill-ring= (Emacs 28 naming). -- =consult-yank= commands: =delete-selection-mode= support, added properties. -- =consult-preview-at-point=, =consult-preview-at-point-mode=: New command and - minor mode to preview candidate at point in =*Completions*= buffer. -- Add =consult-async-split-style= and =consult-async-split-styles-alist=. -- =consult-async-default-split=: Obsoleted in favor of =consult-async-split-style=. -- Deprecate =consult-config= in favor of new =consult-customize= macro. -- =consult-buffer=: Enable previews for files and bookmarks by default. -- =consult-buffer=/=consult--multi=: Add support for =:preview-key= per source. -- =consult-buffer=: Push visible buffers down in the buffer list. -- =consult-flycheck=: Moved to separate repository prior to ELPA submission. -- Submitted Consult to ELPA. - -* Version 0.7 (2021-04-29) - -- Bugfixes -- =consult-buffer=: Respect =confirm-nonexistent-file-or-buffer= -- =consult-widen-key=: Change default setting to twice the =consult-narrow-key= -- =consult-flycheck=: Sort errors first -- Added support for the Vertico completion system -- Consult adds disambiguation suffixes as suffix instead of as prefix now - for the commands =consult-line=, =consult-buffer=, etc. - This enables support for the =basic= completion style and TAB completion. -- =consult--read=: The =:title= function must accept two arguments now, - the candidate string and a flag. If the flag is nil, the function should - return the title of the candidate, otherwise the function should return the - transformed candidate. -- =consult-grep= and related commands: Strip the file name if grouping is used. -- =consult-find/grep=: Ensure that the commands work with Tramp -- =consult-outline=: Add narrowing -- Added =consult-org-heading= and =consult-org-agenda= -- =consult-line=: Highlight visual line during jump preview -- =consult-line=: Start search at current line, add configuration variable - =consult-start-from-top=. The starting point can be toggled by the prefix - argument =C-u=. - -* Version 0.6 (2021-03-02) - -- Bugfixes -- =consult-keep/focus-lines=: Align behavior on regions with built-in =keep-lines=. -- =consult-buffer=: Enable file sources only when =recentf-mode= is enabled -- =consult--multi=: Add =:default= flag, use flag for =consult--source-buffer= -- Add =consult-grep-max-columns= to prevent performance issues for long lines -- Add =consult-fontify-preserve= customization variable -- =consult-line=: Quits Isearch, when started from an Isearch session -- =consult-register-load=: Align prefix argument handling with =insert-register= -- Rename =consult-error= to =consult-compile-error= -- =consult-compile-error=: Allow calling the command from any buffer, - use the errors from all compilation buffers related to the current buffer. -- =consult-man=: Handle aggregated entries returned by mandoc -- =consult-completion-in-region=: Added preview and =consult-preview-region= face -- Added =consult-completion-in-region-styles= customization variable -- Added =consult-xref=. The function can be set as =xref-show-xrefs-function= - and =xref-show-definitions-function=. -- Added support for the candidate grouping function =x-group-function= - -* Version 0.5 (2021-02-09) - -- Bugfixes -- =consult-keep/focus-lines=: If region is active, operate only on the region. -- =consult-register-format=: Do not truncate register strings. -- =consult-buffer= multi sources: Ensure that original buffer is - shown, when the currently selected source does not perform preview. -- Add =consult-preview-raw-size= -- Expose preview functionality for multi-source bookmarks/files -- Multi sources: Add =:enabled=, =:state= and =:action= fields -- =consult-imenu=: Add faces depending on item types - -* Version 0.4 (2021-02-01) - -- Bugfixes -- Introduce multi sources, reimplement =consult-buffer= with multi sources -- =consult-isearch=: Add preview highlighting -- =consult-line=: Use =isearch-string= when invoked from running isearch - -* Version 0.3 (2021-01-28) - -- Bugfixes -- New command =consult-isearch= -- New functions =consult-register-format=, =consult-register-window=, - removed =consult-register-preview= - -* Version 0.2 (2021-01-16) - -- Initial stable release blob - e5f761d19263cd88492e651837300d0334184e61 (mode 644) blob + /dev/null --- elpa/consult-1.4/README-elpa +++ /dev/null @@ -1,1332 +0,0 @@ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - CONSULT.EL - CONSULTING COMPLETING-READ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - -Consult provides search and navigation commands based on the Emacs -completion function [completing-read]. Completion allows you to quickly -select an item from a list of candidates. Consult offers asynchronous -and interactive `consult-grep' and `consult-ripgrep' commands, and the -line-based search command `consult-line'. Furthermore Consult provides -an advanced buffer switching command `consult-buffer' to switch between -buffers, recently opened files, bookmarks and buffer-like candidates -from other sources. Some of the Consult commands are enhanced versions -of built-in Emacs commands. For example the command `consult-imenu' -presents a flat list of the Imenu with [live preview], [grouping and -narrowing]. Please take a look at the [full list of commands]. - -Consult is fully compatible with completion systems centered around the -standard Emacs `completing-read' API, notably the default completion -system, [Vertico], [Mct], and [Icomplete]. - -This package keeps the completion system specifics to a minimum. The -ability of the Consult commands to work well with arbitrary completion -systems is one of the main advantages of the package. Consult fits well -into existing setups and it helps you to create a full completion -environment out of small and independent components. - -You can combine the complementary packages [Marginalia], [Embark] and -[Orderless] with Consult. Marginalia enriches the completion display -with annotations, e.g., documentation strings or file information. The -versatile Embark package provides local actions, comparable to a context -menu. These actions operate on the selected candidate in the minibuffer -or at point in normal buffers. For example, when selecting from a list -of files, Embark offers an action to delete the file. Additionally -Embark offers a facility to collect completion candidates in a collect -buffer. The section [Embark integration] documents in detail how Consult -and Embark work together. - -Table of Contents -───────────────── - -1. Available commands -.. 1. Virtual Buffers -.. 2. Editing -.. 3. Register -.. 4. Navigation -.. 5. Search -.. 6. Grep and Find -.. 7. Compilation -.. 8. Histories -.. 9. Modes -.. 10. Org Mode -.. 11. Help -.. 12. Miscellaneous -2. Special features -.. 1. Live previews -.. 2. Narrowing and grouping -.. 3. Asynchronous search -.. 4. Multiple sources -.. 5. Embark integration -3. Configuration -.. 1. Use-package example -.. 2. Custom variables -.. 3. Fine-tuning -4. Recommended packages -5. Bug reports -6. Contributions -7. Acknowledgments -8. Indices -.. 1. Function index -.. 2. Concept index - - -[completing-read] - - -[live preview] See section 2.1 - -[grouping and narrowing] See section 2.2 - -[full list of commands] See section 1 - -[Vertico] - -[Mct] - -[Icomplete] - - -[Marginalia] - -[Embark] - -[Orderless] - -[Embark integration] See section 2.5 - - -1 Available commands -════════════════════ - - Most Consult commands follow the meaningful naming scheme - `consult-'. Many commands implement a little known but - convenient Emacs feature called "future history", which guesses what - input the user wants. At a command prompt type `M-n' and typically - Consult will insert the symbol or thing at point into the input. - - *TIP:* If you have [Marginalia] annotators activated, type `M-x - ^consult' to see all Consult commands with their abbreviated - description. Alternatively, type `C-h a ^consult' to get an overview - of all Consult variables and functions with their descriptions. - - -[Marginalia] - -1.1 Virtual Buffers -─────────────────── - - • `consult-buffer': Enhanced version of `switch-to-buffer' with - support for virtual buffers. Supports live preview of buffers and - narrowing to the virtual buffer types. You can type `f SPC' in order - to narrow to recent files. Press `SPC' to show ephemeral - buffers. Supported narrowing keys: - • b Buffers - • SPC Hidden buffers - • * Modified buffers - • f Files (Requires `recentf-mode') - • r File registers - • m Bookmarks - • p Project - • Custom [other sources] configured in `consult-buffer-sources'. - • `consult-buffer-other-window', `consult-buffer-other-frame', - `consult-buffer-other-tab': Variants of `consult-buffer'. - • `consult-project-buffer': Variant of `consult-buffer' restricted to - buffers and recent files of the current project. You can add custom - sources to `consult-project-buffer-sources'. The command may prompt - you for a project if you invoke it from outside a project. - • `consult-bookmark': Select or create bookmark. To select bookmarks - you might use the `consult-buffer' as an alternative, which can - include a bookmark virtual buffer source. Note that - `consult-bookmark' supports preview of bookmarks and narrowing. - • `consult-recent-file': Select from recent files with preview. You - might prefer the powerful `consult-buffer' instead, which can - include recent files as a virtual buffer source. The `recentf-mode' - enables tracking of recent files. - - -[other sources] See section 2.4 - - -1.2 Editing -─────────── - - • `consult-yank-from-kill-ring': Enhanced version of `yank' to select - an item from the `kill-ring'. The selected text previewed as overlay - in the buffer. - • `consult-yank-pop': Enhanced version of `yank-pop' with - DWIM-behavior, which either replaces the last `yank' by cycling - through the `kill-ring', or if there has not been a last `yank' - consults the `kill-ring'. The selected text previewed as overlay in - the buffer. - • `consult-yank-replace': Like `consult-yank-pop', but always replaces - the last `yank' with an item from the `kill-ring'. - • `consult-kmacro': Select macro from the macro ring and execute it. - - -1.3 Register -──────────── - - • `consult-register': Select from list of registers. The command - supports narrowing to register types and preview of marker - positions. This command is useful to search the register - contents. For quick access use the commands `consult-register-load', - `consult-register-store' or the built-in Emacs register commands. - • `consult-register-format': Set `register-preview-function' to this - function for an enhanced register formatting. See the [example - configuration]. - • `consult-register-window': Replace `register-preview' with this - function for a better register window. See the [example - configuration]. - • `consult-register-load': Utility command to quickly load a register. - The command either jumps to the register value or inserts it. - • `consult-register-store': Improved UI to store registers depending - on the current context with an action menu. With an active region, - store/append/prepend the contents, optionally deleting the region - when a prefix argument is given. With a numeric prefix argument, - store/add the number. Otherwise store point, frameset, window or - kmacro. Usage examples: - ‣ `M-' x': If no region is active, store point in register `x'. If - a region is active, store the region in register `x'. - ‣ `M-' M-w x': Store window configuration in register `x'. - ‣ `C-u 100 M-' x': Store number in register `x'. - - -[example configuration] See section 3.1 - - -1.4 Navigation -────────────── - - • `consult-goto-line': Jump to line number enhanced with live - preview. This is a drop-in replacement for `goto-line'. Enter a line - number to jump to the first column of the given line. Alternatively - enter `line:column' in order to jump to a specific column. - • `consult-mark': Jump to a marker in the `mark-ring'. Supports live - preview and recursive editing. - • `consult-global-mark': Jump to a marker in the `global-mark-ring'. - Supports live preview and recursive editing. - • `consult-outline': Jump to a heading of the outline. Supports - narrowing to a heading level, live preview and recursive editing. - • `consult-imenu': Jump to imenu item in the current buffer. Supports - live preview, recursive editing and narrowing. - • `consult-imenu-multi': Jump to imenu item in project buffers, with - the same major mode as the current buffer. Supports live preview, - recursive editing and narrowing. This feature has been inspired by - [imenu-anywhere]. - - -[imenu-anywhere] - - -1.5 Search -────────── - - • `consult-line': Enter search string and select from matching lines. - Supports live preview and recursive editing. The symbol at point and - the recent Isearch string are added to the "future history" and can - be accessed by pressing `M-n'. When `consult-line' is bound to the - `isearch-mode-map' and is invoked during a running Isearch, it will - use the current Isearch string. - • `consult-line-multi': Search dynamically across multiple buffers. By - default search across project buffers. If invoked with a prefix - argument search across all buffers. The candidates are computed on - demand based on the input. The command behaves like `consult-grep', - but operates on buffers instead of files. - • `consult-keep-lines': Replacement for `keep/flush-lines' which uses - the current completion style for filtering the buffer. The function - updates the buffer while typing. In particular `consult-keep-lines' - can narrow down an exported Embark collect buffer further, relying - on the same completion filtering as `completing-read'. If the input - begins with the negation operator, i.e., `! SPC', the filter matches - the complement. If a region is active, the region restricts the - filtering. - • `consult-focus-lines': Temporarily hide lines by filtering them - using the current completion style. Call with `C-u' prefix argument - in order to show the hidden lines again. If the input begins with - the negation operator, i.e., `! SPC', the filter matches the - complement. In contrast to `consult-keep-lines' this function does - not edit the buffer. If a region is active, the region restricts the - filtering. - - -1.6 Grep and Find -───────────────── - - • `consult-grep', `consult-ripgrep', `consult-git-grep': Search for - regular expression in files. Consult invokes Grep asynchronously, - while you enter the search term. After at least - `consult-async-min-input' characters, the search gets - started. Consult splits the input string into two parts, if the - first character is a punctuation character, like `#'. For example - `#regexps#filter-string', is split at the second `#'. The string - `regexps' is passed to Grep. Note that Consult transforms Emacs - regular expressions to expressions understand by the search - program. Always use Emacs regular expressions at the prompt. If you - enter multiple regular expressions separated by space only lines - matching all regular expressions are shown. In order to match space - literally, escape the space with a backslash. The `filter-string' is - passed to the /fast/ Emacs filtering to further narrow down the list - of matches. This is particularly useful if you are using an advanced - completion style like orderless. `consult-grep' supports preview. If - the `consult-project-function' returns non-nil, `consult-grep' - searches the current project directory. Otherwise the - `default-directory' is searched. If `consult-grep' is invoked with - prefix argument `C-u M-s g', you can specify one or more - comma-separated files and directories manually. - • `consult-find', `consult-fd', `consult-locate': Find file by - matching the path against a regexp. Like for `consult-grep', either - the project root or the current directory is the root directory for - the search. The input string is treated similarly to `consult-grep', - where the first part is passed to find, and the second part is used - for Emacs filtering. Prefix arguments to `consult-find' work just - like those for the consult grep commands. - - -1.7 Compilation -─────────────── - - • `consult-compile-error': Jump to a compilation error. Supports live - preview narrowing and recursive editing. - • `consult-flymake': Jump to Flymake diagnostic. Supports live preview - and recursive editing. The command supports narrowing. Press `e - SPC', `w SPC', `n SPC' to only show errors, warnings and notes - respectively. - • `consult-xref': Integration with xref. This function can be set as - `xref-show-xrefs-function' and `xref-show-definitions-function'. - - -1.8 Histories -───────────── - - • `consult-complex-command': Select a command from the - `command-history'. This command is a `completing-read' version of - `repeat-complex-command' and is also a replacement for the - `command-history' command from chistory.el. - • `consult-history': Insert a string from the current buffer history, - for example the Eshell or Comint history. You can also invoke this - command from the minibuffer. In that case `consult-history' uses the - history stored in the `minibuffer-history-variable'. If you prefer - `completion-at-point', take a look at `cape-history' from the [Cape] - package. - • `consult-isearch-history': During an Isearch session, this command - picks a search string from history and continues the search with the - newly selected string. Outside of Isearch, the command allows you to - pick a string from the history and starts a new - Isearch. `consult-isearch-history' acts as a drop-in replacement for - `isearch-edit-string'. - - -[Cape] - - -1.9 Modes -───────── - - • `consult-minor-mode-menu': Enable/disable minor mode. Supports - narrowing to on/off/local/global modes by pressing `i/o/l/g SPC' - respectively. - • `consult-mode-command': Run a command from the currently active - minor or major modes. Supports narrowing to - local-minor/global-minor/major mode via the keys `l/g/m'. - - -1.10 Org Mode -───────────── - - • `consult-org-heading': Variant of `consult-imenu' or - `consult-outline' for Org buffers. The headline and its ancestors - headlines are separated by slashes. Supports narrowing by heading - level, priority and TODO keyword, as well as live preview and - recursive editing. - • `consult-org-agenda': Jump to an Org agenda heading. Supports - narrowing by heading level, priority and TODO keyword, as well as - live preview and recursive editing. - - -1.11 Help -───────── - - • `consult-man': Find Unix man page, via Unix `apropos' or `man - -k'. `consult-man' opens the selected man page using the Emacs `man' - command. - • `consult-info': Full text search through info pages. If the command - is invoked from within an `*info*' buffer, it will search through - the current manual. You may want to create your own commands which - search through a predefined set of info pages, for example: - ┌──── - │ (defun consult-info-emacs () - │ "Search through Emacs info pages." - │ (interactive) - │ (consult-info "emacs" "efaq" "elisp" "cl" "compat")) - │ - │ (defun consult-info-org () - │ "Search through the Org info page." - │ (interactive) - │ (consult-info "org")) - │ - │ (defun consult-info-completion () - │ "Search through completion info pages." - │ (interactive) - │ (consult-info "vertico" "consult" "marginalia" "orderless" "embark" - │ "corfu" "cape" "tempel")) - └──── - - -1.12 Miscellaneous -────────────────── - - • `consult-theme': Select a theme and disable all currently enabled - themes. Supports live preview of the theme while scrolling through - the candidates. - • `consult-preview-at-point' and `consult-preview-at-point-mode': - Command and minor mode which previews the candidate at point in the - `*Completions*' buffer. This mode is relevant if you use [Mct] or - the default `*Completions*' UI. - • `consult-completion-in-region': In case you don't use [Corfu] as - your in-buffer completion UI, this function can be set as - `completion-in-region-function'. Then your minibuffer completion UI - (e.g., Vertico or Icomplete) will be used for `completion-at-point'. - ┌──── - │ ;; Use `consult-completion-in-region' if Vertico is enabled. - │ ;; Otherwise use the default `completion--in-region' function. - │ (setq completion-in-region-function - │ (lambda (&rest args) - │ (apply (if vertico-mode - │ #'consult-completion-in-region - │ #'completion--in-region) - │ args))) - └──── - Instead of `consult-completion-in-region', you may prefer to see the - completions directly in the buffer as a small popup. In that case, I - recommend the [Corfu] package. There is a technical limitation of - `consult-completion-in-region' in combination with the Lsp - modes. The Lsp server relies on the input at point, in order to - generate refined candidate strings. Since the completion is - transferred from the original buffer to the minibuffer, the server - does not receive the updated input. In contrast, in-buffer Lsp - completion for example via Corfu works properly since the completion - takes place directly in the original buffer. - - -[Mct] - -[Corfu] - - -2 Special features -══════════════════ - - Consult enhances `completing-read' with live previews of candidates, - additional narrowing capabilities to candidate groups and - asynchronously generated candidate lists. The internal `consult--read' - function, which is used by most Consult commands, is a thin wrapper - around `completing-read' and provides the special functionality. In - order to support multiple candidate sources there exists the - high-level function `consult--multi'. The architecture of Consult - allows it to work with different completion systems in the backend, - while still offering advanced features. - - -2.1 Live previews -───────────────── - - Some Consult commands support live previews. For example when you - scroll through the items of `consult-line', the buffer will scroll to - the corresponding position. It is possible to jump back and forth - between the minibuffer and the buffer to perform recursive editing - while the search is ongoing. - - Consult enables previews by default. You can disable them by adjusting - the `consult-preview-key' variable. Furthermore it is possible to - specify keybindings which trigger the preview manually as shown in the - [example configuration]. The default setting of `consult-preview-key' - is `any' which means that Consult triggers the preview /immediately/ - on any key press when the selected candidate changes. You can - configure each command individually with its own `:preview-key'. The - following settings are possible: - - • Automatic and immediate `'any' - • Automatic and delayed `(list :debounce 0.5 'any)' - • Manual and immediate `"M-."' - • Manual and delayed `(list :debounce 0.5 "M-.")' - • Disabled `nil' - - A safe recommendation is to leave automatic immediate previews enabled - in general and disable the automatic preview only for commands where - the preview may be expensive due to file loading. Internally, Consult - uses the value of `this-command' to determine the `:preview-key' - customized. This means that if you wrap a `consult-*' command within - your own function or command, you will also need to add the name of - /your custom command/ to the `consult-customize' call in order for it - to be considered. - - ┌──── - │ (consult-customize - │ consult-ripgrep consult-git-grep consult-grep - │ consult-bookmark consult-recent-file consult-xref - │ consult--source-bookmark consult--source-file-register - │ consult--source-recent-file consult--source-project-recent-file - │ ;; my/command-wrapping-consult ;; disable auto previews inside my command - │ :preview-key '(:debounce 0.4 any) ;; Option 1: Delay preview - │ ;; :preview-key "M-.") ;; Option 2: Manual preview - └──── - - In this case one may wonder what the difference is between using an - Embark action on the current candidate in comparison to a manually - triggered preview. The main difference is that the files opened by - manual preview are closed again after the completion session. During - preview some functionality is disabled to improve the performance, see - for example the customization variables `consult-preview-variables' - and `consult-preview-allowed-hooks'. Only the hooks listed in - `consult-preview-allowed-hooks' are executed when a file is opened - (`find-file-hook'). In order to enable additional font locking during - preview, add the corresponding hooks to the allow list. The following - code demonstrates this for [org-modern] and [hl-todo]. - - ┌──── - │ (add-to-list 'consult-preview-allowed-hooks 'global-org-modern-mode-check-buffers) - │ (add-to-list 'consult-preview-allowed-hooks 'global-hl-todo-mode-check-buffers) - └──── - - Files larger than `consult-preview-partial-size' are previewed - partially. Delaying the preview is also useful for `consult-theme', - since the theme preview is slow. The delay results in a smoother UI - experience. - - ┌──── - │ ;; Preview on any key press, but delay 0.5s - │ (consult-customize consult-theme :preview-key '(:debounce 0.5 any)) - │ ;; Preview immediately on M-., on up/down after 0.5s, on any other key after 1s - │ (consult-customize consult-theme - │ :preview-key - │ '("M-." - │ :debounce 0.5 "" "" - │ :debounce 1 any)) - └──── - - -[example configuration] See section 3.1 - -[org-modern] - -[hl-todo] - - -2.2 Narrowing and grouping -────────────────────────── - - Consult has special support for candidate groups. If the completion UI - supports the grouping functionality, the UI separates the groups with - thin lines and shows group titles. Grouping is useful if the list of - candidates consists of candidates of multiple types or candidates from - [multiple sources], like the `consult-buffer' command, which shows - both buffers and recently opened files. Note that you can disable the - group titles by setting the `:group' property of the corresponding - command to nil using the `consult-customize' macro. - - By entering a narrowing prefix or by pressing a narrowing key it is - possible to restrict the completion candidates to a certain candidate - group. When you use the `consult-buffer' command, you can enter the - prefix `b SPC' to restrict list of candidates to buffers only. If you - press `DEL' afterwards, the full candidate list will be shown - again. Furthermore a narrowing prefix key and a widening key can be - configured which can be pressed to achieve the same effect, see the - configuration variables `consult-narrow-key' and `consult-widen-key'. - - After pressing `consult-narrow-key', the possible narrowing keys can - be shown by pressing `C-h'. When pressing `C-h' after some prefix key, - the `prefix-help-command' is invoked, which shows the keybinding help - window by default. As a more compact alternative, there is the - `consult-narrow-help' command which can be bound to a key, for example - `?' or `C-h' in the `consult-narrow-map', as shown in the [example - configuration]. If [which-key] is installed, the narrowing keys are - automatically shown in the which-key window after pressing the - `consult-narrow-key'. - - -[multiple sources] See section 2.4 - -[example configuration] See section 3.1 - -[which-key] - - -2.3 Asynchronous search -─────────────────────── - - Consult has support for asynchronous generation of candidate - lists. This feature is used for search commands like `consult-grep', - where the list of matches is generated dynamically while the user is - typing a regular expression. The grep process is executed in the - background. When modifying the regular expression, the background - process is terminated and a new process is started with the modified - regular expression. - - The matches, which have been found, can then be narrowed using the - installed Emacs completion-style. This can be powerful if you are - using for example the `orderless' completion style. - - This two-level filtering is possible by splitting the input - string. Part of the input string is treated as input to grep and part - of the input is used for filtering. There are multiple splitting - styles available, configured in `consult-async-split-styles-alist': - `nil', `comma', `semicolon' and `perl'. The default splitting style is - configured with the variable `consult-async-split-style'. - - With the `comma' and `semicolon' splitting styles, the first word - before the comma or semicolon is passed to grep, the remaining string - is used for filtering. The `nil' splitting style does not perform any - splitting, the whole input is passed to grep. - - The `perl' splitting style splits the input string at a punctuation - character, using a similar syntax as Perl regular expressions. - - Examples: - - • `#defun': Search for "defun" using grep. - • `#consult embark': Search for both "consult" and "embark" using grep - in any order. - • `#first.*second': Search for "first" followed by "second" using - grep. - • `#\(consult\|embark\)': Search for "consult" or "embark" using - grep. Note the usage of Emacs-style regular expressions. - • `#defun#consult': Search for "defun" using grep, filter with the - word "consult". - • `/defun/consult': It is also possible to use other punctuation - characters. - • `#to#': Force searching for "to" using grep, since the grep pattern - must be longer than `consult-async-min-input' characters by default. - • `#defun -- --invert-match#': Pass argument `--invert-match' to grep. - - Asynchronous processes like `find' and `grep' create an error log - buffer `_*consult-async*' (note the leading space), which is useful - for troubleshooting. The prompt has a small indicator showing the - process status: - - • `:' the usual prompt colon, before input is provided. - • `*' with warning face, the process is running. - • `:' with success face, success, process exited with an error code of - zero. - • `!' with error face, failure, process exited with a nonzero error - code. - • `;' with error face, interrupted, for example if more input is - provided. - - -2.4 Multiple sources -──────────────────── - - Multiple synchronous candidate sources can be combined. This feature - is used by the `consult-buffer' command to present buffer-like - candidates in a single menu for quick access. By default - `consult-buffer' includes buffers, bookmarks, recent files and - project-specific buffers and files. It is possible to configure the - list of sources via the `consult-buffer-sources' variable. Arbitrary - custom sources can be defined. - - As an example, the bookmark source is defined as follows: - - ┌──── - │ (defvar consult--source-bookmark - │ `(:name "Bookmark" - │ :narrow ?m - │ :category bookmark - │ :face consult-bookmark - │ :history bookmark-history - │ :items ,#'bookmark-all-names - │ :action ,#'consult--bookmark-action)) - └──── - - Required source fields: - • `:category' Completion category. - • `:items' List of strings to select from or function returning list - of strings. A list of cons cells is not supported. - - Optional source fields: - • `:name' Name of the source, used for narrowing, group titles and - annotations. - • `:narrow' Narrowing character or `(character . string)' pair. - • `:preview-key' Preview key or keys which trigger preview. - • `:enabled' Function which must return t if the source is enabled. - • `:hidden' When t candidates of this source are hidden by default. - • `:face' Face used for highlighting the candidates. - • `:annotate' Annotation function called for each candidate, returns - string. - • `:history' Name of history variable to add selected candidate. - • `:default' Must be t if the first item of the source is the default - value. - • `:action' Function called with the selected candidate. - • `:new' Function called with new candidate name, only if - `:require-match' is nil. - • `:state' State constructor for the source, must return the state - function. - • Other source fields can be added specifically to the use case. - - The `:state' and `:action' fields of the sources deserve a longer - explanation. The `:action' function takes a single argument and is - only called after selection with the selected candidate, if the - selection has not been aborted. This functionality is provided for - convenience and easy definition of sources. The `:state' field is more - general. The `:state' function is a constructor function without - arguments, which can perform some setup necessary for the preview. It - must return a closure which takes an ACTION and a CANDIDATE - argument. See the docstring of `consult--with-preview' for more - details about the ACTION argument. - - By default, `consult-buffer' previews buffers, bookmarks and - files. Loading recent files or bookmarks can result in expensive - operations. However it is possible to configure a manual preview as - follows. - - ┌──── - │ (consult-customize - │ consult--source-bookmark consult--source-file-register - │ consult--source-recent-file consult--source-project-recent-file - │ :preview-key "M-.") - └──── - - Sources can be added directly to the `consult-buffer-source' list for - convenience. For example, the following source lists all Org buffers - and lets you create new ones. - - ┌──── - │ (defvar org-source - │ (list :name "Org Buffer" - │ :category 'buffer - │ :narrow ?o - │ :face 'consult-buffer - │ :history 'buffer-name-history - │ :state #'consult--buffer-state - │ :new - │ (lambda (name) - │ (with-current-buffer (get-buffer-create name) - │ (insert "#+title: " name "\n\n") - │ (org-mode) - │ (consult--buffer-action (current-buffer)))) - │ :items - │ (lambda () - │ (consult--buffer-query :mode 'org-mode :as #'buffer-name)))) - │ - │ (add-to-list 'consult-buffer-sources 'org-source 'append) - └──── - - One can create similar sources for other major modes. See the [Consult - wiki] for many additional source examples. See also the documentation - of `consult-buffer' and of the internal `consult--multi' API. The - function `consult--multi' can be used to create new multi-source - commands. - - -[Consult wiki] - - -2.5 Embark integration -────────────────────── - - *NOTE*: Install the `embark-consult' package from MELPA, which - provides Consult-specific Embark actions and the Occur buffer export. - - Embark is a versatile package which offers context dependent actions, - comparable to a context menu. See the [Embark manual] for an extensive - description of its capabilities. - - Actions are commands which can operate on the currently selected - candidate (or target in Embark terminology). When completing files, - for example the `delete-file' command is offered. With Embark you can - execute arbitrary commands on the currently selected candidate via - `M-x'. - - Furthermore Embark provides the `embark-collect' command, which - collects candidates and presents them in an Embark collect buffer, - where further actions can be applied to them. A related feature is the - `embark-export' command, which exports candidate lists to a buffer of - a special type. For example in the case of file completion, a Dired - buffer is opened. - - In the context of Consult, particularly exciting is the possibility to - export the matching lines from `consult-line', `consult-outline', - `consult-mark' and `consult-global-mark'. The matching lines are - exported to an Occur buffer where they can be edited via the - `occur-edit-mode' (press key `e'). Similarly, Embark supports - exporting the matches found by `consult-grep', `consult-ripgrep' and - `consult-git-grep' to a Grep buffer, where the matches across files - can be edited, if the [wgrep] package is installed. These three - workflows are symmetric. - - ⁃ `consult-line' -> `embark-export' to `occur-mode' buffer -> - `occur-edit-mode' for editing of matches in buffer. - ⁃ `consult-grep' -> `embark-export' to `grep-mode' buffer -> `wgrep' - for editing of all matches. - ⁃ `consult-find' -> `embark-export' to `dired-mode' buffer -> - `wdired-change-to-wdired-mode' for editing. - - -[Embark manual] - -[wgrep] - - -3 Configuration -═══════════════ - - Consult can be installed from [ELPA] or [MELPA] via the Emacs built-in - package manager. Alternatively it can be directly installed from the - development repository via other non-standard package managers. - - There is the [Consult wiki], where additional configuration examples - can be contributed. - - *IMPORTANT:* It is recommended that you enable [lexical binding] in - your configuration. Many Consult-related code snippets require lexical - binding, since they use lambdas and closures. - - -[ELPA] - -[MELPA] - -[Consult wiki] - -[lexical binding] - - -3.1 Use-package example -─────────────────────── - - The Consult package only provides commands and does not add any - keybindings or modes. Therefore the package is non-intrusive but - requires a little setup effort. In order to use the Consult commands, - it is advised to add keybindings for commands which are accessed - often. Rarely used commands can be invoked via `M-x'. Feel free to - only bind the commands you consider useful to your workflow. The - configuration shown here relies on the `use-package' macro, which is a - convenient tool to manage package configurations. - - *NOTE:* There is the [Consult wiki], where you can contribute - additional configuration examples. - - ┌──── - │ ;; Example configuration for Consult - │ (use-package consult - │ ;; Replace bindings. Lazily loaded due by `use-package'. - │ :bind (;; C-c bindings in `mode-specific-map' - │ ("C-c M-x" . consult-mode-command) - │ ("C-c h" . consult-history) - │ ("C-c k" . consult-kmacro) - │ ("C-c m" . consult-man) - │ ("C-c i" . consult-info) - │ ([remap Info-search] . consult-info) - │ ;; C-x bindings in `ctl-x-map' - │ ("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command - │ ("C-x b" . consult-buffer) ;; orig. switch-to-buffer - │ ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window - │ ("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame - │ ("C-x t b" . consult-buffer-other-tab) ;; orig. switch-to-buffer-other-tab - │ ("C-x r b" . consult-bookmark) ;; orig. bookmark-jump - │ ("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer - │ ;; Custom M-# bindings for fast register access - │ ("M-#" . consult-register-load) - │ ("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated) - │ ("C-M-#" . consult-register) - │ ;; Other custom bindings - │ ("M-y" . consult-yank-pop) ;; orig. yank-pop - │ ;; M-g bindings in `goto-map' - │ ("M-g e" . consult-compile-error) - │ ("M-g f" . consult-flymake) ;; Alternative: consult-flycheck - │ ("M-g g" . consult-goto-line) ;; orig. goto-line - │ ("M-g M-g" . consult-goto-line) ;; orig. goto-line - │ ("M-g o" . consult-outline) ;; Alternative: consult-org-heading - │ ("M-g m" . consult-mark) - │ ("M-g k" . consult-global-mark) - │ ("M-g i" . consult-imenu) - │ ("M-g I" . consult-imenu-multi) - │ ;; M-s bindings in `search-map' - │ ("M-s d" . consult-find) ;; Alternative: consult-fd - │ ("M-s c" . consult-locate) - │ ("M-s g" . consult-grep) - │ ("M-s G" . consult-git-grep) - │ ("M-s r" . consult-ripgrep) - │ ("M-s l" . consult-line) - │ ("M-s L" . consult-line-multi) - │ ("M-s k" . consult-keep-lines) - │ ("M-s u" . consult-focus-lines) - │ ;; Isearch integration - │ ("M-s e" . consult-isearch-history) - │ :map isearch-mode-map - │ ("M-e" . consult-isearch-history) ;; orig. isearch-edit-string - │ ("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string - │ ("M-s l" . consult-line) ;; needed by consult-line to detect isearch - │ ("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch - │ ;; Minibuffer history - │ :map minibuffer-local-map - │ ("M-s" . consult-history) ;; orig. next-matching-history-element - │ ("M-r" . consult-history)) ;; orig. previous-matching-history-element - │ - │ ;; Enable automatic preview at point in the *Completions* buffer. This is - │ ;; relevant when you use the default completion UI. - │ :hook (completion-list-mode . consult-preview-at-point-mode) - │ - │ ;; The :init configuration is always executed (Not lazy) - │ :init - │ - │ ;; Optionally configure the register formatting. This improves the register - │ ;; preview for `consult-register', `consult-register-load', - │ ;; `consult-register-store' and the Emacs built-ins. - │ (setq register-preview-delay 0.5 - │ register-preview-function #'consult-register-format) - │ - │ ;; Optionally tweak the register preview window. - │ ;; This adds thin lines, sorting and hides the mode line of the window. - │ (advice-add #'register-preview :override #'consult-register-window) - │ - │ ;; Use Consult to select xref locations with preview - │ (setq xref-show-xrefs-function #'consult-xref - │ xref-show-definitions-function #'consult-xref) - │ - │ ;; Configure other variables and modes in the :config section, - │ ;; after lazily loading the package. - │ :config - │ - │ ;; Optionally configure preview. The default value - │ ;; is 'any, such that any key triggers the preview. - │ ;; (setq consult-preview-key 'any) - │ ;; (setq consult-preview-key "M-.") - │ ;; (setq consult-preview-key '("S-" "S-")) - │ ;; For some commands and buffer sources it is useful to configure the - │ ;; :preview-key on a per-command basis using the `consult-customize' macro. - │ (consult-customize - │ consult-theme :preview-key '(:debounce 0.2 any) - │ consult-ripgrep consult-git-grep consult-grep - │ consult-bookmark consult-recent-file consult-xref - │ consult--source-bookmark consult--source-file-register - │ consult--source-recent-file consult--source-project-recent-file - │ ;; :preview-key "M-." - │ :preview-key '(:debounce 0.4 any)) - │ - │ ;; Optionally configure the narrowing key. - │ ;; Both < and C-+ work reasonably well. - │ (setq consult-narrow-key "<") ;; "C-+" - │ - │ ;; Optionally make narrowing help available in the minibuffer. - │ ;; You may want to use `embark-prefix-help-command' or which-key instead. - │ ;; (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help) - │ - │ ;; By default `consult-project-function' uses `project-root' from project.el. - │ ;; Optionally configure a different project root function. - │ ;;;; 1. project.el (the default) - │ ;; (setq consult-project-function #'consult--default-project--function) - │ ;;;; 2. vc.el (vc-root-dir) - │ ;; (setq consult-project-function (lambda (_) (vc-root-dir))) - │ ;;;; 3. locate-dominating-file - │ ;; (setq consult-project-function (lambda (_) (locate-dominating-file "." ".git"))) - │ ;;;; 4. projectile.el (projectile-project-root) - │ ;; (autoload 'projectile-project-root "projectile") - │ ;; (setq consult-project-function (lambda (_) (projectile-project-root))) - │ ;;;; 5. No project support - │ ;; (setq consult-project-function nil) - │ ) - └──── - - -[Consult wiki] - - -3.2 Custom variables -──────────────────── - - *TIP:* If you have [Marginalia] installed, type `M-x - customize-variable RET ^consult' to see all Consult-specific - customizable variables with their current values and abbreviated - description. Alternatively, type `C-h a ^consult' to get an overview - of all Consult variables and functions with their descriptions. - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Variable Description - ─────────────────────────────────────────────────────────────────────────────────────── - consult-after-jump-hook Functions to call after jumping to a location - consult-async-input-debounce Input debounce for asynchronous commands - consult-async-input-throttle Input throttle for asynchronous commands - consult-async-min-input Minimum numbers of input characters - consult-async-refresh-delay Refresh delay for asynchronous commands - consult-async-split-style Splitting style used for async commands - consult-async-split-styles-alist Available splitting styles used for async commands - consult-bookmark-narrow Narrowing configuration for `consult-bookmark' - consult-buffer-filter Filter for `consult-buffer' - consult-buffer-sources List of virtual buffer sources - consult-fd-args Command line arguments for fd - consult-find-args Command line arguments for find - consult-fontify-max-size Buffers larger than this limit are not fontified - consult-fontify-preserve Preserve fontification for line-based commands. - consult-git-grep-args Command line arguments for git-grep - consult-goto-line-numbers Show line numbers for `consult-goto-line' - consult-grep-max-columns Maximal number of columns of the matching lines - consult-grep-args Command line arguments for grep - consult-imenu-config Mode-specific configuration for `consult-imenu' - consult-line-numbers-widen Show absolute line numbers when narrowing is active - consult-line-start-from-top Start the `consult-line' search from the top - consult-locate-args Command line arguments for locate - consult-man-args Command line arguments for man - consult-mode-command-filter Filter for `consult-mode-command' - consult-mode-histories Mode-specific history variables - consult-narrow-key Narrowing prefix key during completion - consult-point-placement Placement of the point when jumping to matches - consult-preview-key Keys which triggers preview - consult-preview-allowed-hooks List of `find-file' hooks to enable during preview - consult-preview-excluded-files Regexps matched against file names during preview - consult-preview-max-count Maximum number of files to keep open during preview - consult-preview-partial-size Files larger than this size are previewed partially - consult-preview-partial-chunk Size of the file chunk which is previewed partially - consult-preview-variables Alist of variables to bind during preview - consult-project-buffer-sources List of virtual project buffer sources - consult-project-function Function which returns current project root - consult-register-prefix Prefix string for register keys during completion - consult-ripgrep-args Command line arguments for ripgrep - consult-themes List of themes to be presented for selection - consult-widen-key Widening key during completion - consult-yank-rotate Rotate kill ring - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - -[Marginalia] - - -3.3 Fine-tuning of individual commands -────────────────────────────────────── - - *NOTE:* Consult supports fine-grained customization of individual - commands. This configuration feature exists for experienced users with - special requirements. There is the [Consult wiki], where we collect - further configuration examples. - - Commands and buffer sources allow flexible, individual customization - by using the `consult-customize' macro. You can override any option - passed to the internal `consult--read' API. Note that since - `consult--read' is part of the internal API, options could be removed, - replaced or renamed in future versions of the package. - - Useful options are: - • `:prompt' set the prompt string - • `:preview-key' set the preview key, default is `consult-preview-key' - • `:initial' set the initial input - • `:default' set the default value - • `:history' set the history variable symbol - • `:add-history' add items to the future history, for example symbol - at point - • `:sort' enable or disable sorting - • `:group' set to nil to disable candidate grouping and titles. - • `:inherit-input-method' set to non-nil to inherit the input method. - - ┌──── - │ (consult-customize - │ ;; Disable preview for `consult-theme' completely. - │ consult-theme :preview-key nil - │ ;; Set preview for `consult-buffer' to key `M-.' - │ consult-buffer :preview-key "M-." - │ ;; For `consult-line' change the prompt and specify multiple preview - │ ;; keybindings. Note that you should bind and in the - │ ;; `minibuffer-local-completion-map' or `vertico-map' to the commands which - │ ;; select the previous or next candidate. - │ consult-line :prompt "Search: " - │ :preview-key '("S-" "S-")) - └──── - - The configuration values are evaluated at runtime, just before the - completion session is started. Therefore you can use for example - `thing-at-point' to adjust the initial input or the future history. - - ┌──── - │ (consult-customize - │ consult-line - │ :add-history (seq-some #'thing-at-point '(region symbol))) - │ - │ (defalias 'consult-line-thing-at-point 'consult-line) - │ - │ (consult-customize - │ consult-line-thing-at-point - │ :initial (thing-at-point 'symbol)) - └──── - - Generally it is possible to modify commands for your individual needs - by the following techniques: - - 1. Use `consult-customize' in order to change the command or source - settings. - 2. Create your own wrapper function which passes modified arguments to - the Consult functions. - 3. Create your own buffer [multi sources] for `consult-buffer'. - 4. Create advices to modify some internal behavior. - 5. Write or propose a patch. - - -[Consult wiki] - -[multi sources] See section 2.4 - - -4 Recommended packages -══════════════════════ - - I use and recommend this combination of packages: - - • consult: This package - • [vertico]: Fast and minimal vertical completion system - • [marginalia]: Annotations for the completion candidates - • [embark and embark-consult]: Action commands, which can act on the - completion candidates - • [orderless]: Completion style which offers flexible candidate - filtering - • [wgrep]: Editing of grep buffers. Use with `consult-grep' via - `embark-export'. - - There exist multiple fine completion UIs beside Vertico, which are - supported by Consult. Give them a try and find out which interaction - model fits best for you. - - • The builtin completion UI, which pops up the `*Completions*' buffer. - • The builtin `icomplete-vertical-mode' in Emacs 28 or newer. - • [mct by Protesilaos Stavrou]: Minibuffer and Completions in Tandem, - which builds on the default completion UI. - - Note that all packages are independent and can be exchanged with - alternative components, since there exist no hard - dependencies. Furthermore it is possible to get started with only - default completion and Consult and add more components later to the - mix. For example you can omit Marginalia if you don't need - annotations. I highly recommend the Embark package, but in order to - familiarize yourself with the other components, you can first start - without it - or you could use with Embark right away and add the other - components later on. - - We document a [list of auxiliary packages] in the Consult wiki. These - packages integrate Consult with special programs or with other - packages in the wider Emacs ecosystem. - - -[vertico] - -[marginalia] - -[embark and embark-consult] - -[orderless] - -[wgrep] - -[mct by Protesilaos Stavrou] - -[list of auxiliary packages] - - - -5 Bug reports -═════════════ - - If you find a bug or suspect that there is a problem with Consult, - please carry out the following steps: - - 1. *Search through the issue tracker* if your issue has been reported - before (and has been resolved eventually) in the meantime. - 2. *Remove all packages involved in the suspected bug from your - installation.* - 3. *Reinstall the newest version of all relevant packages*. Updating - alone is not sufficient, since package.el sometimes causes - miscompilation. The list of packages includes Consult, Compat, - Vertico or other completion UIs, Marginalia, Embark and Orderless. - 4. Either use the default completion UI or ensure that exactly one of - `vertico-mode', `mct-mode', or `icomplete-mode' is enabled. The - unsupported modes `selectrum-mode', `ivy-mode', `helm-mode', - `ido-mode' and `ido-ubiquitous-mode' must be disabled. - 5. Ensure that the `completion-styles' variable is properly - configured. Try to set `completion-styles' to a list including - `substring' or `orderless'. - 6. Try to reproduce the issue by starting a bare bone Emacs instance - with `emacs -Q' on the command line. Execute the following minimal - code snippets in the scratch buffer. This way we can exclude side - effects due to configuration settings. If other packages are - relevant to reproduce the issue, include them in the minimal - configuration snippet. - - Minimal setup with Vertico for `emacs -Q': - ┌──── - │ (package-initialize) - │ (require 'consult) - │ (require 'vertico) - │ (vertico-mode) - │ (setq completion-styles '(substring basic)) - └──── - - Minimal setup with the default completion system for `emacs -Q': - ┌──── - │ (package-initialize) - │ (require 'consult) - │ (setq completion-styles '(substring basic)) - └──── - - Please provide the necessary important information with your bug - report: - - • The minimal configuration snippet used to reproduce the issue. - • Your completion UI (Default completion, Vertico, Mct or Icomplete). - • A stack trace in case the bug triggers an exception. - • Your Emacs version, since bugs may be fixed or introduced in newer - versions. - • Your operating system, since Emacs behavior varies subtly between - Linux, Mac and Windows. - • The package manager, e.g., straight.el or package.el, used to - install the Emacs packages, in order to exclude update issues. Did - you install Consult as part of the Doom Emacs distribution? - • Do you use Evil? Consult does not provide Evil integration out of - the box, but there is some support in [evil-collection]. - - When evaluating Consult-related code snippets you should enable - [lexical binding]. Consult often relies on lambdas and lexical - closures. - - -[evil-collection] - -[lexical binding] - - - -6 Contributions -═══════════════ - - Consult is a community effort, please participate in the discussions. - Contributions are welcome, but you may want to discuss potential - contributions first. Since this package is part of [GNU ELPA] - contributions require a copyright assignment to the FSF. - - If you have a proposal, take a look at the [Consult issue tracker] and - the [Consult wishlist]. There have been many prior feature - discussions. Please search through the issue tracker, maybe your issue - or feature request has already been discussed. You can contribute to - the [Consult wiki], in case you want to share small configuration or - command snippets. - - -[GNU ELPA] - -[Consult issue tracker] - -[Consult wishlist] - -[Consult wiki] - - -7 Acknowledgments -═════════════════ - - This package took inspiration from [Counsel] by Oleh Krehel. Some of - the Consult commands originated in the Counsel package or the wiki of - the Selectrum package. This package exists only thanks to the help of - these great contributors and thanks to the feedback of many - users. Thank you! - - Code contributions: [Aymeric Agon-Rambosson], [Amos Bird], [Ashton - Wiersdorf], [Adam Spiers], [Augusto Stoffel], [Clemens Radermacher], - [Zhengyi], [Geoffrey Lessel], [Illia Ostapyshyn], [jakanakaevangeli], - [JD Smith], [Jean-Philippe Bernardy], [mattiasdrp], [Mohamed - Abdelnour], [Mohsin Kaleem], [Fox Kiester], [Omar Antolín Camarena], - [Earl Hyatt], [Omar Polo], [Piotr Kwiecinski], [Robert Weiner], - [Sergey Kostyaev], [Alexandru Scvorțov], [Tecosaur], [Sylvain - Rousseau], [Tom Fitzhenry], [Iñigo Serna] and [Alex Kreisher]. - - Advice and useful discussions: [Enrique Kessler Martínez], [Adam - Porter], [Bruce d'Arcus], [Clemens Radermacher], [Dmitry Gutov], - [Howard Melman], [Itai Y. Efrat], [JD Smith], [Manuel Uberti], [Stefan - Monnier], [Omar Antolín Camarena], [Steve Purcell], [Radon - Rosborough], [Tom Fitzhenry] and [Protesilaos Stavrou]. - - -[Counsel] - -[Aymeric Agon-Rambosson] - -[Amos Bird] - -[Ashton Wiersdorf] - -[Adam Spiers] - -[Augusto Stoffel] - -[Clemens Radermacher] - -[Zhengyi] - -[Geoffrey Lessel] - -[Illia Ostapyshyn] - -[jakanakaevangeli] - -[JD Smith] - -[Jean-Philippe Bernardy] - -[mattiasdrp] - -[Mohamed Abdelnour] - -[Mohsin Kaleem] - -[Fox Kiester] - -[Omar Antolín Camarena] - -[Earl Hyatt] - -[Omar Polo] - -[Piotr Kwiecinski] - -[Robert Weiner] - -[Sergey Kostyaev] - -[Alexandru Scvorțov] - -[Tecosaur] - -[Sylvain Rousseau] - -[Tom Fitzhenry] - -[Iñigo Serna] - -[Alex Kreisher] - -[Enrique Kessler Martínez] - -[Adam Porter] - -[Bruce d'Arcus] - -[Dmitry Gutov] - -[Howard Melman] - -[Itai Y. Efrat] - -[Manuel Uberti] - -[Stefan Monnier] - -[Steve Purcell] - -[Radon Rosborough] - -[Protesilaos Stavrou] - - -8 Indices -═════════ - -8.1 Function index -────────────────── - - -8.2 Concept index -───────────────── blob - be174b0580734ededad7d06675277530fc3223c0 (mode 644) blob + /dev/null --- elpa/consult-1.4/README.org +++ /dev/null @@ -1,1153 +0,0 @@ -#+title: consult.el - Consulting completing-read -#+author: Daniel Mendler -#+language: en -#+export_file_name: consult.texi -#+texinfo_dir_category: Emacs misc features -#+texinfo_dir_title: Consult: (consult). -#+texinfo_dir_desc: Useful commands built on completing-read. - -#+html: GNU Emacs -#+html: GNU ELPA -#+html: GNU-devel ELPA -#+html: MELPA -#+html: MELPA Stable - -Consult provides search and navigation commands based on the Emacs completion -function [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Minibuffer-Completion.html][completing-read]]. Completion allows you to quickly select an item from a -list of candidates. Consult offers asynchronous and interactive =consult-grep= and -=consult-ripgrep= commands, and the line-based search command =consult-line=. -Furthermore Consult provides an advanced buffer switching command =consult-buffer= -to switch between buffers, recently opened files, bookmarks and buffer-like -candidates from other sources. Some of the Consult commands are enhanced -versions of built-in Emacs commands. For example the command =consult-imenu= -presents a flat list of the Imenu with [[#live-previews][live preview]], [[#narrowing-and-grouping][grouping and narrowing]]. -Please take a look at the [[#available-commands][full list of commands]]. - -Consult is fully compatible with completion systems centered around the standard -Emacs =completing-read= API, notably the default completion system, [[https://github.com/minad/vertico][Vertico]], [[https://github.com/protesilaos/mct][Mct]], -and [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Icomplete.html][Icomplete]]. - -This package keeps the completion system specifics to a minimum. The ability of -the Consult commands to work well with arbitrary completion systems is one of -the main advantages of the package. Consult fits well into existing setups and -it helps you to create a full completion environment out of small and -independent components. - -You can combine the complementary packages [[https://github.com/minad/marginalia/][Marginalia]], [[https://github.com/oantolin/embark/][Embark]] and [[https://github.com/oantolin/orderless][Orderless]] with -Consult. Marginalia enriches the completion display with annotations, e.g., -documentation strings or file information. The versatile Embark package provides -local actions, comparable to a context menu. These actions operate on the -selected candidate in the minibuffer or at point in normal buffers. For example, -when selecting from a list of files, Embark offers an action to delete the file. -Additionally Embark offers a facility to collect completion candidates in a -collect buffer. The section [[#embark-integration][Embark integration]] documents in detail how Consult -and Embark work together. - -#+toc: headlines 8 - -* Screenshots :noexport: - -#+caption: consult-grep -[[https://github.com/minad/consult/blob/screenshots/consult-grep.gif?raw=true]] -Fig. 1: Command =consult-git-grep= - -#+caption: consult-imenu -[[https://github.com/minad/consult/blob/screenshots/consult-imenu.png?raw=true]] -Fig. 2: Command =consult-imenu= - -#+caption: consult-line -[[https://github.com/minad/consult/blob/screenshots/consult-line.png?raw=true]] -Fig. 3: Command =consult-line= - -* Available commands -:properties: -:custom_id: available-commands -:description: Navigation, search, editing commands and more -:end: -#+cindex: commands - -Most Consult commands follow the meaningful naming scheme =consult-=. -Many commands implement a little known but convenient Emacs feature called -"future history", which guesses what input the user wants. At a command prompt -type =M-n= and typically Consult will insert the symbol or thing at point into -the input. - -*TIP:* If you have [[https://github.com/minad/marginalia][Marginalia]] annotators activated, type =M-x ^consult= to see -all Consult commands with their abbreviated description. Alternatively, type -=C-h a ^consult= to get an overview of all Consult variables and functions with -their descriptions. - -** Virtual Buffers -:properties: -:description: Buffers, bookmarks and recent files -:end: -#+cindex: virtual buffers - -#+findex: consult-buffer -#+findex: consult-buffer-other-window -#+findex: consult-buffer-other-frame -#+findex: consult-buffer-other-tab -#+findex: consult-project-buffer -#+findex: consult-recent-file -#+findex: consult-bookmark -- =consult-buffer=: Enhanced version of =switch-to-buffer= with support for virtual - buffers. Supports live preview of buffers and narrowing to the virtual buffer - types. You can type =f SPC= in order to narrow to recent files. Press =SPC= to - show ephemeral buffers. Supported narrowing keys: - - b Buffers - - SPC Hidden buffers - - * Modified buffers - - f Files (Requires =recentf-mode=) - - r File registers - - m Bookmarks - - p Project - - Custom [[#multiple-sources][other sources]] configured in =consult-buffer-sources=. -- =consult-buffer-other-window=, =consult-buffer-other-frame=, - =consult-buffer-other-tab=: Variants of =consult-buffer=. -- =consult-project-buffer=: Variant of =consult-buffer= restricted to buffers and - recent files of the current project. You can add custom sources to - =consult-project-buffer-sources=. The command may prompt you for a project if - you invoke it from outside a project. -- =consult-bookmark=: Select or create bookmark. To select bookmarks you might use the - =consult-buffer= as an alternative, which can include a bookmark virtual buffer - source. Note that =consult-bookmark= supports preview of bookmarks and - narrowing. -- =consult-recent-file=: Select from recent files with preview. - You might prefer the powerful =consult-buffer= instead, which can include - recent files as a virtual buffer source. The =recentf-mode= enables tracking of - recent files. - -** Editing -:properties: -:description: Commands useful for editing -:end: -#+cindex: editing - -#+findex: consult-yank-pop -#+findex: consult-yank-from-kill-ring -#+findex: consult-yank-replace -#+findex: consult-kmacro -- =consult-yank-from-kill-ring=: Enhanced version of =yank= to select an item - from the =kill-ring=. The selected text previewed as overlay in the buffer. -- =consult-yank-pop=: Enhanced version of =yank-pop= with DWIM-behavior, which - either replaces the last =yank= by cycling through the =kill-ring=, or if there - has not been a last =yank= consults the =kill-ring=. The selected text previewed - as overlay in the buffer. -- =consult-yank-replace=: Like =consult-yank-pop=, but always replaces the last - =yank= with an item from the =kill-ring=. -- =consult-kmacro=: Select macro from the macro ring and execute it. - -** Register -:properties: -:description: Searching through registers and fast access -:end: -#+cindex: register - -#+findex: consult-register -#+findex: consult-register-load -#+findex: consult-register-store -#+findex: consult-register-format -#+findex: consult-register-window -- =consult-register=: Select from list of registers. The command - supports narrowing to register types and preview of marker positions. This - command is useful to search the register contents. For quick access use the - commands =consult-register-load=, =consult-register-store= or the built-in Emacs - register commands. -- =consult-register-format=: Set =register-preview-function= to this function for - an enhanced register formatting. See the [[#use-package-example][example configuration]]. -- =consult-register-window=: Replace =register-preview= with this function for a - better register window. See the [[#use-package-example][example configuration]]. -- =consult-register-load=: Utility command to quickly load a register. - The command either jumps to the register value or inserts it. -- =consult-register-store=: Improved UI to store registers depending on the current - context with an action menu. With an active region, store/append/prepend the - contents, optionally deleting the region when a prefix argument is given. - With a numeric prefix argument, store/add the number. Otherwise store point, - frameset, window or kmacro. Usage examples: - * =M-' x=: If no region is active, store point in register =x=. - If a region is active, store the region in register =x=. - * =M-' M-w x=: Store window configuration in register =x=. - * =C-u 100 M-' x=: Store number in register =x=. - -** Navigation -:properties: -:description: Mark rings, outlines and imenu -:end: -#+cindex: navigation - -#+findex: consult-goto-line -#+findex: consult-mark -#+findex: consult-global-mark -#+findex: consult-outline -#+findex: consult-imenu -#+findex: consult-imenu-multi -- =consult-goto-line=: Jump to line number enhanced with live preview. This is a - drop-in replacement for =goto-line=. Enter a line number to jump to the first - column of the given line. Alternatively enter =line:column= in order to jump to - a specific column. -- =consult-mark=: Jump to a marker in the =mark-ring=. Supports live - preview and recursive editing. -- =consult-global-mark=: Jump to a marker in the =global-mark-ring=. - Supports live preview and recursive editing. -- =consult-outline=: Jump to a heading of the outline. Supports narrowing - to a heading level, live preview and recursive editing. -- =consult-imenu=: Jump to imenu item in the current buffer. Supports - live preview, recursive editing and narrowing. -- =consult-imenu-multi=: Jump to imenu item in project buffers, with - the same major mode as the current buffer. Supports live preview, - recursive editing and narrowing. This feature has been inspired by - [[https://github.com/vspinu/imenu-anywhere][imenu-anywhere]]. - -** Search -:properties: -:description: Line search, grep and file search -:end: -#+cindex: search - -#+findex: consult-line -#+findex: consult-line-multi -#+findex: consult-keep-lines -#+findex: consult-focus-lines -- =consult-line=: Enter search string and select from matching lines. - Supports live preview and recursive editing. The symbol at point and the - recent Isearch string are added to the "future history" and can be accessed - by pressing =M-n=. When =consult-line= is bound to the =isearch-mode-map= and - is invoked during a running Isearch, it will use the current Isearch string. -- =consult-line-multi=: Search dynamically across multiple buffers. By default - search across project buffers. If invoked with a prefix argument search across - all buffers. The candidates are computed on demand based on the input. The - command behaves like =consult-grep=, but operates on buffers instead of files. -- =consult-keep-lines=: Replacement for =keep/flush-lines= which uses the current - completion style for filtering the buffer. The function updates the buffer - while typing. In particular =consult-keep-lines= can narrow down an exported - Embark collect buffer further, relying on the same completion filtering as - ~completing-read~. If the input begins with the negation operator, i.e., ~! SPC~, - the filter matches the complement. If a region is active, the region restricts - the filtering. -- =consult-focus-lines=: Temporarily hide lines by filtering them using the - current completion style. Call with =C-u= prefix argument in order to show the - hidden lines again. If the input begins with the negation operator, i.e., ~! - SPC~, the filter matches the complement. In contrast to =consult-keep-lines= this - function does not edit the buffer. If a region is active, the region restricts - the filtering. - -** Grep and Find -:properties: -:description: Searching through the filesystem -:end: -#+cindex: grep -#+cindex: find -#+cindex: locate - -#+findex: consult-grep -#+findex: consult-ripgrep -#+findex: consult-git-grep -#+findex: consult-find -#+findex: consult-fd -#+findex: consult-locate -- =consult-grep=, =consult-ripgrep=, =consult-git-grep=: Search for regular expression - in files. Consult invokes Grep asynchronously, while you enter the search - term. After at least =consult-async-min-input= characters, the search gets - started. Consult splits the input string into two parts, if the first - character is a punctuation character, like =#=. For example - =#regexps#filter-string=, is split at the second =#=. The string =regexps= is passed - to Grep. Note that Consult transforms Emacs regular expressions to expressions - understand by the search program. Always use Emacs regular expressions at the - prompt. If you enter multiple regular expressions separated by space only - lines matching all regular expressions are shown. In order to match space - literally, escape the space with a backslash. The =filter-string= is passed to - the /fast/ Emacs filtering to further narrow down the list of matches. This is - particularly useful if you are using an advanced completion style like - orderless. =consult-grep= supports preview. If the =consult-project-function= - returns non-nil, =consult-grep= searches the current project directory. - Otherwise the =default-directory= is searched. If =consult-grep= is invoked - with prefix argument =C-u M-s g=, you can specify one or more comma-separated files - and directories manually. -- =consult-find=, =consult-fd=, =consult-locate=: Find file by matching the path - against a regexp. Like for =consult-grep=, either the project root or the - current directory is the root directory for the search. The input string is - treated similarly to =consult-grep=, where the first part is passed to find, and - the second part is used for Emacs filtering. Prefix arguments to =consult-find= - work just like those for the consult grep commands. - -** Compilation -:properties: -:description: Jumping to references and compilation errors -:end: -#+cindex: compilation errors - -#+findex: consult-compile-error -#+findex: consult-flymake -#+findex: consult-xref -- =consult-compile-error=: Jump to a compilation error. Supports live preview - narrowing and recursive editing. -- =consult-flymake=: Jump to Flymake diagnostic. Supports live preview and - recursive editing. The command supports narrowing. Press =e SPC=, =w SPC=, =n SPC= - to only show errors, warnings and notes respectively. -- =consult-xref=: Integration with xref. This function can be set as - =xref-show-xrefs-function= and =xref-show-definitions-function=. - -** Histories -:properties: -:description: Navigating histories -:end: -#+cindex: history - -#+findex: consult-complex-command -#+findex: consult-history -#+findex: consult-isearch-history -- =consult-complex-command=: Select a command from the - =command-history=. This command is a =completing-read= version of - =repeat-complex-command= and is also a replacement for the =command-history= - command from chistory.el. -- =consult-history=: Insert a string from the current buffer history, for example - the Eshell or Comint history. You can also invoke this command from the - minibuffer. In that case =consult-history= uses the history stored in the - =minibuffer-history-variable=. If you prefer =completion-at-point=, take a look at - =cape-history= from the [[https://github.com/minad/cape][Cape]] package. -- =consult-isearch-history=: During an Isearch session, this command picks a - search string from history and continues the search with the newly selected - string. Outside of Isearch, the command allows you to pick a string from the - history and starts a new Isearch. =consult-isearch-history= acts as a drop-in - replacement for =isearch-edit-string=. - -** Modes -:properties: -:description: Toggling minor modes and executing commands -:end: -#+cindex: minor mode -#+cindex: major mode - -#+findex: consult-minor-mode-menu -#+findex: consult-mode-command -- =consult-minor-mode-menu=: Enable/disable minor mode. Supports - narrowing to on/off/local/global modes by pressing =i/o/l/g SPC= - respectively. -- =consult-mode-command=: Run a command from the currently active minor or major - modes. Supports narrowing to local-minor/global-minor/major mode via the keys - =l/g/m=. - -** Org Mode -:properties: -:description: Org-specific commands -:end: - -#+findex: consult-org-heading -#+findex: consult-org-agenda -- =consult-org-heading=: Variant of =consult-imenu= or =consult-outline= for Org - buffers. The headline and its ancestors headlines are separated by slashes. - Supports narrowing by heading level, priority and TODO keyword, as well as live - preview and recursive editing. -- =consult-org-agenda=: Jump to an Org agenda heading. Supports narrowing by - heading level, priority and TODO keyword, as well as live preview and - recursive editing. -** Help -:properties: -:description: Searching through help -:end: - -#+findex: consult-info -#+findex: consult-man -- =consult-man=: Find Unix man page, via Unix =apropos= or =man -k=. =consult-man= opens - the selected man page using the Emacs =man= command. -- =consult-info=: Full text search through info pages. If the command is invoked - from within an ~*info*~ buffer, it will search through the current manual. You - may want to create your own commands which search through a predefined set of - info pages, for example: -#+begin_src emacs-lisp -(defun consult-info-emacs () - "Search through Emacs info pages." - (interactive) - (consult-info "emacs" "efaq" "elisp" "cl" "compat")) - -(defun consult-info-org () - "Search through the Org info page." - (interactive) - (consult-info "org")) - -(defun consult-info-completion () - "Search through completion info pages." - (interactive) - (consult-info "vertico" "consult" "marginalia" "orderless" "embark" - "corfu" "cape" "tempel")) -#+end_src - -** Miscellaneous -:properties: -:description: Various other useful commands -:end: - -#+findex: consult-completion-in-region -#+findex: consult-theme -#+findex: consult-preview-at-point -#+findex: consult-preview-at-point-mode -- =consult-theme=: Select a theme and disable all currently enabled themes. - Supports live preview of the theme while scrolling through the candidates. -- =consult-preview-at-point= and =consult-preview-at-point-mode=: Command and minor - mode which previews the candidate at point in the =*Completions*= buffer. This - mode is relevant if you use [[https://git.sr.ht/~protesilaos/mct][Mct]] or the default =*Completions*= UI. -- =consult-completion-in-region=: In case you don't use [[https://github.com/minad/corfu][Corfu]] as your in-buffer - completion UI, this function can be set as =completion-in-region-function=. Then - your minibuffer completion UI (e.g., Vertico or Icomplete) will be used for - =completion-at-point=. - #+begin_src emacs-lisp - ;; Use `consult-completion-in-region' if Vertico is enabled. - ;; Otherwise use the default `completion--in-region' function. - (setq completion-in-region-function - (lambda (&rest args) - (apply (if vertico-mode - #'consult-completion-in-region - #'completion--in-region) - args))) - #+end_src - Instead of =consult-completion-in-region=, you may prefer to see the completions - directly in the buffer as a small popup. In that case, I recommend the [[https://github.com/minad/corfu][Corfu]] - package. There is a technical limitation of =consult-completion-in-region= in - combination with the Lsp modes. The Lsp server relies on the input at point, - in order to generate refined candidate strings. Since the completion is - transferred from the original buffer to the minibuffer, the server does not - receive the updated input. In contrast, in-buffer Lsp completion for example - via Corfu works properly since the completion takes place directly in the - original buffer. - -* Special features -:properties: -:description: Enhancements over built-in `completing-read' -:end: - -Consult enhances =completing-read= with live previews of candidates, additional -narrowing capabilities to candidate groups and asynchronously generated -candidate lists. The internal =consult--read= function, which is used by most -Consult commands, is a thin wrapper around =completing-read= and provides the -special functionality. In order to support multiple candidate sources there -exists the high-level function =consult--multi=. The architecture of Consult -allows it to work with different completion systems in the backend, while still -offering advanced features. - -** Live previews -:properties: -:description: Preview the currently selected candidate -:custom_id: live-previews -:end: -#+cindex: preview - -Some Consult commands support live previews. For example when you scroll through -the items of =consult-line=, the buffer will scroll to the corresponding position. -It is possible to jump back and forth between the minibuffer and the buffer to -perform recursive editing while the search is ongoing. - -Consult enables previews by default. You can disable them by adjusting the -=consult-preview-key= variable. Furthermore it is possible to specify keybindings -which trigger the preview manually as shown in the [[#use-package-example][example configuration]]. The -default setting of =consult-preview-key= is =any= which means that Consult triggers -the preview /immediately/ on any key press when the selected candidate changes. -You can configure each command individually with its own =:preview-key=. The -following settings are possible: - -- Automatic and immediate ='any= -- Automatic and delayed =(list :debounce 0.5 'any)= -- Manual and immediate ="M-."= -- Manual and delayed =(list :debounce 0.5 "M-.")= -- Disabled =nil= - -A safe recommendation is to leave automatic immediate previews enabled in -general and disable the automatic preview only for commands where the preview -may be expensive due to file loading. Internally, Consult uses the -value of =this-command= to determine the =:preview-key= -customized. This means that if you wrap a =consult-*= command within -your own function or command, you will also need to add the name of -/your custom command/ to the =consult-customize= call in order for it -to be considered. - -#+begin_src emacs-lisp -(consult-customize - consult-ripgrep consult-git-grep consult-grep - consult-bookmark consult-recent-file consult-xref - consult--source-bookmark consult--source-file-register - consult--source-recent-file consult--source-project-recent-file - ;; my/command-wrapping-consult ;; disable auto previews inside my command - :preview-key '(:debounce 0.4 any) ;; Option 1: Delay preview - ;; :preview-key "M-.") ;; Option 2: Manual preview -#+end_src - -In this case one may wonder what the difference is between using an Embark -action on the current candidate in comparison to a manually triggered preview. -The main difference is that the files opened by manual preview are closed again -after the completion session. During preview some functionality is disabled to -improve the performance, see for example the customization variables -=consult-preview-variables= and =consult-preview-allowed-hooks=. Only the hooks -listed in =consult-preview-allowed-hooks= are executed when a file is opened -(=find-file-hook=). In order to enable additional font locking during preview, add -the corresponding hooks to the allow list. The following code demonstrates this -for [[https://github.com/minad/org-modern][org-modern]] and [[https://github.com/tarsius/hl-todo][hl-todo]]. - -#+begin_src emacs-lisp -(add-to-list 'consult-preview-allowed-hooks 'global-org-modern-mode-check-buffers) -(add-to-list 'consult-preview-allowed-hooks 'global-hl-todo-mode-check-buffers) -#+end_src - -Files larger than =consult-preview-partial-size= are previewed partially. Delaying -the preview is also useful for =consult-theme=, since the theme preview is slow. -The delay results in a smoother UI experience. - -#+begin_src emacs-lisp -;; Preview on any key press, but delay 0.5s -(consult-customize consult-theme :preview-key '(:debounce 0.5 any)) -;; Preview immediately on M-., on up/down after 0.5s, on any other key after 1s -(consult-customize consult-theme - :preview-key - '("M-." - :debounce 0.5 "" "" - :debounce 1 any)) -#+end_src - -** Narrowing and grouping -:properties: -:description: Restricting the completion to a candidate group -:custom_id: narrowing-and-grouping -:end: -#+cindex: narrowing - -Consult has special support for candidate groups. If the completion UI supports -the grouping functionality, the UI separates the groups with thin lines and -shows group titles. Grouping is useful if the list of candidates consists of -candidates of multiple types or candidates from [[#multiple-sources][multiple sources]], like the -=consult-buffer= command, which shows both buffers and recently opened files. Note -that you can disable the group titles by setting the =:group= property of the -corresponding command to nil using the =consult-customize= macro. - -By entering a narrowing prefix or by pressing a narrowing key it is possible to -restrict the completion candidates to a certain candidate group. When you use -the =consult-buffer= command, you can enter the prefix =b SPC= to restrict list of -candidates to buffers only. If you press =DEL= afterwards, the full candidate list -will be shown again. Furthermore a narrowing prefix key and a widening key can -be configured which can be pressed to achieve the same effect, see the -configuration variables =consult-narrow-key= and =consult-widen-key=. - -After pressing =consult-narrow-key=, the possible narrowing keys can be shown by -pressing =C-h=. When pressing =C-h= after some prefix key, the =prefix-help-command= -is invoked, which shows the keybinding help window by default. As a more compact -alternative, there is the =consult-narrow-help= command which can be bound to a -key, for example =?= or =C-h= in the =consult-narrow-map=, as shown in the [[#use-package-example][example -configuration]]. If [[https://github.com/justbur/emacs-which-key][which-key]] is installed, the narrowing keys are automatically -shown in the which-key window after pressing the =consult-narrow-key=. - -** Asynchronous search -:properties: -:description: Filtering asynchronously generated candidate lists -:end: -#+cindex: asynchronous search - -Consult has support for asynchronous generation of candidate lists. This feature -is used for search commands like =consult-grep=, where the list of matches is -generated dynamically while the user is typing a regular expression. The grep -process is executed in the background. When modifying the regular expression, -the background process is terminated and a new process is started with the -modified regular expression. - -The matches, which have been found, can then be narrowed using the installed -Emacs completion-style. This can be powerful if you are using for example the -=orderless= completion style. - -This two-level filtering is possible by splitting the input string. Part of the -input string is treated as input to grep and part of the input is used for -filtering. There are multiple splitting styles available, configured in -~consult-async-split-styles-alist~: =nil=, =comma=, =semicolon= and =perl=. The default -splitting style is configured with the variable ~consult-async-split-style~. - -With the =comma= and =semicolon= splitting styles, the first word before the comma -or semicolon is passed to grep, the remaining string is used for filtering. The -=nil= splitting style does not perform any splitting, the whole input is passed to -grep. - -The =perl= splitting style splits the input string at a punctuation character, -using a similar syntax as Perl regular expressions. - -Examples: - -- =#defun=: Search for "defun" using grep. -- =#consult embark=: Search for both "consult" and "embark" using grep in any order. -- =#first.*second=: Search for "first" followed by "second" using grep. -- =#\(consult\|embark\)=: Search for "consult" or "embark" using grep. Note the - usage of Emacs-style regular expressions. -- =#defun#consult=: Search for "defun" using grep, filter with the word - "consult". -- =/defun/consult=: It is also possible to use other punctuation - characters. -- =#to#=: Force searching for "to" using grep, since the grep pattern - must be longer than =consult-async-min-input= characters by default. -- =#defun -- --invert-match#=: Pass argument =--invert-match= to grep. - -Asynchronous processes like =find= and =grep= create an error log buffer -=_*consult-async*= (note the leading space), which is useful for -troubleshooting. The prompt has a small indicator showing the process status: - -- =:= the usual prompt colon, before input is provided. -- =*= with warning face, the process is running. -- =:= with success face, success, process exited with an error code of zero. -- =!= with error face, failure, process exited with a nonzero error code. -- =;= with error face, interrupted, for example if more input is provided. - -** Multiple sources -:properties: -:description: Combining candidates from different sources -:custom_id: multiple-sources -:end: -#+cindex: multiple sources - -Multiple synchronous candidate sources can be combined. This feature is used by -the =consult-buffer= command to present buffer-like candidates in a single menu -for quick access. By default =consult-buffer= includes buffers, bookmarks, recent -files and project-specific buffers and files. It is possible to configure the -list of sources via the =consult-buffer-sources= variable. Arbitrary custom -sources can be defined. - -As an example, the bookmark source is defined as follows: - -#+begin_src emacs-lisp -(defvar consult--source-bookmark - `(:name "Bookmark" - :narrow ?m - :category bookmark - :face consult-bookmark - :history bookmark-history - :items ,#'bookmark-all-names - :action ,#'consult--bookmark-action)) -#+end_src - -Required source fields: -- =:category= Completion category. -- =:items= List of strings to select from or function returning list of strings. - A list of cons cells is not supported. - -Optional source fields: -- =:name= Name of the source, used for narrowing, group titles and annotations. -- =:narrow= Narrowing character or =(character . string)= pair. -- =:preview-key= Preview key or keys which trigger preview. -- =:enabled= Function which must return t if the source is enabled. -- =:hidden= When t candidates of this source are hidden by default. -- =:face= Face used for highlighting the candidates. -- =:annotate= Annotation function called for each candidate, returns string. -- =:history= Name of history variable to add selected candidate. -- =:default= Must be t if the first item of the source is the default value. -- =:action= Function called with the selected candidate. -- =:new= Function called with new candidate name, only if =:require-match= is nil. -- =:state= State constructor for the source, must return the state function. -- Other source fields can be added specifically to the use case. - -The =:state= and =:action= fields of the sources deserve a longer explanation. The -=:action= function takes a single argument and is only called after selection with -the selected candidate, if the selection has not been aborted. This -functionality is provided for convenience and easy definition of sources. The -=:state= field is more general. The =:state= function is a constructor function -without arguments, which can perform some setup necessary for the preview. It -must return a closure which takes an ACTION and a CANDIDATE argument. See the -docstring of =consult--with-preview= for more details about the ACTION argument. - -By default, =consult-buffer= previews buffers, bookmarks and files. Loading recent -files or bookmarks can result in expensive operations. However it is possible to -configure a manual preview as follows. - -#+begin_src emacs-lisp -(consult-customize - consult--source-bookmark consult--source-file-register - consult--source-recent-file consult--source-project-recent-file - :preview-key "M-.") -#+end_src - -Sources can be added directly to the =consult-buffer-source= list for convenience. -For example, the following source lists all Org buffers and lets you create new -ones. - -#+begin_src emacs-lisp -(defvar org-source - (list :name "Org Buffer" - :category 'buffer - :narrow ?o - :face 'consult-buffer - :history 'buffer-name-history - :state #'consult--buffer-state - :new - (lambda (name) - (with-current-buffer (get-buffer-create name) - (insert "#+title: " name "\n\n") - (org-mode) - (consult--buffer-action (current-buffer)))) - :items - (lambda () - (consult--buffer-query :mode 'org-mode :as #'buffer-name)))) - -(add-to-list 'consult-buffer-sources 'org-source 'append) -#+end_src - -One can create similar sources for other major modes. See the [[https://github.com/minad/consult/wiki][Consult wiki]] for -many additional source examples. See also the documentation of =consult-buffer= -and of the internal =consult--multi= API. The function =consult--multi= can be used -to create new multi-source commands. - -** Embark integration -:properties: -:description: Actions, Grep/Occur-buffer export -:custom_id: embark-integration -:end: -#+cindex: embark - -*NOTE*: Install the =embark-consult= package from MELPA, which provides -Consult-specific Embark actions and the Occur buffer export. - -Embark is a versatile package which offers context dependent actions, comparable -to a context menu. See the [[https://github.com/oantolin/embark][Embark manual]] for an extensive description of its -capabilities. - -Actions are commands which can operate on the currently selected candidate (or -target in Embark terminology). When completing files, for example the -=delete-file= command is offered. With Embark you can execute arbitrary commands -on the currently selected candidate via =M-x=. - -Furthermore Embark provides the =embark-collect= command, which collects -candidates and presents them in an Embark collect buffer, where further actions -can be applied to them. A related feature is the =embark-export= command, which -exports candidate lists to a buffer of a special type. For example in the case -of file completion, a Dired buffer is opened. - -In the context of Consult, particularly exciting is the possibility to export -the matching lines from =consult-line=, =consult-outline=, =consult-mark= and -=consult-global-mark=. The matching lines are exported to an Occur buffer where -they can be edited via the =occur-edit-mode= (press key =e=). Similarly, Embark -supports exporting the matches found by =consult-grep=, =consult-ripgrep= and -=consult-git-grep= to a Grep buffer, where the matches across files can be edited, -if the [[https://github.com/mhayashi1120/Emacs-wgrep][wgrep]] package is installed. These three workflows are symmetric. - -+ =consult-line= -> =embark-export= to =occur-mode= buffer -> =occur-edit-mode= for editing of matches in buffer. -+ =consult-grep= -> =embark-export= to =grep-mode= buffer -> =wgrep= for editing of all matches. -+ =consult-find= -> =embark-export= to =dired-mode= buffer -> =wdired-change-to-wdired-mode= for editing. - -* Configuration -:properties: -:description: Example configuration and customization variables -:end: - -Consult can be installed from [[https://elpa.gnu.org/packages/consult.html][ELPA]] or [[https://melpa.org/#/consult][MELPA]] via the Emacs built-in package -manager. Alternatively it can be directly installed from the development -repository via other non-standard package managers. - -There is the [[https://github.com/minad/consult/wiki][Consult wiki]], where additional configuration examples can be -contributed. - -*IMPORTANT:* It is recommended that you enable [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Lexical-Binding.html][lexical binding]] in your -configuration. Many Consult-related code snippets require lexical binding, since -they use lambdas and closures. - -** Use-package example -:properties: -:description: Configuration example based on use-package -:custom_id: use-package-example -:end: -#+cindex: use-package - -The Consult package only provides commands and does not add any keybindings or -modes. Therefore the package is non-intrusive but requires a little setup -effort. In order to use the Consult commands, it is advised to add keybindings -for commands which are accessed often. Rarely used commands can be invoked via -=M-x=. Feel free to only bind the commands you consider useful to your workflow. -The configuration shown here relies on the =use-package= macro, which is a -convenient tool to manage package configurations. - -*NOTE:* There is the [[https://github.com/minad/consult/wiki][Consult wiki]], where you can contribute additional -configuration examples. - -#+begin_src emacs-lisp -;; Example configuration for Consult -(use-package consult - ;; Replace bindings. Lazily loaded due by `use-package'. - :bind (;; C-c bindings in `mode-specific-map' - ("C-c M-x" . consult-mode-command) - ("C-c h" . consult-history) - ("C-c k" . consult-kmacro) - ("C-c m" . consult-man) - ("C-c i" . consult-info) - ([remap Info-search] . consult-info) - ;; C-x bindings in `ctl-x-map' - ("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command - ("C-x b" . consult-buffer) ;; orig. switch-to-buffer - ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window - ("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame - ("C-x t b" . consult-buffer-other-tab) ;; orig. switch-to-buffer-other-tab - ("C-x r b" . consult-bookmark) ;; orig. bookmark-jump - ("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer - ;; Custom M-# bindings for fast register access - ("M-#" . consult-register-load) - ("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated) - ("C-M-#" . consult-register) - ;; Other custom bindings - ("M-y" . consult-yank-pop) ;; orig. yank-pop - ;; M-g bindings in `goto-map' - ("M-g e" . consult-compile-error) - ("M-g f" . consult-flymake) ;; Alternative: consult-flycheck - ("M-g g" . consult-goto-line) ;; orig. goto-line - ("M-g M-g" . consult-goto-line) ;; orig. goto-line - ("M-g o" . consult-outline) ;; Alternative: consult-org-heading - ("M-g m" . consult-mark) - ("M-g k" . consult-global-mark) - ("M-g i" . consult-imenu) - ("M-g I" . consult-imenu-multi) - ;; M-s bindings in `search-map' - ("M-s d" . consult-find) ;; Alternative: consult-fd - ("M-s c" . consult-locate) - ("M-s g" . consult-grep) - ("M-s G" . consult-git-grep) - ("M-s r" . consult-ripgrep) - ("M-s l" . consult-line) - ("M-s L" . consult-line-multi) - ("M-s k" . consult-keep-lines) - ("M-s u" . consult-focus-lines) - ;; Isearch integration - ("M-s e" . consult-isearch-history) - :map isearch-mode-map - ("M-e" . consult-isearch-history) ;; orig. isearch-edit-string - ("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string - ("M-s l" . consult-line) ;; needed by consult-line to detect isearch - ("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch - ;; Minibuffer history - :map minibuffer-local-map - ("M-s" . consult-history) ;; orig. next-matching-history-element - ("M-r" . consult-history)) ;; orig. previous-matching-history-element - - ;; Enable automatic preview at point in the *Completions* buffer. This is - ;; relevant when you use the default completion UI. - :hook (completion-list-mode . consult-preview-at-point-mode) - - ;; The :init configuration is always executed (Not lazy) - :init - - ;; Optionally configure the register formatting. This improves the register - ;; preview for `consult-register', `consult-register-load', - ;; `consult-register-store' and the Emacs built-ins. - (setq register-preview-delay 0.5 - register-preview-function #'consult-register-format) - - ;; Optionally tweak the register preview window. - ;; This adds thin lines, sorting and hides the mode line of the window. - (advice-add #'register-preview :override #'consult-register-window) - - ;; Use Consult to select xref locations with preview - (setq xref-show-xrefs-function #'consult-xref - xref-show-definitions-function #'consult-xref) - - ;; Configure other variables and modes in the :config section, - ;; after lazily loading the package. - :config - - ;; Optionally configure preview. The default value - ;; is 'any, such that any key triggers the preview. - ;; (setq consult-preview-key 'any) - ;; (setq consult-preview-key "M-.") - ;; (setq consult-preview-key '("S-" "S-")) - ;; For some commands and buffer sources it is useful to configure the - ;; :preview-key on a per-command basis using the `consult-customize' macro. - (consult-customize - consult-theme :preview-key '(:debounce 0.2 any) - consult-ripgrep consult-git-grep consult-grep - consult-bookmark consult-recent-file consult-xref - consult--source-bookmark consult--source-file-register - consult--source-recent-file consult--source-project-recent-file - ;; :preview-key "M-." - :preview-key '(:debounce 0.4 any)) - - ;; Optionally configure the narrowing key. - ;; Both < and C-+ work reasonably well. - (setq consult-narrow-key "<") ;; "C-+" - - ;; Optionally make narrowing help available in the minibuffer. - ;; You may want to use `embark-prefix-help-command' or which-key instead. - ;; (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help) - - ;; By default `consult-project-function' uses `project-root' from project.el. - ;; Optionally configure a different project root function. - ;;;; 1. project.el (the default) - ;; (setq consult-project-function #'consult--default-project--function) - ;;;; 2. vc.el (vc-root-dir) - ;; (setq consult-project-function (lambda (_) (vc-root-dir))) - ;;;; 3. locate-dominating-file - ;; (setq consult-project-function (lambda (_) (locate-dominating-file "." ".git"))) - ;;;; 4. projectile.el (projectile-project-root) - ;; (autoload 'projectile-project-root "projectile") - ;; (setq consult-project-function (lambda (_) (projectile-project-root))) - ;;;; 5. No project support - ;; (setq consult-project-function nil) -) -#+end_src - -** Custom variables -:properties: -:description: Short description of all customization settings -:end: -#+cindex: customization - -*TIP:* If you have [[https://github.com/minad/marginalia][Marginalia]] installed, type =M-x customize-variable RET -^consult= to see all Consult-specific customizable variables with their current -values and abbreviated description. Alternatively, type =C-h a ^consult= to get -an overview of all Consult variables and functions with their descriptions. - -| Variable | Description | -|----------------------------------+-----------------------------------------------------| -| consult-after-jump-hook | Functions to call after jumping to a location | -| consult-async-input-debounce | Input debounce for asynchronous commands | -| consult-async-input-throttle | Input throttle for asynchronous commands | -| consult-async-min-input | Minimum numbers of input characters | -| consult-async-refresh-delay | Refresh delay for asynchronous commands | -| consult-async-split-style | Splitting style used for async commands | -| consult-async-split-styles-alist | Available splitting styles used for async commands | -| consult-bookmark-narrow | Narrowing configuration for =consult-bookmark= | -| consult-buffer-filter | Filter for =consult-buffer= | -| consult-buffer-sources | List of virtual buffer sources | -| consult-fd-args | Command line arguments for fd | -| consult-find-args | Command line arguments for find | -| consult-fontify-max-size | Buffers larger than this limit are not fontified | -| consult-fontify-preserve | Preserve fontification for line-based commands. | -| consult-git-grep-args | Command line arguments for git-grep | -| consult-goto-line-numbers | Show line numbers for =consult-goto-line= | -| consult-grep-max-columns | Maximal number of columns of the matching lines | -| consult-grep-args | Command line arguments for grep | -| consult-imenu-config | Mode-specific configuration for =consult-imenu= | -| consult-line-numbers-widen | Show absolute line numbers when narrowing is active | -| consult-line-start-from-top | Start the =consult-line= search from the top | -| consult-locate-args | Command line arguments for locate | -| consult-man-args | Command line arguments for man | -| consult-mode-command-filter | Filter for =consult-mode-command= | -| consult-mode-histories | Mode-specific history variables | -| consult-narrow-key | Narrowing prefix key during completion | -| consult-point-placement | Placement of the point when jumping to matches | -| consult-preview-key | Keys which triggers preview | -| consult-preview-allowed-hooks | List of =find-file= hooks to enable during preview | -| consult-preview-excluded-files | Regexps matched against file names during preview | -| consult-preview-max-count | Maximum number of files to keep open during preview | -| consult-preview-partial-size | Files larger than this size are previewed partially | -| consult-preview-partial-chunk | Size of the file chunk which is previewed partially | -| consult-preview-variables | Alist of variables to bind during preview | -| consult-project-buffer-sources | List of virtual project buffer sources | -| consult-project-function | Function which returns current project root | -| consult-register-prefix | Prefix string for register keys during completion | -| consult-ripgrep-args | Command line arguments for ripgrep | -| consult-themes | List of themes to be presented for selection | -| consult-widen-key | Widening key during completion | -| consult-yank-rotate | Rotate kill ring | - -** Fine-tuning of individual commands -:properties: -:alt_title: Fine-tuning -:description: Fine-grained configuration for special requirements -:end: - -*NOTE:* Consult supports fine-grained customization of individual commands. This -configuration feature exists for experienced users with special requirements. -There is the [[https://github.com/minad/consult/wiki][Consult wiki]], where we collect further configuration examples. - -Commands and buffer sources allow flexible, individual customization by using -the =consult-customize= macro. You can override any option passed to the internal -=consult--read= API. Note that since =consult--read= is part of the internal API, -options could be removed, replaced or renamed in future versions of the package. - -Useful options are: -- =:prompt= set the prompt string -- =:preview-key= set the preview key, default is =consult-preview-key= -- =:initial= set the initial input -- =:default= set the default value -- =:history= set the history variable symbol -- =:add-history= add items to the future history, for example symbol at point -- =:sort= enable or disable sorting -- =:group= set to nil to disable candidate grouping and titles. -- =:inherit-input-method= set to non-nil to inherit the input method. - -#+begin_src emacs-lisp -(consult-customize - ;; Disable preview for `consult-theme' completely. - consult-theme :preview-key nil - ;; Set preview for `consult-buffer' to key `M-.' - consult-buffer :preview-key "M-." - ;; For `consult-line' change the prompt and specify multiple preview - ;; keybindings. Note that you should bind and in the - ;; `minibuffer-local-completion-map' or `vertico-map' to the commands which - ;; select the previous or next candidate. - consult-line :prompt "Search: " - :preview-key '("S-" "S-")) -#+end_src - -The configuration values are evaluated at runtime, just before the completion -session is started. Therefore you can use for example =thing-at-point= to adjust -the initial input or the future history. - -#+begin_src emacs-lisp -(consult-customize - consult-line - :add-history (seq-some #'thing-at-point '(region symbol))) - -(defalias 'consult-line-thing-at-point 'consult-line) - -(consult-customize - consult-line-thing-at-point - :initial (thing-at-point 'symbol)) -#+end_src - -Generally it is possible to modify commands for your individual needs by the -following techniques: - -1. Use =consult-customize= in order to change the command or source settings. -2. Create your own wrapper function which passes modified arguments to the Consult functions. -3. Create your own buffer [[#multiple-sources][multi sources]] for =consult-buffer=. -4. Create advices to modify some internal behavior. -5. Write or propose a patch. - -* Recommended packages -:properties: -:description: Related packages recommended for installation -:end: - -I use and recommend this combination of packages: - -- consult: This package -- [[https://github.com/minad/vertico][vertico]]: Fast and minimal vertical completion system -- [[https://github.com/minad/marginalia][marginalia]]: Annotations for the completion candidates -- [[https://github.com/oantolin/embark][embark and embark-consult]]: Action commands, which can act on the completion candidates -- [[https://github.com/oantolin/orderless][orderless]]: Completion style which offers flexible candidate filtering -- [[https://github.com/mhayashi1120/Emacs-wgrep][wgrep]]: Editing of grep buffers. Use with =consult-grep= via =embark-export=. - -There exist multiple fine completion UIs beside Vertico, which are supported by -Consult. Give them a try and find out which interaction model fits best for you. - -- The builtin completion UI, which pops up the =*Completions*= buffer. -- The builtin =icomplete-vertical-mode= in Emacs 28 or newer. -- [[https://git.sr.ht/~protesilaos/mct][mct by Protesilaos Stavrou]]: Minibuffer and Completions in Tandem, which builds - on the default completion UI. - -Note that all packages are independent and can be exchanged with alternative -components, since there exist no hard dependencies. Furthermore it is possible -to get started with only default completion and Consult and add more components -later to the mix. For example you can omit Marginalia if you don't need -annotations. I highly recommend the Embark package, but in order to familiarize -yourself with the other components, you can first start without it - or you could -use with Embark right away and add the other components later on. - -We document a [[https://github.com/minad/consult/wiki/Auxiliary-packages][list of auxiliary packages]] in the Consult wiki. These packages -integrate Consult with special programs or with other packages in the wider -Emacs ecosystem. - -* Bug reports -:properties: -:description: How to create reproducible bug reports -:end: - -If you find a bug or suspect that there is a problem with Consult, please carry -out the following steps: - -1. *Search through the issue tracker* if your issue has been reported before (and - has been resolved eventually) in the meantime. -2. *Remove all packages involved in the suspected bug from your installation.* -3. *Reinstall the newest version of all relevant packages*. Updating alone is not - sufficient, since package.el sometimes causes miscompilation. The list of - packages includes Consult, Compat, Vertico or other completion UIs, - Marginalia, Embark and Orderless. -4. Either use the default completion UI or ensure that exactly one of - =vertico-mode=, =mct-mode=, or =icomplete-mode= is enabled. The unsupported modes - =selectrum-mode=, =ivy-mode=, =helm-mode=, =ido-mode= and =ido-ubiquitous-mode= must be - disabled. -5. Ensure that the =completion-styles= variable is properly configured. Try to set - =completion-styles= to a list including =substring= or =orderless=. -6. Try to reproduce the issue by starting a bare bone Emacs instance with =emacs -Q= - on the command line. Execute the following minimal code snippets in the - scratch buffer. This way we can exclude side effects due to configuration - settings. If other packages are relevant to reproduce the issue, include them - in the minimal configuration snippet. - -Minimal setup with Vertico for =emacs -Q=: -#+begin_src emacs-lisp -(package-initialize) -(require 'consult) -(require 'vertico) -(vertico-mode) -(setq completion-styles '(substring basic)) -#+end_src - -Minimal setup with the default completion system for =emacs -Q=: -#+begin_src emacs-lisp -(package-initialize) -(require 'consult) -(setq completion-styles '(substring basic)) -#+end_src - -Please provide the necessary important information with your bug report: - -- The minimal configuration snippet used to reproduce the issue. -- Your completion UI (Default completion, Vertico, Mct or Icomplete). -- A stack trace in case the bug triggers an exception. -- Your Emacs version, since bugs may be fixed or introduced in newer versions. -- Your operating system, since Emacs behavior varies subtly between Linux, Mac - and Windows. -- The package manager, e.g., straight.el or package.el, used to install the - Emacs packages, in order to exclude update issues. Did you install Consult as - part of the Doom Emacs distribution? -- Do you use Evil? Consult does not provide Evil integration out of the box, but - there is some support in [[https://github.com/emacs-evil/evil-collection][evil-collection]]. - -When evaluating Consult-related code snippets you should enable [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Lexical-Binding.html][lexical binding]]. -Consult often relies on lambdas and lexical closures. - -* Contributions -:properties: -:description: Feature requests and pull requests -:end: - -Consult is a community effort, please participate in the discussions. -Contributions are welcome, but you may want to discuss potential contributions -first. Since this package is part of [[https://elpa.gnu.org/packages/consult.html][GNU ELPA]] contributions require a copyright -assignment to the FSF. - -If you have a proposal, take a look at the [[https://github.com/consult/issues][Consult issue tracker]] and the [[https://github.com/minad/consult/issues/6][Consult -wishlist]]. There have been many prior feature discussions. Please search through -the issue tracker, maybe your issue or feature request has already been -discussed. You can contribute to the [[https://github.com/minad/consult/wiki][Consult wiki]], in case you want to share -small configuration or command snippets. - -* Acknowledgments -:properties: -:description: Contributors and Sources of Inspiration -:end: - -This package took inspiration from [[https://github.com/abo-abo/swiper#counsel][Counsel]] by Oleh Krehel. Some of the Consult -commands originated in the Counsel package or the wiki of the Selectrum package. -This package exists only thanks to the help of these great contributors and -thanks to the feedback of many users. Thank you! - -Code contributions: [[https://github.com/aagon][Aymeric Agon-Rambosson]], [[https://github.com/amosbird][Amos Bird]], [[https://github.com/ashton314][Ashton Wiersdorf]], [[https://github.com/aspiers/][Adam -Spiers]], [[https://github.com/astoff][Augusto Stoffel]], [[https://github.com/clemera/][Clemens Radermacher]], [[https://github.com/fuzy112][Zhengyi]], [[https://github.com/geolessel][Geoffrey Lessel]], [[https://github.com/iostapyshyn][Illia -Ostapyshyn]], [[https://github.com/jakanakaevangeli][jakanakaevangeli]], [[https://github.com/jdtsmith][JD Smith]], [[https://github.com/jyp][Jean-Philippe Bernardy]], [[https://github.com/mattiasdrp][mattiasdrp]], -[[https://github.com/mohamed-abdelnour][Mohamed Abdelnour]], [[https://github.com/mohkale][Mohsin Kaleem]], [[https://github.com/noctuid][Fox Kiester]], [[https://github.com/oantolin/][Omar Antolín Camarena]], [[https://github.com/okamsn/][Earl -Hyatt]], [[https://github.com/omar-polo][Omar Polo]], [[https://github.com/piotrkwiecinski][Piotr Kwiecinski]], [[https://github.com/rswgnu][Robert Weiner]], [[https://github.com/s-kostyaev/][Sergey Kostyaev]], [[https://github.com/scvalex][Alexandru -Scvorțov]], [[https://github.com/tecosaur][Tecosaur]], [[https://github.com/thisirs][Sylvain Rousseau]], [[https://github.com/tomfitzhenry/][Tom Fitzhenry]], [[https://hg.serna.eu][Iñigo Serna]] and [[https://github.com/akreisher][Alex -Kreisher]]. - -Advice and useful discussions: [[https://github.com/Qkessler][Enrique Kessler Martínez]], [[https://github.com/alphapapa/][Adam Porter]], [[https://github.com/bdarcus][Bruce -d'Arcus]], [[https://github.com/clemera/][Clemens Radermacher]], [[https://github.com/dgutov/][Dmitry Gutov]], [[https://github.com/hmelman/][Howard Melman]], [[https://github.com/iyefrat][Itai Y. Efrat]], [[https://github.com/jdtsmith][JD -Smith]], [[https://github.com/manuel-uberti/][Manuel Uberti]], [[https://github.com/monnier/][Stefan Monnier]], [[https://github.com/oantolin/][Omar Antolín Camarena]], [[https://github.com/purcell/][Steve Purcell]], -[[https://github.com/raxod502][Radon Rosborough]], [[https://github.com/tomfitzhenry/][Tom Fitzhenry]] and [[https://protesilaos.com][Protesilaos Stavrou]]. - -#+html: blob - 2158c876254d5ff21b2fae26f9bbc13c9c8ff23c (mode 644) blob + /dev/null --- elpa/consult-1.4/consult-autoloads.el +++ /dev/null @@ -1,445 +0,0 @@ -;;; consult-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 consult.el - -(autoload 'consult-completion-in-region "consult" "\ -Use minibuffer completion as the UI for `completion-at-point'. - -The function is called with 4 arguments: START END COLLECTION -PREDICATE. The arguments and expected return value are as -specified for `completion-in-region'. Use this function as a -value for `completion-in-region-function'. - -(fn START END COLLECTION &optional PREDICATE)") -(autoload 'consult-outline "consult" "\ -Jump to an outline heading, obtained by matching against `outline-regexp'. - -This command supports narrowing to a heading level and candidate -preview. The initial narrowing LEVEL can be given as prefix -argument. The symbol at point is added to the future history. - -(fn &optional LEVEL)" t) -(autoload 'consult-mark "consult" "\ -Jump to a marker in MARKERS list (defaults to buffer-local `mark-ring'). - -The command supports preview of the currently selected marker position. -The symbol at point is added to the future history. - -(fn &optional MARKERS)" t) -(autoload 'consult-global-mark "consult" "\ -Jump to a marker in MARKERS list (defaults to `global-mark-ring'). - -The command supports preview of the currently selected marker position. -The symbol at point is added to the future history. - -(fn &optional MARKERS)" t) -(autoload 'consult-line "consult" "\ -Search for a matching line. - -Depending on the setting `consult-point-placement' the command -jumps to the beginning or the end of the first match on the line -or the line beginning. The default candidate is the non-empty -line next to point. This command obeys narrowing. Optional -INITIAL input can be provided. The search starting point is -changed if the START prefix argument is set. The symbol at point -and the last `isearch-string' is added to the future history. - -(fn &optional INITIAL START)" t) -(autoload 'consult-line-multi "consult" "\ -Search for a matching line in multiple buffers. - -By default search across all project buffers. If the prefix -argument QUERY is non-nil, all buffers are searched. Optional -INITIAL input can be provided. The symbol at point and the last -`isearch-string' is added to the future history. In order to -search a subset of buffers, QUERY can be set to a plist according -to `consult--buffer-query'. - -(fn QUERY &optional INITIAL)" t) -(autoload 'consult-keep-lines "consult" "\ -Select a subset of the lines in the current buffer with live preview. - -The selected lines are kept and the other lines are deleted. When called -interactively, the lines selected are those that match the minibuffer input. In -order to match the inverse of the input, prefix the input with `! '. When -called from Elisp, the filtering is performed by a FILTER function. This -command obeys narrowing. - -FILTER is the filter function. -INITIAL is the initial input. - -(fn FILTER &optional INITIAL)" t) -(autoload 'consult-focus-lines "consult" "\ -Hide or show lines using overlays. - -The selected lines are shown and the other lines hidden. When called -interactively, the lines selected are those that match the minibuffer input. In -order to match the inverse of the input, prefix the input with `! '. With -optional prefix argument SHOW reveal the hidden lines. Alternatively the -command can be restarted to reveal the lines. When called from Elisp, the -filtering is performed by a FILTER function. This command obeys narrowing. - -FILTER is the filter function. -INITIAL is the initial input. - -(fn FILTER &optional SHOW INITIAL)" t) -(autoload 'consult-goto-line "consult" "\ -Read line number and jump to the line with preview. - -Enter either a line number to jump to the first column of the -given line or line:column in order to jump to a specific column. -Jump directly if a line number is given as prefix ARG. The -command respects narrowing and the settings -`consult-goto-line-numbers' and `consult-line-numbers-widen'. - -(fn &optional ARG)" t) -(autoload 'consult-recent-file "consult" "\ -Find recent file using `completing-read'." t) -(autoload 'consult-mode-command "consult" "\ -Run a command from any of the given MODES. - -If no MODES are specified, use currently active major and minor modes. - -(fn &rest MODES)" t) -(autoload 'consult-yank-from-kill-ring "consult" "\ -Select STRING from the kill ring and insert it. -With prefix ARG, put point at beginning, and mark at end, like `yank' does. - -This command behaves like `yank-from-kill-ring' in Emacs 28, which also offers -a `completing-read' interface to the `kill-ring'. Additionally the Consult -version supports preview of the selected string. - -(fn STRING &optional ARG)" t) -(autoload 'consult-yank-pop "consult" "\ -If there is a recent yank act like `yank-pop'. - -Otherwise select string from the kill ring and insert it. -See `yank-pop' for the meaning of ARG. - -This command behaves like `yank-pop' in Emacs 28, which also offers a -`completing-read' interface to the `kill-ring'. Additionally the Consult -version supports preview of the selected string. - -(fn &optional ARG)" t) -(autoload 'consult-yank-replace "consult" "\ -Select STRING from the kill ring. - -If there was no recent yank, insert the string. -Otherwise replace the just-yanked string with the selected string. - -There exists no equivalent of this command in Emacs 28. - -(fn STRING)" t) -(autoload 'consult-bookmark "consult" "\ -If bookmark NAME exists, open it, otherwise create a new bookmark with NAME. - -The command supports preview of file bookmarks and narrowing. See the -variable `consult-bookmark-narrow' for the narrowing configuration. - -(fn NAME)" t) -(autoload 'consult-complex-command "consult" "\ -Select and evaluate command from the command history. - -This command can act as a drop-in replacement for `repeat-complex-command'." t) -(autoload 'consult-history "consult" "\ -Insert string from HISTORY of current buffer. -In order to select from a specific HISTORY, pass the history -variable as argument. INDEX is the name of the index variable to -update, if any. BOL is the function which jumps to the beginning -of the prompt. See also `cape-history' from the Cape package. - -(fn &optional HISTORY INDEX BOL)" t) -(autoload 'consult-isearch-history "consult" "\ -Read a search string with completion from the Isearch history. - -This replaces the current search string if Isearch is active, and -starts a new Isearch session otherwise." t) -(autoload 'consult-minor-mode-menu "consult" "\ -Enable or disable minor mode. - -This is an alternative to `minor-mode-menu-from-indicator'." t) -(autoload 'consult-theme "consult" "\ -Disable current themes and enable THEME from `consult-themes'. - -The command supports previewing the currently selected theme. - -(fn THEME)" t) -(autoload 'consult-buffer "consult" "\ -Enhanced `switch-to-buffer' command with support for virtual buffers. - -The command supports recent files, bookmarks, views and project files as -virtual buffers. Buffers are previewed. Narrowing to buffers (b), files (f), -bookmarks (m) and project files (p) is supported via the corresponding -keys. In order to determine the project-specific files and buffers, the -`consult-project-function' is used. The virtual buffer SOURCES -default to `consult-buffer-sources'. See `consult--multi' for the -configuration of the virtual buffer sources. - -(fn &optional SOURCES)" t) -(autoload 'consult-project-buffer "consult" "\ -Enhanced `project-switch-to-buffer' command with support for virtual buffers. -The command may prompt you for a project directory if it is invoked from -outside a project. See `consult-buffer' for more details." t) -(autoload 'consult-buffer-other-window "consult" "\ -Variant of `consult-buffer', switching to a buffer in another window." t) -(autoload 'consult-buffer-other-frame "consult" "\ -Variant of `consult-buffer', switching to a buffer in another frame." t) -(autoload 'consult-buffer-other-tab "consult" "\ -Variant of `consult-buffer', switching to a buffer in another tab." t) -(autoload 'consult-grep "consult" "\ -Search with `grep' for files in DIR where the content matches a regexp. - -The initial input is given by the INITIAL argument. DIR can be -nil, a directory string or a list of file/directory paths. If -`consult-grep' is called interactively with a prefix argument, -the user can specify the directories or files to search in. -Multiple directories must be separated by comma in the -minibuffer, since they are read via `completing-read-multiple'. -By default the project directory is used if -`consult-project-function' is defined and returns non-nil. -Otherwise the `default-directory' is searched. - -The input string is split, the first part of the string (grep -input) is passed to the asynchronous grep process and the second -part of the string is passed to the completion-style filtering. - -The input string is split at a punctuation character, which is -given as the first character of the input string. The format is -similar to Perl-style regular expressions, e.g., /regexp/. -Furthermore command line options can be passed to grep, specified -behind --. The overall prompt input has the form -`#async-input -- grep-opts#filter-string'. - -Note that the grep input string is transformed from Emacs regular -expressions to Posix regular expressions. Always enter Emacs -regular expressions at the prompt. `consult-grep' behaves like -builtin Emacs search commands, e.g., Isearch, which take Emacs -regular expressions. Furthermore the asynchronous input split -into words, each word must match separately and in any order. -See `consult--regexp-compiler' for the inner workings. In order -to disable transformations of the grep input, adjust -`consult--regexp-compiler' accordingly. - -Here we give a few example inputs: - -#alpha beta : Search for alpha and beta in any order. -#alpha.*beta : Search for alpha before beta. -#\\(alpha\\|beta\\) : Search for alpha or beta (Note Emacs syntax!) -#word -- -C3 : Search for word, include 3 lines as context -#first#second : Search for first, quick filter for second. - -The symbol at point is added to the future history. - -(fn &optional DIR INITIAL)" t) -(autoload 'consult-git-grep "consult" "\ -Search with `git grep' for files in DIR with INITIAL input. -See `consult-grep' for details. - -(fn &optional DIR INITIAL)" t) -(autoload 'consult-ripgrep "consult" "\ -Search with `rg' for files in DIR with INITIAL input. -See `consult-grep' for details. - -(fn &optional DIR INITIAL)" t) -(autoload 'consult-find "consult" "\ -Search for files with `find' in DIR. -The file names must match the input regexp. INITIAL is the -initial minibuffer input. See `consult-grep' for details -regarding the asynchronous search and the arguments. - -(fn &optional DIR INITIAL)" t) -(autoload 'consult-fd "consult" "\ -Search for files with `fd' in DIR. -The file names must match the input regexp. INITIAL is the -initial minibuffer input. See `consult-grep' for details -regarding the asynchronous search and the arguments. - -(fn &optional DIR INITIAL)" t) -(autoload 'consult-locate "consult" "\ -Search with `locate' for files which match input given INITIAL input. - -The input is treated literally such that locate can take advantage of -the locate database index. Regular expressions would often force a slow -linear search through the entire database. The locate process is started -asynchronously, similar to `consult-grep'. See `consult-grep' for more -details regarding the asynchronous search. - -(fn &optional INITIAL)" t) -(autoload 'consult-man "consult" "\ -Search for man page given INITIAL input. - -The input string is not preprocessed and passed literally to the -underlying man commands. The man process is started asynchronously, -similar to `consult-grep'. See `consult-grep' for more details regarding -the asynchronous search. - -(fn &optional INITIAL)" t) -(register-definition-prefixes "consult" '("consult-")) - - -;;; Generated autoloads from consult-compile.el - -(autoload 'consult-compile-error "consult-compile" "\ -Jump to a compilation error in the current buffer. - -This command collects entries from compilation buffers and grep -buffers related to the current buffer. The command supports -preview of the currently selected error." t) -(register-definition-prefixes "consult-compile" '("consult-compile--")) - - -;;; Generated autoloads from consult-flymake.el - -(autoload 'consult-flymake "consult-flymake" "\ -Jump to Flymake diagnostic. -When PROJECT is non-nil then prompt with diagnostics from all -buffers in the current project instead of just the current buffer. - -(fn &optional PROJECT)" t) -(register-definition-prefixes "consult-flymake" '("consult-flymake--")) - - -;;; Generated autoloads from consult-imenu.el - -(autoload 'consult-imenu "consult-imenu" "\ -Select item from flattened `imenu' using `completing-read' with preview. - -The command supports preview and narrowing. See the variable -`consult-imenu-config', which configures the narrowing. -The symbol at point is added to the future history. - -See also `consult-imenu-multi'." t) -(autoload 'consult-imenu-multi "consult-imenu" "\ -Select item from the imenus of all buffers from the same project. - -In order to determine the buffers belonging to the same project, the -`consult-project-function' is used. Only the buffers with the -same major mode as the current buffer are used. See also -`consult-imenu' for more details. In order to search a subset of buffers, -QUERY can be set to a plist according to `consult--buffer-query'. - -(fn &optional QUERY)" t) -(register-definition-prefixes "consult-imenu" '("consult-imenu-")) - - -;;; Generated autoloads from consult-info.el - -(autoload 'consult-info "consult-info" "\ -Full text search through info MANUALS. - -(fn &rest MANUALS)" t) -(register-definition-prefixes "consult-info" '("consult-info--")) - - -;;; Generated autoloads from consult-kmacro.el - -(autoload 'consult-kmacro "consult-kmacro" "\ -Run a chosen keyboard macro. - -With prefix ARG, run the macro that many times. -Macros containing mouse clicks are omitted. - -(fn ARG)" t) -(register-definition-prefixes "consult-kmacro" '("consult-kmacro--")) - - -;;; Generated autoloads from consult-org.el - -(autoload 'consult-org-heading "consult-org" "\ -Jump to an Org heading. - -MATCH and SCOPE are as in `org-map-entries' and determine which -entries are offered. By default, all entries of the current -buffer are offered. - -(fn &optional MATCH SCOPE)" t) -(autoload 'consult-org-agenda "consult-org" "\ -Jump to an Org agenda heading. - -By default, all agenda entries are offered. MATCH is as in -`org-map-entries' and can used to refine this. - -(fn &optional MATCH)" t) -(register-definition-prefixes "consult-org" '("consult-org--")) - - -;;; Generated autoloads from consult-register.el - -(autoload 'consult-register-window "consult-register" "\ -Enhanced drop-in replacement for `register-preview'. - -BUFFER is the window buffer. -SHOW-EMPTY must be t if the window should be shown for an empty register list. - -(fn BUFFER &optional SHOW-EMPTY)") -(autoload 'consult-register-format "consult-register" "\ -Enhanced preview of register REG. -This function can be used as `register-preview-function'. -If COMPLETION is non-nil format the register for completion. - -(fn REG &optional COMPLETION)") -(autoload 'consult-register "consult-register" "\ -Load register and either jump to location or insert the stored text. - -This command is useful to search the register contents. For quick access -to registers it is still recommended to use the register functions -`consult-register-load' and `consult-register-store' or the built-in -built-in register access functions. The command supports narrowing, see -`consult-register--narrow'. Marker positions are previewed. See -`jump-to-register' and `insert-register' for the meaning of prefix ARG. - -(fn &optional ARG)" t) -(autoload 'consult-register-load "consult-register" "\ -Do what I mean with a REG. - -For a window configuration, restore it. For a number or text, insert it. -For a location, jump to it. See `jump-to-register' and `insert-register' -for the meaning of prefix ARG. - -(fn REG &optional ARG)" t) -(autoload 'consult-register-store "consult-register" "\ -Store register dependent on current context, showing an action menu. - -With an active region, store/append/prepend the contents, optionally -deleting the region when a prefix ARG is given. With a numeric prefix -ARG, store or add the number. Otherwise store point, frameset, window or -kmacro. - -(fn ARG)" t) -(register-definition-prefixes "consult-register" '("consult-register-")) - - -;;; Generated autoloads from consult-xref.el - -(autoload 'consult-xref "consult-xref" "\ -Show xrefs with preview in the minibuffer. - -This function can be used for `xref-show-xrefs-function'. -See `xref-show-xrefs-function' for the description of the -FETCHER and ALIST arguments. - -(fn FETCHER &optional ALIST)") -(register-definition-prefixes "consult-xref" '("consult-xref--")) - -;;; End of scraped data - -(provide 'consult-autoloads) - -;; Local Variables: -;; version-control: never -;; no-byte-compile: t -;; no-update-autoloads: t -;; no-native-compile: t -;; coding: utf-8-emacs-unix -;; End: - -;;; consult-autoloads.el ends here blob - 2d8c4bdf3c27f6c9b722e9376c583ebc6b926beb (mode 644) blob + /dev/null --- elpa/consult-1.4/consult-compile.el +++ /dev/null @@ -1,127 +0,0 @@ -;;; consult-compile.el --- Provides the command `consult-compile-error' -*- lexical-binding: t -*- - -;; Copyright (C) 2021-2024 Free Software Foundation, Inc. - -;; This file is 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: - -;; Provides the command `consult-compile-error'. This is an extra -;; package, to allow lazy loading of compile.el. The -;; `consult-compile-error' command is autoloaded. - -;;; Code: - -(require 'consult) -(require 'compile) - -(defvar consult-compile--history nil) - -(defconst consult-compile--narrow - '((?e . "Error") - (?w . "Warning") - (?i . "Info"))) - -(defun consult-compile--font-lock (str) - "Apply `font-lock' faces in STR, copy them to `face'." - (let ((pos 0) (len (length str))) - (while (< pos len) - (let* ((face (get-text-property pos 'font-lock-face str)) - (end (or (text-property-not-all pos len 'font-lock-face face str) len))) - (put-text-property pos end 'face face str) - (setq pos end))) - str)) - -(defun consult-compile--error-candidates (buffer) - "Return alist of errors and positions in BUFFER, a compilation buffer." - (with-current-buffer buffer - (let ((candidates) - (pos (point-min))) - (save-excursion - (while (setq pos (compilation-next-single-property-change pos 'compilation-message)) - (when-let (msg (get-text-property pos 'compilation-message)) - (goto-char pos) - (push (propertize - (consult-compile--font-lock (consult--buffer-substring pos (pos-eol))) - 'consult--type (pcase (compilation--message->type msg) - (0 ?i) - (1 ?w) - (_ ?e)) - 'consult--candidate (point-marker)) - candidates)))) - (nreverse candidates)))) - -(defun consult-compile--lookup (marker) - "Lookup error position given error MARKER." - (when-let (buffer (and marker (marker-buffer marker))) - (with-current-buffer buffer - (let ((next-error-highlight nil) - (compilation-current-error marker) - (overlay-arrow-position overlay-arrow-position)) - (ignore-errors - (save-window-excursion - (compilation-next-error-function 0) - (point-marker))))))) - -(defun consult-compile--compilation-buffers (file) - "Return a list of compilation buffers relevant to FILE." - (consult--buffer-query - :sort 'alpha :predicate - (lambda (buffer) - (with-current-buffer buffer - (and (compilation-buffer-internal-p) - (file-in-directory-p file default-directory)))))) - -(defun consult-compile--state () - "Like `consult--jump-state', also setting the current compilation error." - (let ((jump (consult--jump-state))) - (lambda (action marker) - (let ((pos (consult-compile--lookup marker))) - (when-let (buffer (and (eq action 'return) - marker - (marker-buffer marker))) - (with-current-buffer buffer - (setq compilation-current-error marker - overlay-arrow-position marker))) - (funcall jump action pos))))) - -;;;###autoload -(defun consult-compile-error () - "Jump to a compilation error in the current buffer. - -This command collects entries from compilation buffers and grep -buffers related to the current buffer. The command supports -preview of the currently selected error." - (interactive) - (consult--read - (or (mapcan #'consult-compile--error-candidates - (or (consult-compile--compilation-buffers - default-directory) - (user-error "No compilation buffers found for the current buffer"))) - (user-error "No compilation errors found")) - :prompt "Go to error: " - :category 'consult-compile-error - :sort nil - :require-match t - :history t ;; disable history - :lookup #'consult--lookup-candidate - :group (consult--type-group consult-compile--narrow) - :narrow (consult--type-narrow consult-compile--narrow) - :history '(:input consult-compile--history) - :state (consult-compile--state))) - -(provide 'consult-compile) -;;; consult-compile.el ends here blob - 3061ffe7426e58663cf9916e16b6e30e0783f04c (mode 644) blob + /dev/null --- elpa/consult-1.4/consult-flymake.el +++ /dev/null @@ -1,116 +0,0 @@ -;;; consult-flymake.el --- Provides the command `consult-flymake' -*- lexical-binding: t -*- - -;; Copyright (C) 2021-2024 Free Software Foundation, Inc. - -;; This file is 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: - -;; Provides the command `consult-flymake'. This is an extra package, -;; to allow lazy loading of flymake.el. The `consult-flymake' command -;; is autoloaded. - -;;; Code: - -(require 'consult) -(require 'flymake) -(eval-when-compile (require 'cl-lib)) - -(defconst consult-flymake--narrow - '((?e . "Error") - (?w . "Warning") - (?n . "Note"))) - -(defun consult-flymake--candidates (diags) - "Return Flymake errors from DIAGS as formatted candidates. -DIAGS should be a list of diagnostics as returned from `flymake-diagnostics'." - (let* ((diags - (mapcar - (lambda (diag) - (let ((buffer (flymake-diagnostic-buffer diag)) - (type (flymake-diagnostic-type diag))) - (when (buffer-live-p buffer) - (with-current-buffer buffer - (save-excursion - (without-restriction - (goto-char (flymake-diagnostic-beg diag)) - (list (buffer-name buffer) - (line-number-at-pos) - type - (flymake-diagnostic-text diag) - (point-marker) - (flymake-diagnostic-end diag) - (pcase (flymake--lookup-type-property type 'flymake-category) - ('flymake-error ?e) - ('flymake-warning ?w) - (_ ?n))))))))) - diags)) - (diags (or (delq nil diags) - (user-error "No flymake errors (Status: %s)" - (if (seq-difference (flymake-running-backends) - (flymake-reporting-backends)) - 'running 'finished)))) - (buffer-width (cl-loop for x in diags maximize (length (nth 0 x)))) - (line-width (cl-loop for x in diags maximize (length (number-to-string (nth 1 x))))) - (fmt (format "%%-%ds %%-%dd %%-7s %%s" buffer-width line-width))) - (mapcar - (pcase-lambda (`(,buffer ,line ,type ,text ,beg ,end ,narrow)) - (propertize (format fmt buffer line - (propertize (format "%s" (flymake--lookup-type-property - type 'flymake-type-name type)) - 'face (flymake--lookup-type-property - type 'mode-line-face 'flymake-error)) - text) - 'consult--candidate (list beg (cons 0 (- end beg))) - 'consult--type narrow)) - ;; Sort by buffer, severity and position. - (sort diags - (pcase-lambda (`(,b1 _ ,t1 _ ,m1 _) `(,b2 _ ,t2 _ ,m2 _)) - (let ((s1 (flymake--severity t1)) - (s2 (flymake--severity t2))) - (or - (string-lessp b1 b2) - (and (string-equal b1 b2) - (or - (> s1 s2) - (and (= s1 s2) - (< m1 m2))))))))))) - -;;;###autoload -(defun consult-flymake (&optional project) - "Jump to Flymake diagnostic. -When PROJECT is non-nil then prompt with diagnostics from all -buffers in the current project instead of just the current buffer." - (interactive "P") - (consult--forbid-minibuffer) - (consult--read - (consult-flymake--candidates - (if-let (((and project (fboundp 'flymake--project-diagnostics))) - (project (project-current))) - (flymake--project-diagnostics project) - (flymake-diagnostics))) - :prompt "Flymake diagnostic: " - :category 'consult-flymake-error - :history t ;; disable history - :require-match t - :sort nil - :group (consult--type-group consult-flymake--narrow) - :narrow (consult--type-narrow consult-flymake--narrow) - :lookup #'consult--lookup-candidate - :state (consult--jump-state))) - -(provide 'consult-flymake) -;;; consult-flymake.el ends here blob - 3b072a60bc07611d89c53e55be057b219c5c074a (mode 644) blob + /dev/null --- elpa/consult-1.4/consult-imenu.el +++ /dev/null @@ -1,260 +0,0 @@ -;;; consult-imenu.el --- Consult commands for imenu -*- lexical-binding: t -*- - -;; Copyright (C) 2021-2024 Free Software Foundation, Inc. - -;; This file is 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: - -;; Provides imenu-related Consult commands. - -;;; Code: - -(require 'consult) -(require 'imenu) - -(defcustom consult-imenu-config - '((emacs-lisp-mode :toplevel "Functions" - :types ((?f "Functions" font-lock-function-name-face) - (?m "Macros" font-lock-function-name-face) - (?p "Packages" font-lock-constant-face) - (?t "Types" font-lock-type-face) - (?v "Variables" font-lock-variable-name-face)))) - "Imenu configuration, faces and narrowing keys used by `consult-imenu'. - -For each type a narrowing key and a name must be specified. The -face is optional. The imenu representation provided by the -backend usually puts functions directly at the toplevel. -`consult-imenu' moves them instead under the type specified by -:toplevel." - :type '(repeat (cons symbol plist)) - :group 'consult) - -(defface consult-imenu-prefix - '((t :inherit consult-key)) - "Face used to highlight imenu prefix in `consult-imenu'." - :group 'consult-faces) - -(defvar consult-imenu--history nil) -(defvar-local consult-imenu--cache nil) - -(defun consult-imenu--switch-buffer (name pos buf fn &rest args) - "Switch buffer before invoking special menu items. -NAME is the item name. -POS is the position. -BUF is the buffer. -FN is the original special item function. -ARGS are the arguments to the special item function." - (funcall consult--buffer-display buf) - (apply fn name pos args)) - -(defun consult-imenu--normalize (pos) - "Return normalized imenu POS." - (pcase pos - ;; Create marker from integer item - ((pred integerp) (setq pos (copy-marker pos))) - ;; Semantic uses overlay for positions - ((pred overlayp) (setq pos (copy-marker (overlay-start pos)))) - ;; Wrap special item - (`(,pos ,fn . ,args) - (setq pos `(,pos ,#'consult-imenu--switch-buffer ,(current-buffer) - ,fn ,@args)))) - (if (or (consp pos) - (eq imenu-default-goto-function #'imenu-default-goto-function)) - pos - (list pos #'consult-imenu--switch-buffer (current-buffer) - imenu-default-goto-function))) - -(defun consult-imenu--flatten (prefix face list types) - "Flatten imenu LIST. -PREFIX is prepended in front of all items. -FACE is the item face. -TYPES is the mode-specific types configuration." - (mapcan - (lambda (item) - (if (imenu--subalist-p item) - (let* ((name (concat (car item))) - (next-prefix name) - (next-face face)) - (add-face-text-property 0 (length name) - 'consult-imenu-prefix 'append name) - (if prefix - (setq next-prefix (concat prefix "/" name)) - (when-let (type (cdr (assoc name types))) - (put-text-property 0 (length name) 'consult--type (car type) name) - (setq next-face (cadr type)))) - (consult-imenu--flatten next-prefix next-face (cdr item) types)) - (list (cons - (if prefix - (let ((key (concat prefix " " (car item)))) - (add-face-text-property (1+ (length prefix)) (length key) - face 'append key) - key) - (car item)) - (consult-imenu--normalize (cdr item)))))) - list)) - -(defun consult-imenu--compute () - "Compute imenu candidates." - (consult--forbid-minibuffer) - (let* ((imenu-use-markers t) - ;; Generate imenu, see `imenu--make-index-alist'. - (items (imenu--truncate-items - (save-excursion - (without-restriction - (funcall imenu-create-index-function))))) - (config (cdr (seq-find (lambda (x) (derived-mode-p (car x))) consult-imenu-config)))) - ;; Fix toplevel items, e.g., emacs-lisp-mode toplevel items are functions - (when-let (toplevel (plist-get config :toplevel)) - (let ((tops (seq-remove (lambda (x) (listp (cdr x))) items)) - (rest (seq-filter (lambda (x) (listp (cdr x))) items))) - (setq items (nconc rest (and tops (list (cons toplevel tops))))))) - ;; Apply our flattening in order to ease searching the imenu. - (consult-imenu--flatten - nil nil items - (mapcar (pcase-lambda (`(,x ,y ,z)) (list y x z)) - (plist-get config :types))))) - -(defun consult-imenu--deduplicate (items) - "Deduplicate imenu ITEMS by appending a counter." - ;; Some imenu backends generate duplicate items (e.g. for overloaded methods in java) - (let ((ht (make-hash-table :test #'equal :size (length items)))) - (dolist (item items) - (if-let (count (gethash (car item) ht)) - (setcar item (format "%s (%s)" (car item) - (puthash (car item) (1+ count) ht))) - (puthash (car item) 0 ht))))) - -(defun consult-imenu--items () - "Return cached imenu candidates, may error." - (unless (equal (car consult-imenu--cache) (buffer-modified-tick)) - (setq consult-imenu--cache (cons (buffer-modified-tick) (consult-imenu--compute)))) - (cdr consult-imenu--cache)) - -(defun consult-imenu--items-safe () - "Return cached imenu candidates, will not error." - (condition-case err - (consult-imenu--items) - (t (message "Cannot create Imenu for buffer %s (%s)" - (buffer-name) (error-message-string err)) - nil))) - -(defun consult-imenu--multi-items (buffers) - "Return all imenu items from BUFFERS." - (consult--with-increased-gc - (let ((reporter (make-progress-reporter "Collecting" 0 (length buffers)))) - (prog1 - (apply #'append - (seq-map-indexed (lambda (buf idx) - (with-current-buffer buf - (prog1 (consult-imenu--items-safe) - (progress-reporter-update - reporter (1+ idx) (buffer-name))))) - buffers)) - (progress-reporter-done reporter))))) - -(defun consult-imenu--jump (item) - "Jump to imenu ITEM via `consult--jump'. -In contrast to the builtin `imenu' jump function, -this function can jump across buffers." - (pcase item - (`(,name ,pos ,fn . ,args) - (push-mark nil t) - (apply fn name pos args)) - (`(,_ . ,pos) - (consult--jump pos)) - (_ (error "Unknown imenu item: %S" item))) - (run-hooks 'imenu-after-jump-hook)) - -(defun consult-imenu--narrow () - "Return narrowing configuration for the current buffer." - (mapcar (lambda (x) (cons (car x) (cadr x))) - (plist-get (cdr (seq-find (lambda (x) (derived-mode-p (car x))) - consult-imenu-config)) - :types))) - -(defun consult-imenu--group () - "Create a imenu group function for the current buffer." - (when-let (narrow (consult-imenu--narrow)) - (lambda (cand transform) - (let ((type (get-text-property 0 'consult--type cand))) - (cond - ((and transform type) - (substring cand (1+ (next-single-property-change 0 'consult--type cand)))) - (transform cand) - (type (alist-get type narrow))))))) - -(defun consult-imenu--select (prompt items) - "Select from imenu ITEMS given PROMPT string." - (consult-imenu--deduplicate items) - (consult-imenu--jump - (consult--read - (or items (user-error "Imenu is empty")) - :state - (let ((preview (consult--jump-preview))) - (lambda (action cand) - ;; Only preview simple menu items which are markers, - ;; in order to avoid any bad side effects. - (funcall preview action (and (markerp (cdr cand)) (cdr cand))))) - :narrow - (when-let (narrow (consult-imenu--narrow)) - (list :predicate - (lambda (cand) - (eq (get-text-property 0 'consult--type (car cand)) consult--narrow)) - :keys narrow)) - :group (consult-imenu--group) - :prompt prompt - :require-match t - :category 'imenu - :lookup #'consult--lookup-cons - :history 'consult-imenu--history - :add-history (thing-at-point 'symbol) - :sort nil))) - -;;;###autoload -(defun consult-imenu () - "Select item from flattened `imenu' using `completing-read' with preview. - -The command supports preview and narrowing. See the variable -`consult-imenu-config', which configures the narrowing. -The symbol at point is added to the future history. - -See also `consult-imenu-multi'." - (interactive) - (consult-imenu--select - "Go to item: " - (consult--slow-operation "Building Imenu..." - (consult-imenu--items)))) - -;;;###autoload -(defun consult-imenu-multi (&optional query) - "Select item from the imenus of all buffers from the same project. - -In order to determine the buffers belonging to the same project, the -`consult-project-function' is used. Only the buffers with the -same major mode as the current buffer are used. See also -`consult-imenu' for more details. In order to search a subset of buffers, -QUERY can be set to a plist according to `consult--buffer-query'." - (interactive "P") - (unless (keywordp (car-safe query)) - (setq query (list :sort 'alpha :mode major-mode - :directory (and (not query) 'project)))) - (let ((buffers (consult--buffer-query-prompt "Go to item" query))) - (consult-imenu--select (car buffers) - (consult-imenu--multi-items (cdr buffers))))) - -(provide 'consult-imenu) -;;; consult-imenu.el ends here blob - 8c58caff145bcfeb1c0c42f662ebb45733b44fd6 (mode 644) blob + /dev/null --- elpa/consult-1.4/consult-info.el +++ /dev/null @@ -1,181 +0,0 @@ -;;; consult-info.el --- Search through the info manuals -*- lexical-binding: t -*- - -;; Copyright (C) 2021-2024 Free Software Foundation, Inc. - -;; This file is 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: - -;; Provides the command `consult-info'. This is an extra package, -;; to allow lazy loading of info.el. The `consult-info' command -;; is autoloaded. - -;;; Code: - -(require 'consult) -(require 'info) - -(defvar consult-info--history nil) - -(defun consult-info--candidates (manuals input) - "Dynamically find lines in MANUALS matching INPUT." - (pcase-let* ((`(,regexps . ,hl) - (funcall consult--regexp-compiler input 'emacs t)) - (re (concat "\\(\^_\n\\(?:.*Node:[ \t]*\\([^,\t\n]+\\)\\)?.*\n\\)\\|" (car regexps))) - (candidates nil) - (cand-idx 0) - (last-node nil) - (full-node nil)) - (pcase-dolist (`(,manual . ,buf) manuals) - (with-current-buffer buf - (setq last-node nil full-node nil) - (widen) - (goto-char (point-min)) - ;; TODO Info has support for subfiles, which is currently not supported - ;; by the `consult-info' search routine. Fortunately most (or all?) - ;; Emacs info files are generated with the --no-split option. See the - ;; comment in doc/emacs/Makefile.in. Given the computing powers these - ;; days split info files are probably also not necessary anymore. - ;; However it could happen that info files installed as part of the - ;; Linux distribution are split. - (while (and (not (eobp)) (re-search-forward re nil t)) - (if (match-end 1) - (progn - (if-let ((node (match-string 2))) - (unless (equal node last-node) - (setq full-node (concat "(" manual ")" node) - last-node node)) - (setq last-node nil full-node nil)) - (goto-char (1+ (pos-eol)))) - (let ((bol (pos-bol)) - (eol (pos-eol))) - (goto-char bol) - (when (and - full-node - ;; Information separator character - (>= (- (point) 2) (point-min)) - (not (eq (char-after (- (point) 2)) ?\^_)) - ;; Non-blank line, only printable characters on the line. - (not (looking-at-p "^\\s-*$")) - (looking-at-p "^[[:print:]]*$") - ;; Matches all regexps - (seq-every-p (lambda (r) - (goto-char bol) - (re-search-forward r eol t)) - (cdr regexps))) - (let ((cand (concat - (funcall hl (buffer-substring-no-properties bol eol)) - (consult--tofu-encode cand-idx)))) - (put-text-property 0 1 'consult--info (list full-node bol buf) cand) - (cl-incf cand-idx) - (push cand candidates))) - (goto-char (1+ eol))))))) - (nreverse candidates))) - -(defun consult-info--position (cand) - "Return position information for CAND." - (when-let ((pos (and cand (get-text-property 0 'consult--info cand))) - (matches (consult--point-placement cand 0)) - (dest (+ (cadr pos) (car matches)))) - `( ,(cdr matches) ,dest . ,pos))) - -(defun consult-info--action (cand) - "Jump to info CAND." - (pcase (consult-info--position cand) - (`( ,_matches ,pos ,node ,_bol ,_buf) - (info node) - (widen) - (goto-char pos) - (Info-select-node) - (run-hooks 'consult-after-jump-hook)))) - -(defun consult-info--state () - "Info manual preview state." - (let ((preview (consult--jump-preview))) - (lambda (action cand) - (pcase action - ('preview - (setq cand (consult-info--position cand)) - (funcall preview 'preview - (pcase cand - (`(,matches ,pos ,_node ,_bol ,buf) - (cons (set-marker (make-marker) pos buf) matches)))) - (let (Info-history Info-history-list Info-history-forward) - (when cand (ignore-errors (Info-select-node))))) - ('return - (consult-info--action cand)))))) - -(defun consult-info--group (cand transform) - "Return title for CAND or TRANSFORM the candidate." - (if transform cand - (car (get-text-property 0 'consult--info cand)))) - -(defun consult-info--prepare-buffers (manuals fun) - "Prepare buffers for MANUALS and call FUN with buffers." - (declare (indent 1)) - (let (buffers) - (unwind-protect - (let ((reporter (make-progress-reporter "Preparing" 0 (length manuals)))) - (consult--with-increased-gc - (seq-do-indexed - (lambda (manual idx) - (push (cons manual (generate-new-buffer (format "*info-preview-%s*" manual))) - buffers) - (with-current-buffer (cdar buffers) - (let (Info-history Info-history-list Info-history-forward) - (Info-mode) - (Info-find-node manual "Top"))) - (progress-reporter-update reporter (1+ idx) manual)) - manuals)) - (progress-reporter-done reporter) - (funcall fun (reverse buffers))) - (dolist (buf buffers) - (kill-buffer (cdr buf)))))) - -;;;###autoload -(defun consult-info (&rest manuals) - "Full text search through info MANUALS." - (interactive - (if Info-current-file - (list (file-name-base Info-current-file)) - (info-initialize) - (completing-read-multiple - "Info Manuals: " - (info--manual-names current-prefix-arg) - nil t))) - (consult-info--prepare-buffers manuals - (lambda (buffers) - (consult--read - (consult--dynamic-collection - (apply-partially #'consult-info--candidates buffers)) - :state (consult-info--state) - :prompt - (format "Info (%s): " - (string-join (if (length> manuals 3) - `(,@(seq-take manuals 3) ,"…") - manuals) - ", ")) - :require-match t - :sort nil - :category 'consult-info - :history '(:input consult-info--history) - :group #'consult-info--group - :initial (consult--async-split-initial "") - :add-history (consult--async-split-thingatpt 'symbol) - :lookup #'consult--lookup-member)))) - -(provide 'consult-info) -;;; consult-info.el ends here blob - 3e3418507c285db574b7d8b552de1e7f7e8b11d3 (mode 644) blob + /dev/null --- elpa/consult-1.4/consult-kmacro.el +++ /dev/null @@ -1,90 +0,0 @@ -;;; consult-kmacro.el --- Provides the command `consult-kmacro' -*- lexical-binding: t -*- - -;; Copyright (C) 2021-2024 Free Software Foundation, Inc. - -;; This file is 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: - -;; Provides the command `consult-kmacro'. This is an extra package, -;; to allow lazy loading of kmacro.el. The `consult-kmacro' command -;; is autoloaded. - -;;; Code: - -(require 'consult) -(require 'kmacro) - -(defvar consult-kmacro--history nil) - -(defun consult-kmacro--candidates () - "Return alist of kmacros and indices." - (thread-last - ;; List of macros - (append (and last-kbd-macro (list (kmacro-ring-head))) kmacro-ring) - ;; Emacs 29 uses OClosures. I like OClosures but it would have been better - ;; if public APIs wouldn't change like that. - (mapcar (lambda (x) - (if (eval-when-compile (> emacs-major-version 28)) - (list (kmacro--keys x) (kmacro--counter x) (kmacro--format x) x) - `(,@x ,x)))) - ;; Filter mouse clicks - (seq-remove (lambda (x) (seq-some #'mouse-event-p (car x)))) - ;; Format macros - (mapcar (pcase-lambda (`(,keys ,counter ,format ,km)) - (propertize - (format-kbd-macro keys 1) - 'consult--candidate km - 'consult-kmacro--annotation - ;; If the counter is 0 and the counter format is its default, - ;; then there is a good chance that the counter isn't actually - ;; being used. This can only be wrong when a user - ;; intentionally starts the counter with a negative value and - ;; then increments it to 0. - (cond - ((not (equal format "%d")) ;; show counter for non-default format - (format " (counter=%d, format=%s) " counter format)) - ((/= counter 0) ;; show counter if non-zero - (format " (counter=%d)" counter)))))) - (delete-dups))) - -;;;###autoload -(defun consult-kmacro (arg) - "Run a chosen keyboard macro. - -With prefix ARG, run the macro that many times. -Macros containing mouse clicks are omitted." - (interactive "p") - (let ((km (consult--read - (or (consult-kmacro--candidates) - (user-error "No keyboard macros defined")) - :prompt "Keyboard macro: " - :category 'consult-kmacro - :require-match t - :sort nil - :history 'consult-kmacro--history - :annotate - (lambda (cand) - (get-text-property 0 'consult-kmacro--annotation cand)) - :lookup #'consult--lookup-candidate))) - ;; Kmacros are lambdas (oclosures) on Emacs 29 - (funcall (if (eval-when-compile (> emacs-major-version 28)) - km - (kmacro-lambda-form km)) - arg))) - -(provide 'consult-kmacro) -;;; consult-kmacro.el ends here blob - 317dc3d393cb7ee4b159a204ab5a90303b7b36e9 (mode 644) blob + /dev/null --- elpa/consult-1.4/consult-org.el +++ /dev/null @@ -1,157 +0,0 @@ -;;; consult-org.el --- Consult commands for org-mode -*- lexical-binding: t -*- - -;; Copyright (C) 2021-2024 Free Software Foundation, Inc. - -;; This file is 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: - -;; Provides a `completing-read' interface for Org mode navigation. -;; This is an extra package, to allow lazy loading of Org. - -;;; Code: - -(require 'consult) -(require 'org) - -(defvar consult-org--history nil) - -(defun consult-org--narrow () - "Narrowing configuration for `consult-org' commands." - (let ((todo-kws - (seq-filter - (lambda (x) (<= ?a (car x) ?z)) - (mapcar (lambda (s) - (pcase-let ((`(,a ,b) (split-string s "("))) - (cons (downcase (string-to-char (or b a))) a))) - (apply #'append (mapcar #'cdr org-todo-keywords)))))) - (list :predicate - (lambda (cand) - (pcase-let ((`(,level ,todo . ,prio) - (get-text-property 0 'consult-org--heading cand))) - (cond - ((<= ?1 consult--narrow ?9) (<= level (- consult--narrow ?0))) - ((<= ?A consult--narrow ?Z) (eq prio consult--narrow)) - (t (equal todo (alist-get consult--narrow todo-kws)))))) - :keys - (nconc (mapcar (lambda (c) (cons c (format "Level %c" c))) - (number-sequence ?1 ?9)) - (mapcar (lambda (c) (cons c (format "Priority %c" c))) - (number-sequence (max ?A org-highest-priority) - (min ?Z org-lowest-priority))) - todo-kws)))) - -(defun consult-org--headings (prefix match scope &rest skip) - "Return a list of Org heading candidates. - -If PREFIX is non-nil, prefix the candidates with the buffer name. -MATCH, SCOPE and SKIP are as in `org-map-entries'." - (let (buffer (idx 0)) - (apply - #'org-map-entries - (lambda () - ;; Reset the cache when the buffer changes, since `org-get-outline-path' uses the cache - (unless (eq buffer (buffer-name)) - (setq buffer (buffer-name) - org-outline-path-cache nil)) - (pcase-let* ((`(_ ,level ,todo ,prio ,_hl ,tags) (org-heading-components)) - (tags (if org-use-tag-inheritance - (when-let ((tags (org-get-tags))) - (concat ":" (string-join tags ":") ":")) - tags)) - (cand (org-format-outline-path - (org-get-outline-path 'with-self 'use-cache) - most-positive-fixnum))) - (when tags - (put-text-property 0 (length tags) 'face 'org-tag tags)) - (setq cand (if prefix - (concat buffer " " cand (and tags " ") - tags (consult--tofu-encode idx)) - (concat cand (and tags " ") - tags (consult--tofu-encode idx)))) - (cl-incf idx) - (add-text-properties 0 1 - `(org-marker ,(point-marker) - consult-org--heading (,level ,todo . ,prio)) - cand) - cand)) - match scope skip))) - -(defun consult-org--annotate () - "Generate annotation function for `consult-org-heading'." - (let (buf) - (when (derived-mode-p #'org-mode) - (setq buf (current-buffer))) - (lambda (cand) - (unless (buffer-live-p buf) - (setq buf (seq-find (lambda (b) - (with-current-buffer b (derived-mode-p #'org-mode))) - (buffer-list)))) - (pcase-let ((`(,_level ,kwd . ,prio) - (get-text-property 0 'consult-org--heading cand))) - (consult--annotate-align - cand - (concat - (propertize (or kwd "") 'face - (with-current-buffer (or buf (current-buffer)) - ;; `org-get-todo-face' must be called inside an Org buffer - (org-get-todo-face kwd))) - (and prio (format #(" [#%c]" 1 6 (face org-priority)) prio)))))))) - -;;;###autoload -(defun consult-org-heading (&optional match scope) - "Jump to an Org heading. - -MATCH and SCOPE are as in `org-map-entries' and determine which -entries are offered. By default, all entries of the current -buffer are offered." - (interactive (unless (derived-mode-p #'org-mode) - (user-error "Must be called from an Org buffer"))) - (let ((prefix (not (memq scope '(nil tree region region-start-level file))))) - (consult--read - (consult--slow-operation "Collecting headings..." - (or (consult-org--headings prefix match scope) - (user-error "No headings"))) - :prompt "Go to heading: " - :category 'org-heading - :sort nil - :require-match t - :history '(:input consult-org--history) - :narrow (consult-org--narrow) - :state (consult--jump-state) - :annotate (consult-org--annotate) - :group - (when prefix - (lambda (cand transform) - (let ((name (buffer-name - (marker-buffer - (get-text-property 0 'org-marker cand))))) - (if transform (substring cand (1+ (length name))) name)))) - :lookup (apply-partially #'consult--lookup-prop 'org-marker)))) - -;;;###autoload -(defun consult-org-agenda (&optional match) - "Jump to an Org agenda heading. - -By default, all agenda entries are offered. MATCH is as in -`org-map-entries' and can used to refine this." - (interactive) - (unless org-agenda-files - (user-error "No agenda files")) - (consult-org-heading match 'agenda)) - -(provide 'consult-org) -;;; consult-org.el ends here blob - 8748e153a6de8d96cbe9ee340a8455f7fa42db6a (mode 644) blob + /dev/null --- elpa/consult-1.4/consult-pkg.el +++ /dev/null @@ -1,2 +0,0 @@ -;; Generated package description from consult.el -*- no-byte-compile: t -*- -(define-package "consult" "1.4" "Consulting completing-read" '((emacs "27.1") (compat "29.1.4.4")) :commit "0eab65fe3273c97a422c99ee426eef7f2d2dffa4" :maintainer '("Daniel Mendler" . "mail@daniel-mendler.de") :keywords '("matching" "files" "completion") :url "https://github.com/minad/consult") blob - 4e7c4c7895901319b24433dd5a97f776015333d9 (mode 644) blob + /dev/null --- elpa/consult-1.4/consult-register.el +++ /dev/null @@ -1,327 +0,0 @@ -;;; consult-register.el --- Consult commands for registers -*- lexical-binding: t -*- - -;; Copyright (C) 2021-2024 Free Software Foundation, Inc. - -;; This file is 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: - -;; Provides register-related Consult commands. - -;;; Code: - -(require 'consult) -(require 'kmacro) - -(defcustom consult-register-prefix #("#" 0 1 (face consult-key)) - "Prepend prefix in front of register keys during completion." - :type '(choice (const nil) string) - :group 'consult) - -(defvar consult-register--narrow - '((?n . "Number") - (?s . "String") - (?p . "Point") - (?r . "Rectangle") - (?t . "Frameset") - (?k . "Kmacro") - (?f . "File") - (?b . "Buffer") - (?w . "Window")) - "Register type names. -Each element of the list must have the form (char . name).") - -(cl-defun consult-register--format-value (val) - "Format generic register VAL as string." - (with-output-to-string (register-val-describe val nil))) - -(cl-defgeneric consult-register--describe (val) - "Describe generic register VAL." - (list (consult-register--format-value val))) - -(cl-defmethod consult-register--describe ((val number)) - "Describe numeric register VAL." - (list (consult-register--format-value val) 'consult--type ?n)) - -(cl-defmethod consult-register--describe ((val string)) - "Describe string register VAL." - (list val 'consult--type - (if (eq (car (get-text-property 0 'yank-handler val)) - 'rectangle--insert-for-yank) - ?r ?s))) - -(cl-defmethod consult-register--describe ((val marker)) - "Describe marker register VAL." - (with-current-buffer (marker-buffer val) - (save-excursion - (without-restriction - (goto-char val) - (let* ((line (line-number-at-pos)) - (str (propertize (consult--line-with-mark val) - 'consult-location (cons val line)))) - (list (consult--format-file-line-match (buffer-name) line str) - 'multi-category `(consult-location . ,str) - 'consult--type ?p)))))) - -(defmacro consult-register--describe-kmacro () - "Generate method which describes kmacro register." - `(cl-defmethod consult-register--describe ((val ,(if (< emacs-major-version 30) 'kmacro-register 'kmacro))) - (list (consult-register--format-value val) 'consult--type ?k))) -(consult-register--describe-kmacro) - -(cl-defmethod consult-register--describe ((val (head file))) - "Describe file register VAL." - (list (propertize (abbreviate-file-name (cdr val)) 'face 'consult-file) - 'consult--type ?f 'multi-category `(file . ,(cdr val)))) - -(cl-defmethod consult-register--describe ((val (head buffer))) - "Describe buffer register VAL." - (list (propertize (cdr val) 'face 'consult-buffer) - 'consult--type ?f 'multi-category `(buffer . ,(cdr val)))) - -(cl-defmethod consult-register--describe ((val (head file-query))) - "Describe file-query register VAL." - (list (format "%s at position %d" - (propertize (abbreviate-file-name (cadr val)) - 'face 'consult-file) - (caddr val)) - 'consult--type ?f 'multi-category `(file . ,(cadr val)))) - -(cl-defmethod consult-register--describe ((val cons)) - "Describe rectangle or window-configuration register VAL." - (cond - ((stringp (car val)) - (list (string-join val "\n") 'consult--type ?r)) - ((window-configuration-p (car val)) - (list (consult-register--format-value val) - 'consult--type ?w)) - (t (list (consult-register--format-value val))))) - -(with-eval-after-load 'frameset - (cl-defmethod consult-register--describe ((val frameset-register)) - "Describe frameset register VAL." - (list (consult-register--format-value val) 'consult--type ?t))) - -;;;###autoload -(defun consult-register-window (buffer &optional show-empty) - "Enhanced drop-in replacement for `register-preview'. - -BUFFER is the window buffer. -SHOW-EMPTY must be t if the window should be shown for an empty register list." - (let ((regs (consult-register--alist 'noerror)) - (separator - (and (display-graphic-p) - (propertize #(" \n" 0 1 (display (space :align-to right))) - 'face '(:inherit consult-separator :height 1 :underline t))))) - (when (or show-empty regs) - (with-current-buffer-window buffer - (cons 'display-buffer-at-bottom - '((window-height . fit-window-to-buffer) - (preserve-size . (nil . t)))) - nil - (setq-local cursor-in-non-selected-windows nil - mode-line-format nil - truncate-lines t - window-min-height 1 - window-resize-pixelwise t) - (insert (mapconcat - (lambda (reg) - (concat (funcall register-preview-function reg) separator)) - regs nil)))))) - -;;;###autoload -(defun consult-register-format (reg &optional completion) - "Enhanced preview of register REG. -This function can be used as `register-preview-function'. -If COMPLETION is non-nil format the register for completion." - (pcase-let* ((`(,key . ,val) reg) - (key-str (propertize (single-key-description key) 'face 'consult-key)) - (key-len (max 3 (length key-str))) - (`(,str . ,props) (consult-register--describe val))) - (when (string-search "\n" str) - (let* ((lines (seq-take (seq-remove #'string-blank-p (split-string str "\n")) 3)) - (space (apply #'min most-positive-fixnum - (mapcar (lambda (x) (string-match-p "[^ ]" x)) lines)))) - (setq str (mapconcat (lambda (x) (substring x space)) - lines (concat "\n" (make-string (1+ key-len) ?\s)))))) - (setq str (concat - (and completion consult-register-prefix) - key-str (make-string (- key-len (length key-str)) ?\s) " " - str (and (not completion) "\n"))) - (when completion - (add-text-properties - 0 (length str) - `(consult--candidate ,(car reg) ,@props) - str)) - str)) - -(defun consult-register--alist (&optional noerror filter) - "Return register list, sorted and filtered with FILTER. -Raise an error if the list is empty and NOERROR is nil." - (or (sort (seq-filter - ;; Sometimes, registers are made without a `cdr'. - ;; Such registers don't do anything, and can be ignored. - (lambda (x) (and (cdr x) (or (not filter) (funcall filter x)))) - register-alist) - #'car-less-than-car) - (and (not noerror) (user-error "All registers are empty")))) - -(defun consult-register--candidates (&optional filter) - "Return formatted completion candidates, filtered with FILTER." - (mapcar (lambda (reg) (consult-register-format reg 'completion)) - (consult-register--alist nil filter))) - -;;;###autoload -(defun consult-register (&optional arg) - "Load register and either jump to location or insert the stored text. - -This command is useful to search the register contents. For quick access -to registers it is still recommended to use the register functions -`consult-register-load' and `consult-register-store' or the built-in -built-in register access functions. The command supports narrowing, see -`consult-register--narrow'. Marker positions are previewed. See -`jump-to-register' and `insert-register' for the meaning of prefix ARG." - (interactive "P") - (consult-register-load - (consult--read - (consult-register--candidates) - :prompt "Register: " - :category 'multi-category - :state - (let ((preview (consult--jump-preview))) - (lambda (action cand) - ;; Preview only markers - (funcall preview action - (when-let (reg (get-register cand)) - (and (markerp reg) reg))))) - :group (consult--type-group consult-register--narrow) - :narrow (consult--type-narrow consult-register--narrow) - :sort nil - :require-match t - :history t ;; disable history - :lookup #'consult--lookup-candidate) - arg)) - -;;;###autoload -(defun consult-register-load (reg &optional arg) - "Do what I mean with a REG. - -For a window configuration, restore it. For a number or text, insert it. -For a location, jump to it. See `jump-to-register' and `insert-register' -for the meaning of prefix ARG." - (interactive - (list - (and (consult-register--alist) - (register-read-with-preview "Load register: ")) - current-prefix-arg)) - (condition-case err - (jump-to-register reg arg) - (user-error - (unless (string-search "access aborted" (error-message-string err)) - (insert-register reg (not arg)))))) - -(defun consult-register--action (action-list) - "Read register key and execute action from ACTION-LIST. - -This function is derived from `register-read-with-preview'." - (let* ((buffer "*Register Preview*") - (prefix (car action-list)) - (action-list (cdr action-list)) - (action (car (nth 0 action-list))) - (preview - (lambda () - (unless (get-buffer-window buffer) - (register-preview buffer 'show-empty) - (when-let (win (get-buffer-window buffer)) - (with-selected-window win - (let ((inhibit-read-only t)) - (goto-char (point-max)) - (insert - (propertize (concat prefix ": ") 'face 'consult-help) - (mapconcat - (lambda (x) - (concat (propertize (format "M-%c" (car x)) 'face 'consult-key) - " " (propertize (cadr x) 'face 'consult-help))) - action-list " ")) - (fit-window-to-buffer))))))) - (timer (when (numberp register-preview-delay) - (run-at-time register-preview-delay nil preview))) - (help-chars (seq-remove #'get-register (cons help-char help-event-list))) - key reg) - (unwind-protect - (while (not reg) - (while (memq (setq key - (read-key (propertize (caddr (assq action action-list)) - 'face 'minibuffer-prompt))) - help-chars) - (funcall preview)) - (setq key (if (and (eql key ?\e) (characterp last-input-event)) - ;; in terminal Emacs M-letter is read as two keys, ESC and the letter, - ;; use what would have been read in graphical Emacs - (logior #x8000000 last-input-event) - last-input-event)) - (cond - ((or (eq ?\C-g key) - (eq 'escape key) - (eq ?\C-\[ key)) - (keyboard-quit)) - ((and (numberp key) (assq (logxor #x8000000 key) action-list)) - (setq action (logxor #x8000000 key))) - ((characterp key) - (setq reg key)) - (t (user-error "Non-character input")))) - (when (timerp timer) - (cancel-timer timer)) - (let ((w (get-buffer-window buffer))) - (when (window-live-p w) - (delete-window w))) - (when (get-buffer buffer) - (kill-buffer buffer))) - (when reg - (funcall (cadddr (assq action action-list)) reg)))) - -;;;###autoload -(defun consult-register-store (arg) - "Store register dependent on current context, showing an action menu. - -With an active region, store/append/prepend the contents, optionally -deleting the region when a prefix ARG is given. With a numeric prefix -ARG, store or add the number. Otherwise store point, frameset, window or -kmacro." - (interactive "P") - (consult-register--action - (cond - ((use-region-p) - (let ((beg (region-beginning)) - (end (region-end))) - `("Region" - (?c "copy" "Copy region to register: " ,(lambda (r) (copy-to-register r beg end arg t))) - (?a "append" "Append region to register: " ,(lambda (r) (append-to-register r beg end arg))) - (?p "prepend" "Prepend region to register: " ,(lambda (r) (prepend-to-register r beg end arg)))))) - ((numberp arg) - `(,(format "Number %s" arg) - (?s "store" ,(format "Store %s in register: " arg) ,(lambda (r) (number-to-register arg r))) - (?a "add" ,(format "Add %s to register: " arg) ,(lambda (r) (increment-register arg r))))) - (t - `("Store" - (?p "point" "Point to register: " ,#'point-to-register) - (?f "file" "File to register: " ,(lambda (r) (set-register r `(file . ,(buffer-file-name))))) - (?t "frameset" "Frameset to register: " ,#'frameset-to-register) - (?w "window" "Window to register: " ,#'window-configuration-to-register) - ,@(and last-kbd-macro `((?k "kmacro" "Kmacro to register: " ,#'kmacro-to-register)))))))) - -(provide 'consult-register) -;;; consult-register.el ends here blob - 7188e3727037bce321a02f8f52ed55a427969031 (mode 644) blob + /dev/null --- elpa/consult-1.4/consult-xref.el +++ /dev/null @@ -1,122 +0,0 @@ -;;; consult-xref.el --- Xref integration for Consult -*- lexical-binding: t -*- - -;; Copyright (C) 2021-2024 Free Software Foundation, Inc. - -;; This file is 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: - -;; Provides Xref integration for Consult. This is an extra package, to -;; allow lazy loading of xref.el. The `consult-xref' function is -;; autoloaded. - -;;; Code: - -(require 'consult) -(require 'xref) - -(defvar consult-xref--history nil) - -(defvar consult-xref--fetcher nil - "The current xref fetcher. -The fetch is stored globally such that it can be accessed by - Embark for `embark-export'.") - -(defun consult-xref--candidates () - "Return xref candidate list." - (let ((root (consult--project-root))) - (mapcar (lambda (xref) - (let* ((loc (xref-item-location xref)) - (group (if (fboundp 'xref--group-name-for-display) - ;; This function is available in xref 1.3.2 - (xref--group-name-for-display - (xref-location-group loc) root) - (xref-location-group loc))) - (cand (consult--format-file-line-match - group - (or (xref-location-line loc) 0) - (xref-item-summary xref)))) - (add-text-properties - 0 1 `(consult-xref ,xref consult--prefix-group ,group) cand) - cand)) - (funcall consult-xref--fetcher)))) - -(defun consult-xref--preview (display) - "Xref preview with DISPLAY function." - (let ((open (consult--temporary-files)) - (preview (consult--jump-preview))) - (lambda (action cand) - (unless cand - (funcall open)) - (let ((consult--buffer-display display)) - (funcall preview action - (when-let (loc (and cand (eq action 'preview) - (xref-item-location cand))) - (let ((type (type-of loc))) - ;; Only preview file and buffer markers - (pcase type - ('xref-buffer-location - (xref-location-marker loc)) - ((or 'xref-file-location 'xref-etags-location) - (consult--marker-from-line-column - (funcall open - ;; xref-location-group returns the file name - (let ((xref-file-name-display 'abs)) - (xref-location-group loc))) - (xref-location-line loc) - (if (eq type 'xref-file-location) - (xref-file-location-column loc) - 0))))))))))) - -;;;###autoload -(defun consult-xref (fetcher &optional alist) - "Show xrefs with preview in the minibuffer. - -This function can be used for `xref-show-xrefs-function'. -See `xref-show-xrefs-function' for the description of the -FETCHER and ALIST arguments." - (let* ((consult-xref--fetcher fetcher) - (candidates (consult-xref--candidates)) - (display (alist-get 'display-action alist))) - (unless candidates - (user-error "No xref locations")) - (xref-pop-to-location - (if (cdr candidates) - (apply - #'consult--read - candidates - (append - (consult--customize-get #'consult-xref) - (list - :prompt "Go to xref: " - :history 'consult-xref--history - :require-match t - :sort nil - :category 'consult-xref - :group #'consult--prefix-group - :state - ;; do not preview other frame - (when-let (fun (pcase-exhaustive display - ('frame nil) - ('window #'switch-to-buffer-other-window) - ('nil #'switch-to-buffer))) - (consult-xref--preview fun)) - :lookup (apply-partially #'consult--lookup-prop 'consult-xref)))) - (get-text-property 0 'consult-xref (car candidates))) - display))) - -(provide 'consult-xref) -;;; consult-xref.el ends here blob - 0e98799932b32af2589a4abf6d015bda90200bcc (mode 644) blob + /dev/null --- elpa/consult-1.4/consult.el +++ /dev/null @@ -1,5228 +0,0 @@ -;;; consult.el --- Consulting completing-read -*- lexical-binding: t -*- - -;; Copyright (C) 2021-2024 Free Software Foundation, Inc. - -;; Author: Daniel Mendler and Consult contributors -;; Maintainer: Daniel Mendler -;; Created: 2020 -;; Version: 1.4 -;; Package-Requires: ((emacs "27.1") (compat "29.1.4.4")) -;; Homepage: https://github.com/minad/consult -;; Keywords: matching, files, completion - -;; This file is 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: - -;; Consult implements a set of `consult-' commands, which aim to -;; improve the way you use Emacs. The commands are founded on -;; `completing-read', which selects from a list of candidate strings. -;; Consult provides an enhanced buffer switcher `consult-buffer' and -;; search and navigation commands like `consult-imenu' and -;; `consult-line'. Searching through multiple files is supported by the -;; asynchronous `consult-grep' command. Many Consult commands support -;; previewing candidates. If a candidate is selected in the completion -;; view, the buffer shows the candidate immediately. - -;; The Consult commands are compatible with multiple completion systems -;; based on the Emacs `completing-read' API, including the default -;; completion system, Vertico, Mct and Icomplete. - -;; See the README for an overview of the available Consult commands and -;; the documentation of the configuration and installation of the -;; package. - -;; The full list of contributors can be found in the acknowledgments -;; section of the README. - -;;; Code: - -(eval-when-compile - (require 'cl-lib) - (require 'subr-x)) -(require 'compat) -(require 'bookmark) - -(defgroup consult nil - "Consulting `completing-read'." - :link '(info-link :tag "Info Manual" "(consult)") - :link '(url-link :tag "Homepage" "https://github.com/minad/consult") - :link '(emacs-library-link :tag "Library Source" "consult.el") - :group 'files - :group 'outlines - :group 'minibuffer - :prefix "consult-") - -;;;; Customization - -(defcustom consult-narrow-key nil - "Prefix key for narrowing during completion. - -Good choices for this key are \"<\" and \"C-+\" for example. The -key must be a string accepted by `key-valid-p'." - :type '(choice key (const :tag "None" nil))) - -(defcustom consult-widen-key nil - "Key used for widening during completion. - -If this key is unset, defaults to twice the `consult-narrow-key'. -The key must be a string accepted by `key-valid-p'." - :type '(choice key (const :tag "None" nil))) - -(defcustom consult-project-function - #'consult--default-project-function - "Function which returns project root directory. -The function takes one boolean argument MAY-PROMPT. If -MAY-PROMPT is non-nil, the function may ask the prompt the user -for a project directory. The root directory is used by -`consult-buffer' and `consult-grep'." - :type `(choice - (const :tag "Default project function" ,#'consult--default-project-function) - (function :tag "Custom function") - (const :tag "No project integration" nil))) - -(defcustom consult-async-refresh-delay 0.2 - "Refreshing delay of the completion UI for asynchronous commands. - -The completion UI is only updated every -`consult-async-refresh-delay' seconds. This applies to -asynchronous commands like for example `consult-grep'." - :type '(float :tag "Delay in seconds")) - -(defcustom consult-async-input-throttle 0.5 - "Input throttle for asynchronous commands. - -The asynchronous process is started only every -`consult-async-input-throttle' seconds. This applies to asynchronous -commands, e.g., `consult-grep'." - :type '(float :tag "Delay in seconds")) - -(defcustom consult-async-input-debounce 0.2 - "Input debounce for asynchronous commands. - -The asynchronous process is started only when there has not been new -input for `consult-async-input-debounce' seconds. This applies to -asynchronous commands, e.g., `consult-grep'." - :type '(float :tag "Delay in seconds")) - -(defcustom consult-async-min-input 3 - "Minimum number of characters needed, before asynchronous process is called. - -This applies to asynchronous commands, e.g., `consult-grep'." - :type '(natnum :tag "Number of characters")) - -(defcustom consult-async-split-style 'perl - "Async splitting style, see `consult-async-split-styles-alist'." - :type '(choice (const :tag "No splitting" nil) - (const :tag "Comma" comma) - (const :tag "Semicolon" semicolon) - (const :tag "Perl" perl))) - -(defcustom consult-async-split-styles-alist - `((nil :function ,#'consult--split-nil) - (comma :separator ?, :function ,#'consult--split-separator) - (semicolon :separator ?\; :function ,#'consult--split-separator) - (perl :initial "#" :function ,#'consult--split-perl)) - "Async splitting styles." - :type '(alist :key-type symbol :value-type plist)) - -(defcustom consult-mode-histories - '((eshell-mode eshell-history-ring eshell-history-index eshell-bol) - (comint-mode comint-input-ring comint-input-ring-index comint-bol) - (term-mode term-input-ring term-input-ring-index term-bol)) - "Alist of mode histories (mode history index bol). -The histories can be rings or lists. Index, if provided, is a -variable to set to the index of the selection within the ring or -list. Bol, if provided is a function which jumps to the beginning -of the line after the prompt." - :type '(alist :key-type symbol - :value-type (group :tag "Include Index" - (symbol :tag "List/Ring") - (symbol :tag "Index Variable") - (symbol :tag "Bol Function")))) - -(defcustom consult-themes nil - "List of themes (symbols or regexps) to be presented for selection. -nil shows all `custom-available-themes'." - :type '(repeat (choice symbol regexp))) - -(defcustom consult-after-jump-hook (list #'recenter) - "Function called after jumping to a location. - -Commonly used functions for this hook are `recenter' and -`reposition-window'. You may want to add a function which pulses -the current line, e.g., `pulse-momentary-highlight-one-line' is -supported on Emacs 28 and newer. The hook called during preview -and for the jump after selection." - :type 'hook) - -(defcustom consult-line-start-from-top nil - "Start search from the top if non-nil. -Otherwise start the search at the current line and wrap around." - :type 'boolean) - -(defcustom consult-point-placement 'match-beginning - "Where to leave point when jumping to a match. -This setting affects the command `consult-line' and the `consult-grep' variants." - :type '(choice (const :tag "Beginning of the line" line-beginning) - (const :tag "Beginning of the match" match-beginning) - (const :tag "End of the match" match-end))) - -(defcustom consult-line-numbers-widen t - "Show absolute line numbers when narrowing is active. - -See also `display-line-numbers-widen'." - :type 'boolean) - -(defcustom consult-goto-line-numbers t - "Show line numbers for `consult-goto-line'." - :type 'boolean) - -(defcustom consult-fontify-preserve t - "Preserve fontification for line-based commands." - :type 'boolean) - -(defcustom consult-fontify-max-size 1048576 - "Buffers larger than this byte limit are not fontified. - -This is necessary in order to prevent a large startup time -for navigation commands like `consult-line'." - :type '(natnum :tag "Buffer size in bytes")) - -(defcustom consult-buffer-filter - '("\\` " - "\\`\\*Completions\\*\\'" - "\\`\\*Flymake log\\*\\'" - "\\`\\*Semantic SymRef\\*\\'" - "\\`\\*tramp/.*\\*\\'") - "Filter regexps for `consult-buffer'. - -The default setting is to filter ephemeral buffer names beginning -with a space character, the *Completions* buffer and a few log -buffers. The regular expressions are matched case sensitively." - :type '(repeat regexp)) - -(defcustom consult-buffer-sources - '(consult--source-hidden-buffer - consult--source-modified-buffer - consult--source-buffer - consult--source-recent-file - consult--source-file-register - consult--source-bookmark - consult--source-project-buffer-hidden - consult--source-project-recent-file-hidden) - "Sources used by `consult-buffer'. -See also `consult-project-buffer-sources'. -See `consult--multi' for a description of the source data structure." - :type '(repeat symbol)) - -(defcustom consult-project-buffer-sources - '(consult--source-project-buffer - consult--source-project-recent-file) - "Sources used by `consult-project-buffer'. -See also `consult-buffer-sources'. -See `consult--multi' for a description of the source data structure." - :type '(repeat symbol)) - -(defcustom consult-mode-command-filter - '(;; Filter commands - "-mode\\'" "--" - ;; Filter whole features - simple mwheel time so-long recentf tab-bar tab-line) - "Filter commands for `consult-mode-command'." - :type '(repeat (choice symbol regexp))) - -(defcustom consult-grep-max-columns 300 - "Maximal number of columns of grep output." - :type 'natnum) - -(defconst consult--grep-match-regexp - "\\`\\(?:\\./\\)?\\([^\n\0]+\\)\0\\([0-9]+\\)\\([-:\0]\\)" - "Regexp used to match file and line of grep output.") - -(defcustom consult-grep-args - '("grep" (consult--grep-exclude-args) - "--null --line-buffered --color=never --ignore-case\ - --with-filename --line-number -I -r") - "Command line arguments for grep, see `consult-grep'. -The dynamically computed arguments are appended. -Can be either a string, or a list of strings or expressions." - :type '(choice string (repeat (choice string sexp)))) - -(defcustom consult-git-grep-args - "git --no-pager grep --null --color=never --ignore-case\ - --extended-regexp --line-number -I" - "Command line arguments for git-grep, see `consult-git-grep'. -The dynamically computed arguments are appended. -Can be either a string, or a list of strings or expressions." - :type '(choice string (repeat (choice string sexp)))) - -(defcustom consult-ripgrep-args - "rg --null --line-buffered --color=never --max-columns=1000 --path-separator /\ - --smart-case --no-heading --with-filename --line-number --search-zip" - "Command line arguments for ripgrep, see `consult-ripgrep'. -The dynamically computed arguments are appended. -Can be either a string, or a list of strings or expressions." - :type '(choice string (repeat (choice string sexp)))) - -(defcustom consult-find-args - "find . -not ( -iwholename */.[a-z]* -prune )" - "Command line arguments for find, see `consult-find'. -The dynamically computed arguments are appended. -Can be either a string, or a list of strings or expressions." - :type '(choice string (repeat (choice string sexp)))) - -(defcustom consult-fd-args - '((if (executable-find "fdfind" 'remote) "fdfind" "fd") - "--full-path --color=never") - "Command line arguments for fd, see `consult-fd'. -The dynamically computed arguments are appended. -Can be either a string, or a list of strings or expressions." - :type '(choice string (repeat (choice string sexp)))) - -(defcustom consult-locate-args - "locate --ignore-case" ;; --existing not supported by Debian plocate - "Command line arguments for locate, see `consult-locate'. -The dynamically computed arguments are appended. -Can be either a string, or a list of strings or expressions." - :type '(choice string (repeat (choice string sexp)))) - -(defcustom consult-man-args - "man -k" - "Command line arguments for man, see `consult-man'. -The dynamically computed arguments are appended. -Can be either a string, or a list of strings or expressions." - :type '(choice string (repeat (choice string sexp)))) - -(defcustom consult-preview-key 'any - "Preview trigger keys, can be nil, `any', a single key or a list of keys. -Debouncing can be specified via the `:debounce' attribute. The -individual keys must be strings accepted by `key-valid-p'." - :type '(choice (const :tag "Any key" any) - (list :tag "Debounced" - (const :debounce) - (float :tag "Seconds" 0.1) - (const any)) - (const :tag "No preview" nil) - (key :tag "Key") - (repeat :tag "List of keys" key))) - -(defcustom consult-preview-partial-size 1048576 - "Files larger than this byte limit are previewed partially." - :type '(natnum :tag "File size in bytes")) - -(defcustom consult-preview-partial-chunk 102400 - "Partial preview chunk size in bytes. -If a file is larger than `consult-preview-partial-size' only the -chunk from the beginning of the file is previewed." - :type '(natnum :tag "Chunk size in bytes")) - -(defcustom consult-preview-max-count 10 - "Number of file buffers to keep open temporarily during preview." - :type '(natnum :tag "Number of buffers")) - -(defcustom consult-preview-excluded-files - '("\\`/[^/|:]+:") ;; Do not preview remote files - "List of regexps matched against names of files, which are not previewed." - :type '(repeat regexp)) - -(defcustom consult-preview-allowed-hooks - '(global-font-lock-mode-check-buffers - save-place-find-file-hook) - "List of `find-file' hooks, which should be executed during file preview." - :type '(repeat symbol)) - -(defcustom consult-preview-variables - '((inhibit-message . t) - (enable-dir-local-variables . nil) - (enable-local-variables . :safe) - (non-essential . t) - (delay-mode-hooks . t)) - "Variables which are bound for file preview." - :type '(alist :key-type symbol)) - -(defcustom consult-bookmark-narrow - `((?f "File" bookmark-default-handler) - (?h "Help" help-bookmark-jump Info-bookmark-jump - Man-bookmark-jump woman-bookmark-jump) - (?p "Picture" image-bookmark-jump) - (?d "Docview" doc-view-bookmark-jump) - (?m "Mail" gnus-summary-bookmark-jump) - (?s "Eshell" eshell-bookmark-jump) - (?w "Web" eww-bookmark-jump xwidget-webkit-bookmark-jump-handler) - (?v "VC Directory" vc-dir-bookmark-jump) - (nil "Other")) - "Bookmark narrowing configuration. - -Each element of the list must have the form (char name handlers...)." - :type '(alist :key-type character :value-type (cons string (repeat function)))) - -(defcustom consult-yank-rotate - (if (boundp 'yank-from-kill-ring-rotate) - yank-from-kill-ring-rotate - t) - "Rotate the `kill-ring' in the `consult-yank' commands." - :type 'boolean) - -;;;; Faces - -(defgroup consult-faces nil - "Faces used by Consult." - :group 'consult - :group 'faces) - -(defface consult-preview-line - '((t :inherit consult-preview-insertion :extend t)) - "Face used for line previews.") - -(defface consult-highlight-match - '((t :inherit match)) - "Face used to highlight matches in the completion candidates. -Used for example by `consult-grep'.") - -(defface consult-highlight-mark - '((t :inherit consult-highlight-match)) - "Face used for mark positions in completion candidates. -Used for example by `consult-mark'. The face should be different -than the `cursor' face to avoid confusion.") - -(defface consult-preview-match - '((t :inherit isearch)) - "Face used for match previews, e.g., in `consult-line'.") - -(defface consult-preview-insertion - '((t :inherit region)) - "Face used for previews of text to be inserted. -Used by `consult-completion-in-region', `consult-yank' and `consult-history'.") - -(defface consult-narrow-indicator - '((t :inherit warning)) - "Face used for the narrowing indicator.") - -(defface consult-async-running - '((t :inherit consult-narrow-indicator)) - "Face used if asynchronous process is running.") - -(defface consult-async-finished - '((t :inherit success)) - "Face used if asynchronous process has finished.") - -(defface consult-async-failed - '((t :inherit error)) - "Face used if asynchronous process has failed.") - -(defface consult-async-split - '((t :inherit font-lock-negation-char-face)) - "Face used to highlight punctuation character.") - -(defface consult-help - '((t :inherit shadow)) - "Face used to highlight help, e.g., in `consult-register-store'.") - -(defface consult-key - '((t :inherit font-lock-keyword-face)) - "Face used to highlight keys, e.g., in `consult-register'.") - -(defface consult-line-number - '((t :inherit consult-key)) - "Face used to highlight location line in `consult-global-mark'.") - -(defface consult-file - '((t :inherit font-lock-function-name-face)) - "Face used to highlight files in `consult-buffer'.") - -(defface consult-grep-context - '((t :inherit shadow)) - "Face used to highlight grep context in `consult-grep'.") - -(defface consult-bookmark - '((t :inherit font-lock-constant-face)) - "Face used to highlight bookmarks in `consult-buffer'.") - -(defface consult-buffer - '((t)) - "Face used to highlight buffers in `consult-buffer'.") - -(defface consult-line-number-prefix - '((t :inherit line-number)) - "Face used to highlight line number prefixes.") - -(defface consult-line-number-wrapped - '((t :inherit consult-line-number-prefix :inherit font-lock-warning-face)) - "Face used to highlight line number prefixes after wrap around.") - -(defface consult-separator - '((((class color) (min-colors 88) (background light)) - :foreground "#ccc") - (((class color) (min-colors 88) (background dark)) - :foreground "#333")) - "Face used for thin line separators in `consult-register-window'.") - -;;;; Input history variables - -(defvar consult--path-history nil) -(defvar consult--grep-history nil) -(defvar consult--find-history nil) -(defvar consult--man-history nil) -(defvar consult--line-history nil) -(defvar consult--line-multi-history nil) -(defvar consult--theme-history nil) -(defvar consult--minor-mode-menu-history nil) -(defvar consult--buffer-history nil) - -;;;; Internal variables - -(defvar consult--regexp-compiler - #'consult--default-regexp-compiler - "Regular expression compiler used by `consult-grep' and other commands. -The function must return a list of regular expressions and a highlighter -function.") - -(defvar consult--customize-alist - ;; Disable preview in frames, since `consult--jump-preview' does not properly - ;; clean up. See gh:minad/consult#593. This issue should better be fixed in - ;; `consult--jump-preview'. - `((,#'consult-buffer-other-frame :preview-key nil) - (,#'consult-buffer-other-tab :preview-key nil)) - "Command configuration alist for fine-grained configuration. - -Each element of the list must have the form (command-name plist...). The -options set here will be evaluated and passed to `consult--read', when -called from the corresponding command. Note that the options depend on -the private `consult--read' API and should not be considered as stable -as the public API.") - -(defvar consult--buffer-display #'switch-to-buffer - "Buffer display function.") - -(defvar consult--completion-candidate-hook - (list #'consult--default-completion-minibuffer-candidate - #'consult--default-completion-list-candidate) - "Get candidate from completion system.") - -(defvar consult--completion-refresh-hook nil - "Refresh completion system.") - -(defvar-local consult--preview-function nil - "Minibuffer-local variable which exposes the current preview function. -This function can be called by custom completion systems from -outside the minibuffer.") - -(defvar consult--annotate-align-step 10 - "Round candidate width.") - -(defvar consult--annotate-align-width 0 - "Maximum candidate width used for annotation alignment.") - -(defconst consult--tofu-char #x200000 - "Special character used to encode line prefixes for disambiguation. -We use invalid characters outside the Unicode range.") - -(defconst consult--tofu-range #x100000 - "Special character range.") - -(defvar-local consult--narrow nil - "Current narrowing key.") - -(defvar-local consult--narrow-keys nil - "Narrowing prefixes of the current completion.") - -(defvar-local consult--narrow-predicate nil - "Narrowing predicate of the current completion.") - -(defvar-local consult--narrow-overlay nil - "Narrowing indicator overlay.") - -(defvar consult--gc-threshold (* 64 1024 1024) - "Large GC threshold for temporary increase.") - -(defvar consult--gc-percentage 0.5 - "Large GC percentage for temporary increase.") - -(defvar consult--process-chunk (* 1024 1024) - "Increase process output chunk size.") - -(defvar consult--async-log - " *consult-async*" - "Buffer for async logging output used by `consult--async-process'.") - -(defvar-local consult--focus-lines-overlays nil - "Overlays used by `consult-focus-lines'.") - -(defvar-local consult--org-fold-regions nil - "Stored regions for the org-fold API.") - -;;;; Miscellaneous helper functions - -(defun consult--key-parse (key) - "Parse KEY or signal error if invalid." - (unless (key-valid-p key) - (error "%S is not a valid key definition; see `key-valid-p'" key)) - (key-parse key)) - -(defun consult--in-buffer (fun &optional buffer) - "Ensure that FUN is executed inside BUFFER." - (unless buffer (setq buffer (current-buffer))) - (lambda (&rest args) - (with-current-buffer buffer - (apply fun args)))) - -(defun consult--completion-table-in-buffer (table &optional buffer) - "Ensure that completion TABLE is executed inside BUFFER." - (if (functionp table) - (consult--in-buffer - (lambda (str pred action) - (let ((result (funcall table str pred action))) - (pcase action - ('metadata - (setq result - (mapcar - (lambda (x) - (if (and (string-suffix-p (symbol-name (car-safe x)) "-function") (cdr x)) - (cons (car x) (consult--in-buffer (cdr x))) - x)) - result))) - ((and 'completion--unquote (guard (functionp (cadr result)))) - (cl-callf consult--in-buffer (cadr result) buffer) - (cl-callf consult--in-buffer (cadddr result) buffer))) - result)) - buffer) - table)) - -(defun consult--build-args (arg) - "Return ARG as a flat list of split strings. - -Turn ARG into a list, and for each element either: -- split it if it a string. -- eval it if it is an expression." - (seq-mapcat (lambda (x) - (if (stringp x) - (split-string-and-unquote x) - (ensure-list (eval x 'lexical)))) - (ensure-list arg))) - -(defun consult--command-split (str) - "Return command argument and options list given input STR." - (save-match-data - (let ((opts (when (string-match " +--\\( +\\|\\'\\)" str) - (prog1 (substring str (match-end 0)) - (setq str (substring str 0 (match-beginning 0))))))) - ;; split-string-and-unquote fails if the quotes are invalid. Ignore it. - (cons str (and opts (ignore-errors (split-string-and-unquote opts))))))) - -(defmacro consult--keep! (list form) - "Evaluate FORM for every element of LIST and keep the non-nil results." - (declare (indent 1)) - (cl-with-gensyms (head prev result) - `(let* ((,head (cons nil ,list)) - (,prev ,head)) - (while (cdr ,prev) - (if-let (,result (let ((it (cadr ,prev))) ,form)) - (progn - (pop ,prev) - (setcar ,prev ,result)) - (setcdr ,prev (cddr ,prev)))) - (setq ,list (cdr ,head)) - nil))) - -;; Upstream bug#46326, Consult issue gh:minad/consult#193. -(defmacro consult--minibuffer-with-setup-hook (fun &rest body) - "Variant of `minibuffer-with-setup-hook' using a symbol and `fset'. - -This macro is only needed to prevent memory leaking issues with -the upstream `minibuffer-with-setup-hook' macro. -FUN is the hook function and BODY opens the minibuffer." - (declare (indent 1) (debug t)) - (let ((hook (gensym "hook")) - (append)) - (when (eq (car-safe fun) :append) - (setq append '(t) fun (cadr fun))) - `(let ((,hook (make-symbol "consult--minibuffer-setup-hook"))) - (fset ,hook (lambda () - (remove-hook 'minibuffer-setup-hook ,hook) - (funcall ,fun))) - (unwind-protect - (progn - (add-hook 'minibuffer-setup-hook ,hook ,@append) - ,@body) - (remove-hook 'minibuffer-setup-hook ,hook))))) - -(defun consult--completion-filter (pattern cands category _highlight) - "Filter CANDS with PATTERN. - -CATEGORY is the completion category, used to find the completion style via -`completion-category-defaults' and `completion-category-overrides'. -HIGHLIGHT must be non-nil if the resulting strings should be highlighted." - ;; completion-all-completions returns an improper list - ;; where the last link is not necessarily nil. - (nconc (completion-all-completions pattern cands nil (length pattern) - `(metadata (category . ,category))) - nil)) - -(defun consult--completion-filter-complement (pattern cands category _highlight) - "Filter CANDS with complement of PATTERN. -See `consult--completion-filter' for the arguments CATEGORY and HIGHLIGHT." - (let ((ht (consult--string-hash (consult--completion-filter pattern cands category nil)))) - (seq-remove (lambda (x) (gethash x ht)) cands))) - -(defun consult--completion-filter-dispatch (pattern cands category highlight) - "Filter CANDS with PATTERN with optional complement. -Either using `consult--completion-filter' or -`consult--completion-filter-complement', depending on if the pattern starts -with a bang. See `consult--completion-filter' for the arguments CATEGORY and -HIGHLIGHT." - (cond - ((string-match-p "\\`!? ?\\'" pattern) cands) ;; empty pattern - ((string-prefix-p "! " pattern) (consult--completion-filter-complement - (substring pattern 2) cands category nil)) - (t (consult--completion-filter pattern cands category highlight)))) - -(defmacro consult--each-line (beg end &rest body) - "Iterate over each line. - -The line beginning/ending BEG/END is bound in BODY." - (declare (indent 2)) - (cl-with-gensyms (max) - `(save-excursion - (let ((,beg (point-min)) (,max (point-max)) end) - (while (< ,beg ,max) - (goto-char ,beg) - (setq ,end (pos-eol)) - ,@body - (setq ,beg (1+ ,end))))))) - -(defun consult--display-width (string) - "Compute width of STRING taking display and invisible properties into account." - (let ((pos 0) (width 0) (end (length string))) - (while (< pos end) - (let ((nextd (next-single-property-change pos 'display string end)) - (display (get-text-property pos 'display string))) - (if (stringp display) - (setq width (+ width (string-width display)) - pos nextd) - (while (< pos nextd) - (let ((nexti (next-single-property-change pos 'invisible string nextd))) - (unless (get-text-property pos 'invisible string) - (setq width (+ width (compat-call string-width string pos nexti)))) - (setq pos nexti)))))) - width)) - -(defun consult--string-hash (strings) - "Create hash table from STRINGS." - (let ((ht (make-hash-table :test #'equal :size (length strings)))) - (dolist (str strings) - (puthash str t ht)) - ht)) - -(defmacro consult--local-let (binds &rest body) - "Buffer local let BINDS of dynamic variables in BODY." - (declare (indent 1)) - (let ((buffer (gensym "buffer")) - (local (mapcar (lambda (x) (cons (gensym "local") (car x))) binds))) - `(let ((,buffer (current-buffer)) - ,@(mapcar (lambda (x) `(,(car x) (local-variable-p ',(cdr x)))) local)) - (unwind-protect - (progn - ,@(mapcar (lambda (x) `(make-local-variable ',(car x))) binds) - (let (,@binds) - ,@body)) - (when (buffer-live-p ,buffer) - (with-current-buffer ,buffer - ,@(mapcar (lambda (x) - `(unless ,(car x) - (kill-local-variable ',(cdr x)))) - local))))))) - -(defvar consult--fast-abbreviate-file-name nil) -(defun consult--fast-abbreviate-file-name (name) - "Return abbreviate file NAME. -This function is a pure variant of `abbreviate-file-name', which -does not access the file system. This is important if we require -that the operation is fast, even for remote paths or paths on -network file systems." - (save-match-data - (let (case-fold-search) ;; Assume that file system is case sensitive. - (setq name (directory-abbrev-apply name)) - (if (string-match (with-memoization consult--fast-abbreviate-file-name - (directory-abbrev-make-regexp (expand-file-name "~"))) - name) - (concat "~" (substring name (match-beginning 1))) - name)))) - -(defun consult--left-truncate-file (file) - "Return abbreviated file name of FILE for use in `completing-read' prompt." - (save-match-data - (let ((afile (abbreviate-file-name file))) - (if (string-match "/\\([^/]+\\)/\\([^/]+/?\\)\\'" afile) - (propertize (format "…/%s/%s" (match-string 1 afile) (match-string 2 afile)) - 'help-echo afile) - afile)))) - -(defun consult--directory-prompt (prompt dir) - "Return prompt, paths and default directory. - -PROMPT is the prompt prefix. The directory is appended to the -prompt prefix. For projects only the project name is shown. The -`default-directory' is not shown. Other directories are -abbreviated and only the last two path components are shown. - -If DIR is a string, it is returned as default directory. If DIR -is a list of strings, the list is returned as search paths. If -DIR is nil the `consult-project-function' is tried to retrieve -the default directory. If no project is found the -`default-directory' is returned as is. Otherwise the user is -asked for the directories or files to search via -`completing-read-multiple'." - (let* ((paths nil) - (dir - (pcase dir - ((pred stringp) dir) - ('nil (or (consult--project-root) default-directory)) - (_ - (pcase (if (stringp (car-safe dir)) - dir - ;; Preserve this-command across `completing-read-multiple' call, - ;; such that `consult-customize' continues to work. - (let ((this-command this-command) - (def (abbreviate-file-name default-directory)) - ;; TODO: `minibuffer-completing-file-name' is - ;; mostly deprecated, but still in use. Packages - ;; should instead use the completion metadata. - (minibuffer-completing-file-name t) - (ignore-case read-file-name-completion-ignore-case)) - (consult--minibuffer-with-setup-hook - (lambda () - (setq-local completion-ignore-case ignore-case) - (set-syntax-table minibuffer-local-filename-syntax)) - (completing-read-multiple "Directories or files: " - #'completion-file-name-table - nil t def 'consult--path-history def)))) - ((and `(,p) (guard (file-directory-p p))) p) - (ps (setq paths (mapcar (lambda (p) - (file-relative-name (expand-file-name p))) - ps)) - default-directory))))) - (edir (file-name-as-directory (expand-file-name dir))) - (pdir (let ((default-directory edir)) - ;; Bind default-directory in order to find the project - (consult--project-root)))) - (list - (format "%s (%s): " prompt - (pcase paths - (`(,p) (consult--left-truncate-file p)) - (`(,p . ,_) - (format "%d paths, %s, …" (length paths) (consult--left-truncate-file p))) - ((guard (equal edir pdir)) (concat "Project " (consult--project-name pdir))) - (_ (consult--left-truncate-file edir)))) - (or paths '(".")) - edir))) - -(defun consult--default-project-function (may-prompt) - "Return project root directory. -When no project is found and MAY-PROMPT is non-nil ask the user." - (when-let (proj (project-current may-prompt)) - (cond - ((fboundp 'project-root) (project-root proj)) - ((fboundp 'project-roots) (car (project-roots proj)))))) - -(defun consult--project-root (&optional may-prompt) - "Return project root as absolute path. -When no project is found and MAY-PROMPT is non-nil ask the user." - ;; Preserve this-command across project selection, - ;; such that `consult-customize' continues to work. - (let ((this-command this-command)) - (when-let (root (and consult-project-function - (funcall consult-project-function may-prompt))) - (expand-file-name root)))) - -(defun consult--project-name (dir) - "Return the project name for DIR." - (if (string-match "/\\([^/]+\\)/\\'" dir) - (propertize (match-string 1 dir) 'help-echo (abbreviate-file-name dir)) - dir)) - -(defun consult--format-file-line-match (file line match) - "Format string FILE:LINE:MATCH with faces." - (setq line (number-to-string line) - match (concat file ":" line ":" match) - file (length file)) - (put-text-property 0 file 'face 'consult-file match) - (put-text-property (1+ file) (+ 1 file (length line)) 'face 'consult-line-number match) - match) - -(defun consult--make-overlay (beg end &rest props) - "Make consult overlay between BEG and END with PROPS." - (let ((ov (make-overlay beg end))) - (while props - (overlay-put ov (car props) (cadr props)) - (setq props (cddr props))) - ov)) - -(defun consult--remove-dups (list) - "Remove duplicate strings from LIST." - (delete-dups (copy-sequence list))) - -(defsubst consult--in-range-p (pos) - "Return t if position POS lies in range `point-min' to `point-max'." - (<= (point-min) pos (point-max))) - -(defun consult--completion-window-p () - "Return non-nil if the selected window belongs to the completion UI." - (or (eq (selected-window) (active-minibuffer-window)) - (eq #'completion-list-mode (buffer-local-value 'major-mode (window-buffer))))) - -(defun consult--original-window () - "Return window which was just selected just before the minibuffer was entered. -In contrast to `minibuffer-selected-window' never return nil and -always return an appropriate non-minibuffer window." - (or (minibuffer-selected-window) - (if (window-minibuffer-p (selected-window)) - (next-window) - (selected-window)))) - -(defun consult--forbid-minibuffer () - "Raise an error if executed from the minibuffer." - (when (minibufferp) - (user-error "`%s' called inside the minibuffer" this-command))) - -(defun consult--require-minibuffer () - "Raise an error if executed outside the minibuffer." - (unless (minibufferp) - (user-error "`%s' must be called inside the minibuffer" this-command))) - -(defun consult--fontify-all () - "Ensure that the whole buffer is fontified." - ;; Font-locking is lazy, i.e., if a line has not been looked at yet, the line - ;; is not font-locked. We would observe this if consulting an unfontified - ;; line. Therefore we have to enforce font-locking now, which is slow. In - ;; order to prevent is hang-up we check the buffer size against - ;; `consult-fontify-max-size'. - (when (and consult-fontify-preserve jit-lock-mode - (< (buffer-size) consult-fontify-max-size)) - (jit-lock-fontify-now))) - -(defun consult--fontify-region (start end) - "Ensure that region between START and END is fontified." - (when (and consult-fontify-preserve jit-lock-mode) - (jit-lock-fontify-now start end))) - -(defmacro consult--with-increased-gc (&rest body) - "Temporarily increase the GC limit in BODY to optimize for throughput." - (cl-with-gensyms (overwrite) - `(let* ((,overwrite (> consult--gc-threshold gc-cons-threshold)) - (gc-cons-threshold (if ,overwrite consult--gc-threshold gc-cons-threshold)) - (gc-cons-percentage (if ,overwrite consult--gc-percentage gc-cons-percentage))) - ,@body))) - -(defmacro consult--slow-operation (message &rest body) - "Show delayed MESSAGE if BODY takes too long. -Also temporarily increase the GC limit via `consult--with-increased-gc'." - (declare (indent 1)) - `(let (set-message-function) ;; bug#63253: Broken `with-delayed-message' - (with-delayed-message (1 ,message) - (consult--with-increased-gc - ,@body)))) - -(defun consult--count-lines (pos) - "Move to position POS and return number of lines." - (let ((line 1)) - (while (< (point) pos) - (forward-line) - (when (<= (point) pos) - (cl-incf line))) - (goto-char pos) - line)) - -(defun consult--marker-from-line-column (buffer line column) - "Get marker in BUFFER from LINE and COLUMN." - (when (buffer-live-p buffer) - (with-current-buffer buffer - (save-excursion - (without-restriction - (goto-char (point-min)) - ;; Location data might be invalid by now! - (ignore-errors - (forward-line (1- line)) - (goto-char (min (+ (point) column) (pos-eol)))) - (point-marker)))))) - -(defun consult--line-prefix (&optional curr-line) - "Annotate `consult-location' candidates with line numbers. -CURR-LINE is the current line number." - (setq curr-line (or curr-line -1)) - (let* ((width (length (number-to-string (line-number-at-pos - (point-max) - consult-line-numbers-widen)))) - (before (format #("%%%dd " 0 6 (face consult-line-number-wrapped)) width)) - (after (format #("%%%dd " 0 6 (face consult-line-number-prefix)) width))) - (lambda (cand) - (let ((line (cdr (get-text-property 0 'consult-location cand)))) - (list cand (format (if (< line curr-line) before after) line) ""))))) - -(defsubst consult--location-candidate (cand marker line tofu &rest props) - "Add MARKER and LINE as `consult-location' text property to CAND. -Furthermore add the additional text properties PROPS, and append -TOFU suffix for disambiguation." - (setq cand (concat cand (consult--tofu-encode tofu))) - (add-text-properties 0 1 `(consult-location (,marker . ,line) ,@props) cand) - cand) - -;; There is a similar variable `yank-excluded-properties'. Unfortunately -;; we cannot use it here since it excludes too much (e.g., invisible) -;; and at the same time not enough (e.g., cursor-sensor-functions). -(defconst consult--remove-text-properties - '(category cursor cursor-intangible cursor-sensor-functions field follow-link - fontified front-sticky help-echo insert-behind-hooks insert-in-front-hooks - intangible keymap local-map modification-hooks mouse-face pointer read-only - rear-nonsticky yank-handler) - "List of text properties to remove from buffer strings.") - -(defsubst consult--buffer-substring (beg end &optional fontify) - "Return buffer substring between BEG and END. -If FONTIFY and `consult-fontify-preserve' are non-nil, first ensure that the -region has been fontified." - (if consult-fontify-preserve - (let (str) - (when fontify (consult--fontify-region beg end)) - (setq str (buffer-substring beg end)) - ;; TODO Propose the upstream addition of a function - ;; `preserve-list-of-text-properties', which should be as efficient as - ;; `remove-list-of-text-properties'. - (remove-list-of-text-properties - 0 (- end beg) consult--remove-text-properties str) - str) - (buffer-substring-no-properties beg end))) - -(defun consult--line-with-mark (marker) - "Current line string where the MARKER position is highlighted." - (let* ((beg (pos-bol)) - (end (pos-eol)) - (str (consult--buffer-substring beg end 'fontify))) - (if (>= marker end) - (concat str #(" " 0 1 (face consult-highlight-mark))) - (put-text-property (- marker beg) (- (1+ marker) beg) - 'face 'consult-highlight-mark str) - str))) - -;;;; Tofu cooks - -(defsubst consult--tofu-p (char) - "Return non-nil if CHAR is a tofu." - (<= consult--tofu-char char (+ consult--tofu-char consult--tofu-range -1))) - -(defun consult--tofu-hide (str) - "Hide the tofus in STR." - (let* ((max (length str)) - (end max)) - (while (and (> end 0) (consult--tofu-p (aref str (1- end)))) - (cl-decf end)) - (when (< end max) - (setq str (copy-sequence str)) - (put-text-property end max 'invisible t str)) - str)) - -(defsubst consult--tofu-append (cand id) - "Append tofu-encoded ID to CAND. -The ID must fit within a single character. It must be smaller -than `consult--tofu-range'." - (setq id (char-to-string (+ consult--tofu-char id))) - (add-text-properties 0 1 '(invisible t consult-strip t) id) - (concat cand id)) - -(defsubst consult--tofu-get (cand) - "Extract tofu-encoded ID from CAND. -See `consult--tofu-append'." - (- (aref cand (1- (length cand))) consult--tofu-char)) - -;; We must disambiguate the lines by adding a prefix such that two lines with -;; the same text can be distinguished. In order to avoid matching the line -;; number, such that the user can search for numbers with `consult-line', we -;; encode the line number as characters outside the Unicode range. By doing -;; that, no accidental matching can occur. -(defun consult--tofu-encode (n) - "Return tofu-encoded number N as a string. -Large numbers are encoded as multiple tofu characters." - (let (str tofu) - (while (progn - (setq tofu (char-to-string - (+ consult--tofu-char (% n consult--tofu-range))) - str (if str (concat tofu str) tofu)) - (and (>= n consult--tofu-range) - (setq n (/ n consult--tofu-range))))) - (add-text-properties 0 (length str) '(invisible t consult-strip t) str) - str)) - -;;;; Regexp utilities - -(defun consult--find-highlights (str start &rest ignored-faces) - "Find highlighted regions in STR from position START. -Highlighted regions have a non-nil face property. -IGNORED-FACES are ignored when searching for matches." - (let (highlights - (end (length str)) - (beg start)) - (while (< beg end) - (let ((next (next-single-property-change beg 'face str end)) - (val (get-text-property beg 'face str))) - (when (and val - (not (memq val ignored-faces)) - (not (and (consp val) - (seq-some (lambda (x) (memq x ignored-faces)) val)))) - (push (cons (- beg start) (- next start)) highlights)) - (setq beg next))) - (nreverse highlights))) - -(defun consult--point-placement (str start &rest ignored-faces) - "Compute point placement from STR with START offset. -IGNORED-FACES are ignored when searching for matches. -Return cons of point position and a list of match begin/end pairs." - (let* ((matches (apply #'consult--find-highlights str start ignored-faces)) - (pos (pcase-exhaustive consult-point-placement - ('match-beginning (or (caar matches) 0)) - ('match-end (or (cdar (last matches)) 0)) - ('line-beginning 0)))) - (dolist (match matches) - (cl-decf (car match) pos) - (cl-decf (cdr match) pos)) - (cons pos matches))) - -(defun consult--highlight-regexps (regexps ignore-case str) - "Highlight REGEXPS in STR. -If a regular expression contains capturing groups, only these are highlighted. -If no capturing groups are used highlight the whole match. Case is ignored -if IGNORE-CASE is non-nil." - (dolist (re regexps) - (let ((i 0)) - (while (and (let ((case-fold-search ignore-case)) - (string-match re str i)) - ;; Ensure that regexp search made progress (edge case for .*) - (> (match-end 0) i)) - ;; Unfortunately there is no way to avoid the allocation of the match - ;; data, since the number of capturing groups is unknown. - (let ((m (match-data))) - (setq i (cadr m) m (or (cddr m) m)) - (while m - (when (car m) - (add-face-text-property (car m) (cadr m) - 'consult-highlight-match nil str)) - (setq m (cddr m))))))) - str) - -(defconst consult--convert-regexp-table - (append - ;; For simplicity, treat word beginning/end as word boundaries, - ;; since PCRE does not make this distinction. Usually the - ;; context determines if \b is the beginning or the end. - '(("\\<" . "\\b") ("\\>" . "\\b") - ("\\_<" . "\\b") ("\\_>" . "\\b")) - ;; Treat \` and \' as beginning and end of line. This is more - ;; widely supported and makes sense for line-based commands. - '(("\\`" . "^") ("\\'" . "$")) - ;; Historical: Unescaped *, +, ? are supported at the beginning - (mapcan (lambda (x) - (mapcar (lambda (y) - (cons (concat x y) - (concat (string-remove-prefix "\\" x) "\\" y))) - '("*" "+" "?"))) - '("" "\\(" "\\(?:" "\\|" "^")) - ;; Different escaping - (mapcan (lambda (x) `(,x (,(cdr x) . ,(car x)))) - '(("\\|" . "|") - ("\\(" . "(") ("\\)" . ")") - ("\\{" . "{") ("\\}" . "}")))) - "Regexp conversion table.") - -(defun consult--convert-regexp (regexp type) - "Convert Emacs REGEXP to regexp syntax TYPE." - (if (memq type '(emacs basic)) - regexp - ;; Support for Emacs regular expressions is fairly complete for basic - ;; usage. There are a few unsupported Emacs regexp features: - ;; - \= point matching - ;; - Syntax classes \sx \Sx - ;; - Character classes \cx \Cx - ;; - Explicitly numbered groups (?3:group) - (replace-regexp-in-string - (rx (or "\\\\" "\\^" ;; Pass through - (seq (or "\\(?:" "\\|") (any "*+?")) ;; Historical: \|+ or \(?:* etc - (seq "\\(" (any "*+")) ;; Historical: \(* or \(+ - (seq (or bos "^") (any "*+?")) ;; Historical: + or * at the beginning - (seq (opt "\\") (any "(){|}")) ;; Escape parens/braces/pipe - (seq "\\" (any "'<>`")) ;; Special escapes - (seq "\\_" (any "<>")))) ;; Beginning or end of symbol - (lambda (x) (or (cdr (assoc x consult--convert-regexp-table)) x)) - regexp 'fixedcase 'literal))) - -(defun consult--default-regexp-compiler (input type ignore-case) - "Compile the INPUT string to a list of regular expressions. -The function should return a pair, the list of regular expressions and a -highlight function. The highlight function should take a single -argument, the string to highlight given the INPUT. TYPE is the desired -type of regular expression, which can be `basic', `extended', `emacs' or -`pcre'. If IGNORE-CASE is non-nil return a highlight function which -matches case insensitively." - (setq input (consult--split-escaped input)) - (cons (mapcar (lambda (x) (consult--convert-regexp x type)) input) - (when-let (regexps (seq-filter #'consult--valid-regexp-p input)) - (apply-partially #'consult--highlight-regexps regexps ignore-case)))) - -(defun consult--split-escaped (str) - "Split STR at spaces, which can be escaped with backslash." - (mapcar - (lambda (x) (string-replace "\0" " " x)) - (split-string (replace-regexp-in-string - "\\\\\\\\\\|\\\\ " - (lambda (x) (if (equal x "\\ ") "\0" x)) - str 'fixedcase 'literal) - " +" t))) - -(defun consult--join-regexps (regexps type) - "Join REGEXPS of TYPE." - ;; Add look-ahead wrapper only if there is more than one regular expression - (cond - ((and (eq type 'pcre) (cdr regexps)) - (concat "^" (mapconcat (lambda (x) (format "(?=.*%s)" x)) - regexps ""))) - ((eq type 'basic) - (string-join regexps ".*")) - (t - (when (length> regexps 3) - (message "Too many regexps, %S ignored. Use post-filtering!" - (string-join (seq-drop regexps 3) " ")) - (setq regexps (seq-take regexps 3))) - (consult--join-regexps-permutations regexps (and (eq type 'emacs) "\\"))))) - -(defun consult--join-regexps-permutations (regexps esc) - "Join all permutations of REGEXPS. -ESC is the escaping string for choice and groups." - (pcase regexps - ('nil "") - (`(,r) r) - (_ (mapconcat - (lambda (r) - (concat esc "(" r esc ").*" esc "(" - (consult--join-regexps-permutations (remove r regexps) esc) - esc ")")) - regexps (concat esc "|"))))) - -(defun consult--valid-regexp-p (re) - "Return t if regexp RE is valid." - (condition-case nil - (progn (string-match-p re "") t) - (invalid-regexp nil))) - -(defun consult--regexp-filter (regexps) - "Create filter regexp from REGEXPS." - (if (stringp regexps) - regexps - (mapconcat (lambda (x) (concat "\\(?:" x "\\)")) regexps "\\|"))) - -;;;; Lookup functions - -(defun consult--lookup-member (selected candidates &rest _) - "Lookup SELECTED in CANDIDATES list, return original element." - (car (member selected candidates))) - -(defun consult--lookup-cons (selected candidates &rest _) - "Lookup SELECTED in CANDIDATES alist, return cons." - (assoc selected candidates)) - -(defun consult--lookup-cdr (selected candidates &rest _) - "Lookup SELECTED in CANDIDATES alist, return `cdr' of element." - (cdr (assoc selected candidates))) - -(defun consult--lookup-location (selected candidates &rest _) - "Lookup SELECTED in CANDIDATES list of `consult-location' category. -Return the location marker." - (when-let (found (member selected candidates)) - (setq found (car (consult--get-location (car found)))) - ;; Check that marker is alive - (and (or (not (markerp found)) (marker-buffer found)) found))) - -(defun consult--lookup-prop (prop selected candidates &rest _) - "Lookup SELECTED in CANDIDATES list and return PROP value." - (when-let (found (member selected candidates)) - (get-text-property 0 prop (car found)))) - -(defun consult--lookup-candidate (selected candidates &rest _) - "Lookup SELECTED in CANDIDATES list and return property `consult--candidate'." - (consult--lookup-prop 'consult--candidate selected candidates)) - -;;;; Preview support - -(defun consult--filter-find-file-hook (orig &rest hooks) - "Filter `find-file-hook' by `consult-preview-allowed-hooks'. -This function is an advice for `run-hooks'. -ORIG is the original function, HOOKS the arguments." - (if (memq 'find-file-hook hooks) - (cl-letf* (((default-value 'find-file-hook) - (seq-filter (lambda (x) - (memq x consult-preview-allowed-hooks)) - (default-value 'find-file-hook))) - (find-file-hook (default-value 'find-file-hook))) - (apply orig hooks)) - (apply orig hooks))) - -(defun consult--find-file-temporarily-1 (name) - "Open file NAME, helper function for `consult--find-file-temporarily'." - (when-let (((not (seq-find (lambda (x) (string-match-p x name)) - consult-preview-excluded-files))) - ;; file-attributes may throw permission denied error - (attrs (ignore-errors (file-attributes name))) - (size (file-attribute-size attrs))) - (let* ((partial (>= size consult-preview-partial-size)) - (buffer (if partial - (generate-new-buffer (format "consult-partial-preview-%s" name)) - (find-file-noselect name 'nowarn))) - (success nil)) - (unwind-protect - (with-current-buffer buffer - (if (not partial) - (when (or (eq major-mode 'hexl-mode) - (and (eq major-mode 'fundamental-mode) - (save-excursion (search-forward "\0" nil 'noerror)))) - (error "No preview of binary file `%s'" - (file-name-nondirectory name))) - (with-silent-modifications - (setq buffer-read-only t) - (insert-file-contents name nil 0 consult-preview-partial-chunk) - (goto-char (point-max)) - (insert "\nFile truncated. End of partial preview.\n") - (goto-char (point-min))) - (when (save-excursion (search-forward "\0" nil 'noerror)) - (error "No partial preview of binary file `%s'" - (file-name-nondirectory name))) - ;; Auto detect major mode and hope for the best, given that the - ;; file is only previewed partially. If an error is thrown the - ;; buffer will be killed and preview is aborted. - (set-auto-mode) - (font-lock-mode 1)) - (when (bound-and-true-p so-long-detected-p) - (error "No preview of file `%s' with long lines" - (file-name-nondirectory name))) - (setq success (current-buffer))) - (unless success - (kill-buffer buffer)))))) - -(defun consult--find-file-temporarily (name) - "Open file NAME temporarily for preview." - (let ((vars (delq nil - (mapcar - (pcase-lambda (`(,k . ,v)) - (if (boundp k) - (list k v (default-value k) (symbol-value k)) - (message "consult-preview-variables: The variable `%s' is not bound" k) - nil)) - consult-preview-variables)))) - (condition-case err - (unwind-protect - (progn - (advice-add #'run-hooks :around #'consult--filter-find-file-hook) - (pcase-dolist (`(,k ,v . ,_) vars) - (set-default k v) - (set k v)) - (consult--find-file-temporarily-1 name)) - (advice-remove #'run-hooks #'consult--filter-find-file-hook) - (pcase-dolist (`(,k ,_ ,d ,v) vars) - (set-default k d) - (set k v))) - (error - (message "%s" (error-message-string err)) - nil)))) - -(defun consult--temporary-files () - "Return a function to open files temporarily for preview." - (let ((dir default-directory) - (hook (make-symbol "consult--temporary-files-upgrade-hook")) - (orig-buffers (buffer-list)) - temporary-buffers) - (fset hook - (lambda (_) - ;; Fully initialize previewed files and keep them alive. - (unless (consult--completion-window-p) - (let (live-files) - (pcase-dolist (`(,file . ,buf) temporary-buffers) - (when-let (wins (and (buffer-live-p buf) - (get-buffer-window-list buf))) - (push (cons file (mapcar - (lambda (win) - (cons win (window-state-get win t))) - wins)) - live-files))) - (pcase-dolist (`(,_ . ,buf) temporary-buffers) - (kill-buffer buf)) - (setq temporary-buffers nil) - (pcase-dolist (`(,file . ,wins) live-files) - (when-let (buf (find-file-noselect file)) - (push buf orig-buffers) - (pcase-dolist (`(,win . ,state) wins) - (setf (car (alist-get 'buffer state)) buf) - (window-state-put state win)))))))) - (lambda (&optional name) - (if name - (let ((default-directory dir)) - (setq name (abbreviate-file-name (expand-file-name name))) - (or - ;; Find existing fully initialized buffer (non-previewed). We have - ;; to check for fully initialized buffer before accessing the - ;; previewed buffers, since `embark-act' can open a buffer which is - ;; currently previewed, such that we end up with two buffers for - ;; the same file - one previewed and only partially initialized and - ;; one fully initialized. In this case we prefer the fully - ;; initialized buffer. For directories `get-file-buffer' returns nil, - ;; therefore we have to special case Dired. - (if (and (fboundp 'dired-find-buffer-nocreate) (file-directory-p name)) - (dired-find-buffer-nocreate name) - (get-file-buffer name)) - ;; Find existing previewed buffer. Previewed buffers are not fully - ;; initialized (hooks are delayed) in order to ensure fast preview. - (cdr (assoc name temporary-buffers)) - ;; Finally, if no existing buffer has been found, open the file for - ;; preview. - (when-let (buf (consult--find-file-temporarily name)) - ;; Only add new buffer if not already in the list - (unless (or (rassq buf temporary-buffers) (memq buf orig-buffers)) - (add-hook 'window-selection-change-functions hook) - (push (cons name buf) temporary-buffers) - ;; Disassociate buffer from file by setting `buffer-file-name' - ;; and `dired-directory' to nil and rename the buffer. This - ;; lets us open an already previewed buffer with the Embark - ;; default action C-. RET. - (with-current-buffer buf - (rename-buffer - (format " Preview:%s" - (file-name-nondirectory (directory-file-name name))) - 'unique)) - ;; The buffer disassociation is delayed to avoid breaking modes - ;; like `pdf-view-mode' or `doc-view-mode' which rely on - ;; `buffer-file-name'. Executing (set-visited-file-name nil) - ;; early also prevents the major mode initialization. - (let ((hook (make-symbol "consult--temporary-files-disassociate-hook"))) - (fset hook (lambda () - (when (buffer-live-p buf) - (with-current-buffer buf - (remove-hook 'pre-command-hook hook) - (setq-local buffer-read-only t - dired-directory nil - buffer-file-name nil))))) - (add-hook 'pre-command-hook hook)) - ;; Only keep a few buffers alive - (while (length> temporary-buffers consult-preview-max-count) - (kill-buffer (cdar (last temporary-buffers))) - (setq temporary-buffers (nbutlast temporary-buffers)))) - buf))) - (remove-hook 'window-selection-change-functions hook) - (pcase-dolist (`(,_ . ,buf) temporary-buffers) - (kill-buffer buf)) - (setq temporary-buffers nil))))) - -(defun consult--invisible-open-permanently () - "Open overlays which hide the current line. -See `isearch-open-necessary-overlays' and `isearch-open-overlay-temporary'." - (if (and (derived-mode-p 'org-mode) (fboundp 'org-fold-show-set-visibility)) - ;; New Org 9.6 fold-core API - (let ((inhibit-redisplay t)) ;; HACK: Prevent flicker due to premature redisplay - (org-fold-show-set-visibility 'canonical)) - (dolist (ov (overlays-in (pos-bol) (pos-eol))) - (when-let (fun (overlay-get ov 'isearch-open-invisible)) - (when (invisible-p (overlay-get ov 'invisible)) - (funcall fun ov)))))) - -(defun consult--invisible-open-temporarily () - "Temporarily open overlays which hide the current line. -See `isearch-open-necessary-overlays' and `isearch-open-overlay-temporary'." - (if (and (derived-mode-p 'org-mode) - (fboundp 'org-fold-show-set-visibility) - (fboundp 'org-fold-core-get-regions) - (fboundp 'org-fold-core-region)) - ;; New Org 9.6 fold-core API - ;; TODO The provided Org API `org-fold-show-set-visibility' cannot be used - ;; efficiently. We obtain all regions in the whole buffer in order to - ;; restore them. A better show API would return all the applied - ;; modifications such that we can restore the ones which got modified. - (progn - (unless consult--org-fold-regions - (setq consult--org-fold-regions - (delq nil (org-fold-core-get-regions - :with-markers t :from (point-min) :to (point-max)))) - (when consult--org-fold-regions - (let ((hook (make-symbol "consult--invisible-open-temporarily-cleanup-hook")) - (buffer (current-buffer)) - (depth (recursion-depth))) - (fset hook - (lambda () - (when (= (recursion-depth) depth) - (remove-hook 'minibuffer-exit-hook hook) - (run-at-time - 0 nil - (lambda () - (when (buffer-live-p buffer) - (with-current-buffer buffer - (pcase-dolist (`(,beg ,end ,_) consult--org-fold-regions) - (when (markerp beg) (set-marker beg nil)) - (when (markerp end) (set-marker end nil))) - (kill-local-variable 'consult--org-fold-regions)))))))) - (add-hook 'minibuffer-exit-hook hook)))) - (let ((inhibit-redisplay t)) ;; HACK: Prevent flicker due to premature redisplay - (org-fold-show-set-visibility 'canonical)) - (list (lambda () - (pcase-dolist (`(,beg ,end ,spec) consult--org-fold-regions) - (org-fold-core-region beg end t spec))))) - (let (restore) - (dolist (ov (overlays-in (pos-bol) (pos-eol))) - (let ((inv (overlay-get ov 'invisible))) - (when (and (invisible-p inv) (overlay-get ov 'isearch-open-invisible)) - (push (if-let (fun (overlay-get ov 'isearch-open-invisible-temporary)) - (progn - (funcall fun ov nil) - (lambda () (funcall fun ov t))) - (overlay-put ov 'invisible nil) - (lambda () (overlay-put ov 'invisible inv))) - restore)))) - restore))) - -(defun consult--jump-ensure-buffer (pos) - "Ensure that buffer of marker POS is displayed, return t if successful." - (or (not (markerp pos)) - ;; Switch to buffer if it is not visible - (when-let ((buf (marker-buffer pos))) - (or (and (eq (current-buffer) buf) (eq (window-buffer) buf)) - (consult--buffer-action buf 'norecord) - t)))) - -(defun consult--jump (pos) - "Jump to POS. -First push current position to mark ring, then move to new -position and run `consult-after-jump-hook'." - (when pos - ;; Extract marker from list with with overlay positions, see `consult--line-match' - (when (consp pos) (setq pos (car pos))) - ;; When the marker is in the same buffer, record previous location - ;; such that the user can jump back quickly. - (when (or (not (markerp pos)) (eq (current-buffer) (marker-buffer pos))) - ;; push-mark mutates markers in the mark-ring and the mark-marker. - ;; Therefore we transform the marker to a number to be safe. - ;; We all love side effects! - (setq pos (+ pos 0)) - (push-mark (point) t)) - (when (consult--jump-ensure-buffer pos) - (unless (= (goto-char pos) (point)) ;; Widen if jump failed - (widen) - (goto-char pos)) - (consult--invisible-open-permanently) - (run-hooks 'consult-after-jump-hook))) - nil) - -(defun consult--jump-preview () - "The preview function used if selecting from a list of candidate positions. -The function can be used as the `:state' argument of `consult--read'." - (let (restore) - (lambda (action cand) - (when (eq action 'preview) - (mapc #'funcall restore) - (setq restore nil) - ;; TODO Better buffer preview support - ;; 1. Use consult--buffer-preview instead of consult--jump-ensure-buffer - ;; 2. Remove function consult--jump-ensure-buffer - ;; 3. Remove consult-buffer-other-* from consult-customize-alist - (when-let ((pos (or (car-safe cand) cand)) ;; Candidate can be previewed - ((consult--jump-ensure-buffer pos))) - (let ((saved-min (point-min-marker)) - (saved-max (point-max-marker)) - (saved-pos (point-marker))) - (set-marker-insertion-type saved-max t) ;; Grow when text is inserted - (push (lambda () - (when-let ((buf (marker-buffer saved-pos))) - (with-current-buffer buf - (narrow-to-region saved-min saved-max) - (goto-char saved-pos) - (set-marker saved-pos nil) - (set-marker saved-min nil) - (set-marker saved-max nil)))) - restore)) - (unless (= (goto-char pos) (point)) ;; Widen if jump failed - (widen) - (goto-char pos)) - (setq restore (nconc (consult--invisible-open-temporarily) restore)) - ;; Ensure that cursor is properly previewed (gh:minad/consult#764) - (unless (eq cursor-in-non-selected-windows 'box) - (let ((orig cursor-in-non-selected-windows) - (buf (current-buffer))) - (push - (if (local-variable-p 'cursor-in-non-selected-windows) - (lambda () - (when (buffer-live-p buf) - (with-current-buffer buf - (setq-local cursor-in-non-selected-windows orig)))) - (lambda () - (when (buffer-live-p buf) - (with-current-buffer buf - (kill-local-variable 'cursor-in-non-selected-windows))))) - restore) - (setq-local cursor-in-non-selected-windows 'box))) - ;; Match previews - (let ((overlays - (list (save-excursion - (let ((vbeg (progn (beginning-of-visual-line) (point))) - (vend (progn (end-of-visual-line) (point))) - (end (pos-eol))) - (consult--make-overlay vbeg (if (= vend end) (1+ end) vend) - 'face 'consult-preview-line - 'window (selected-window) - 'priority 1)))))) - (dolist (match (cdr-safe cand)) - (push (consult--make-overlay (+ (point) (car match)) - (+ (point) (cdr match)) - 'face 'consult-preview-match - 'window (selected-window) - 'priority 2) - overlays)) - (push (lambda () (mapc #'delete-overlay overlays)) restore)) - (run-hooks 'consult-after-jump-hook)))))) - -(defun consult--jump-state () - "The state function used if selecting from a list of candidate positions." - (consult--state-with-return (consult--jump-preview) #'consult--jump)) - -(defun consult--get-location (cand) - "Return location from CAND." - (let ((loc (get-text-property 0 'consult-location cand))) - (when (consp (car loc)) - ;; Transform cheap marker to real marker - (setcar loc (set-marker (make-marker) (cdar loc) (caar loc)))) - loc)) - -(defun consult--location-state (candidates) - "Location state function. -The cheap location markers from CANDIDATES are upgraded on window -selection change to full Emacs markers." - (let ((jump (consult--jump-state)) - (hook (make-symbol "consult--location-upgrade-hook"))) - (fset hook - (lambda (_) - (unless (consult--completion-window-p) - (remove-hook 'window-selection-change-functions hook) - (mapc #'consult--get-location - (if (functionp candidates) (funcall candidates) candidates))))) - (lambda (action cand) - (pcase action - ('setup (add-hook 'window-selection-change-functions hook)) - ('exit (remove-hook 'window-selection-change-functions hook))) - (funcall jump action cand)))) - -(defun consult--state-with-return (state return) - "Compose STATE function with RETURN function." - (lambda (action cand) - (funcall state action cand) - (when (and cand (eq action 'return)) - (funcall return cand)))) - -(defmacro consult--define-state (type) - "Define state function for TYPE." - `(defun ,(intern (format "consult--%s-state" type)) () - ,(format "State function for %ss with preview. -The result can be passed as :state argument to `consult--read'." type) - (consult--state-with-return (,(intern (format "consult--%s-preview" type))) - #',(intern (format "consult--%s-action" type))))) - -(defun consult--preview-key-normalize (preview-key) - "Normalize PREVIEW-KEY, return alist of keys and debounce times." - (let ((keys) - (debounce 0)) - (setq preview-key (ensure-list preview-key)) - (while preview-key - (if (eq (car preview-key) :debounce) - (setq debounce (cadr preview-key) - preview-key (cddr preview-key)) - (let ((key (car preview-key))) - (unless (eq key 'any) - (setq key (consult--key-parse key))) - (push (cons key debounce) keys)) - (pop preview-key))) - keys)) - -(defun consult--preview-key-debounce (preview-key cand) - "Return debounce value of PREVIEW-KEY given the current candidate CAND." - (when (and (consp preview-key) (memq :keys preview-key)) - (setq preview-key (funcall (plist-get preview-key :predicate) cand))) - (let ((map (make-sparse-keymap)) - (keys (this-single-command-keys)) - any) - (pcase-dolist (`(,k . ,d) (consult--preview-key-normalize preview-key)) - (if (eq k 'any) - (setq any d) - (define-key map k `(lambda () ,d)))) - (setq keys (lookup-key map keys)) - (if (functionp keys) (funcall keys) any))) - -(defun consult--preview-append-local-pch (fun) - "Append FUN to local `post-command-hook' list." - ;; Symbol indirection because of bug#46407. - (let ((hook (make-symbol "consult--preview-post-command-hook"))) - (fset hook fun) - ;; TODO Emacs 28 has a bug, where the hook--depth-alist is not cleaned up properly - ;; Do not use the broken add-hook here. - ;;(add-hook 'post-command-hook hook 'append 'local) - (setq-local post-command-hook - (append - (remove t post-command-hook) - (list hook) - (and (memq t post-command-hook) '(t)))))) - -(defun consult--with-preview-1 (preview-key state transform candidate save-input fun) - "Add preview support for FUN. -See `consult--with-preview' for the arguments -PREVIEW-KEY, STATE, TRANSFORM, CANDIDATE and SAVE-INPUT." - (let ((mb-input "") mb-narrow selected timer previewed) - (consult--minibuffer-with-setup-hook - (if (and state preview-key) - (lambda () - (let ((hook (make-symbol "consult--preview-minibuffer-exit-hook")) - (depth (recursion-depth))) - (fset hook - (lambda () - (when (= (recursion-depth) depth) - (remove-hook 'minibuffer-exit-hook hook) - (when timer - (cancel-timer timer) - (setq timer nil)) - (with-selected-window (consult--original-window) - ;; STEP 3: Reset preview - (when previewed - (funcall state 'preview nil)) - ;; STEP 4: Notify the preview function of the minibuffer exit - (funcall state 'exit nil))))) - (add-hook 'minibuffer-exit-hook hook)) - ;; STEP 1: Setup the preview function - (with-selected-window (consult--original-window) - (funcall state 'setup nil)) - (setq consult--preview-function - (lambda () - (when-let ((cand (funcall candidate))) - ;; Drop properties to prevent bugs regarding candidate - ;; lookup, which must handle candidates without - ;; properties. Otherwise the arguments passed to the - ;; lookup function are confusing, since during preview - ;; the candidate has properties but for the final lookup - ;; after completion it does not. - (setq cand (substring-no-properties cand)) - (with-selected-window (active-minibuffer-window) - (let ((input (minibuffer-contents-no-properties)) - (narrow consult--narrow) - (win (consult--original-window))) - (with-selected-window win - (when-let ((transformed (funcall transform narrow input cand)) - (debounce (consult--preview-key-debounce preview-key transformed))) - (when timer - (cancel-timer timer) - (setq timer nil)) - ;; The transformed candidate may have text - ;; properties, which change the preview display. - ;; This matters for example for `consult-grep', - ;; where the current candidate and input may - ;; stay equal, but the highlighting of the - ;; candidate changes while the candidates list - ;; is lagging a bit behind and updates - ;; asynchronously. - ;; - ;; In older Consult versions we instead compared - ;; the input without properties, since I worried - ;; that comparing the transformed candidates - ;; could be potentially expensive. However - ;; comparing the transformed candidates is more - ;; correct. The transformed candidate is the - ;; thing which is actually previewed. - (unless (equal-including-properties previewed transformed) - (if (> debounce 0) - (setq timer - (run-at-time - debounce nil - (lambda () - ;; Preview only when a completion - ;; window is selected and when - ;; the preview window is alive. - (when (and (consult--completion-window-p) - (window-live-p win)) - (with-selected-window win - ;; STEP 2: Preview candidate - (funcall state 'preview (setq previewed transformed))))))) - ;; STEP 2: Preview candidate - (funcall state 'preview (setq previewed transformed))))))))))) - (consult--preview-append-local-pch - (lambda () - (setq mb-input (minibuffer-contents-no-properties) - mb-narrow consult--narrow) - (funcall consult--preview-function)))) - (lambda () - (consult--preview-append-local-pch - (lambda () - (setq mb-input (minibuffer-contents-no-properties) - mb-narrow consult--narrow))))) - (unwind-protect - (setq selected (when-let (result (funcall fun)) - (when-let ((save-input) - (list (symbol-value save-input)) - ((equal (car list) result))) - (set save-input (cdr list))) - (funcall transform mb-narrow mb-input result))) - (when save-input - (add-to-history save-input mb-input)) - (when state - ;; STEP 5: The preview function should perform its final action - (funcall state 'return selected)))))) - -(defmacro consult--with-preview (preview-key state transform candidate save-input &rest body) - "Add preview support to BODY. - -STATE is the state function. -TRANSFORM is the transformation function. -CANDIDATE is the function returning the current candidate. -PREVIEW-KEY are the keys which triggers the preview. -SAVE-INPUT can be a history variable symbol to save the input. - -The state function takes two arguments, an action argument and the -selected candidate. The candidate argument can be nil if no candidate is -selected or if the selection was aborted. The function is called in -sequence with the following arguments: - - 1. \\='setup nil After entering the mb (minibuffer-setup-hook). -⎧ 2. \\='preview CAND/nil Preview candidate CAND or reset if CAND is nil. -⎪ \\='preview CAND/nil -⎪ \\='preview CAND/nil -⎪ ... -⎩ 3. \\='preview nil Reset preview. - 4. \\='exit nil Before exiting the mb (minibuffer-exit-hook). - 5. \\='return CAND/nil After leaving the mb, CAND has been selected. - -The state function is always executed with the original window selected, -see `consult--original-window'. The state function is called once in -the beginning of the minibuffer setup with the `setup' argument. This is -useful in order to perform certain setup operations which require that -the minibuffer is initialized. During completion candidates are -previewed. Then the function is called with the `preview' argument and a -candidate CAND or nil if no candidate is selected. Furthermore if nil is -passed for CAND, then the preview must be undone and the original state -must be restored. The call with the `exit' argument happens once at the -end of the completion process, just before exiting the minibuffer. The -minibuffer is still alive at that point. Both `setup' and `exit' are -only useful for setup and cleanup operations. They don't receive a -candidate as argument. After leaving the minibuffer, the selected -candidate or nil is passed to the state function with the action -argument `return'. At this point the state function can perform the -actual action on the candidate. The state function with the `return' -argument is the continuation of `consult--read'. Via `unwind-protect' it -is guaranteed, that if the `setup' action of a state function is -invoked, the state function will also be called with `exit' and -`return'." - (declare (indent 5)) - `(consult--with-preview-1 ,preview-key ,state ,transform ,candidate ,save-input (lambda () ,@body))) - -;;;; Narrowing and grouping - -(defun consult--prefix-group (cand transform) - "Return title for CAND or TRANSFORM the candidate. -The candidate must have a `consult--prefix-group' property." - (if transform - (substring cand (1+ (length (get-text-property 0 'consult--prefix-group cand)))) - (get-text-property 0 'consult--prefix-group cand))) - -(defun consult--type-group (types) - "Return group function for TYPES." - (lambda (cand transform) - (if transform cand - (alist-get (get-text-property 0 'consult--type cand) types)))) - -(defun consult--type-narrow (types) - "Return narrowing configuration from TYPES." - (list :predicate - (lambda (cand) (eq (get-text-property 0 'consult--type cand) consult--narrow)) - :keys types)) - -(defun consult--widen-key () - "Return widening key, if `consult-widen-key' is not set. -The default is twice the `consult-narrow-key'." - (cond - (consult-widen-key - (consult--key-parse consult-widen-key)) - (consult-narrow-key - (let ((key (consult--key-parse consult-narrow-key))) - (vconcat key key))))) - -(defun consult-narrow (key) - "Narrow current completion with KEY. - -This command is used internally by the narrowing system of `consult--read'." - (interactive - (list (unless (equal (this-single-command-keys) (consult--widen-key)) - last-command-event))) - (consult--require-minibuffer) - (setq consult--narrow key) - (when consult--narrow-predicate - (setq minibuffer-completion-predicate (and consult--narrow consult--narrow-predicate))) - (when consult--narrow-overlay - (delete-overlay consult--narrow-overlay)) - (when consult--narrow - (setq consult--narrow-overlay - (consult--make-overlay - (1- (minibuffer-prompt-end)) (minibuffer-prompt-end) - 'before-string - (propertize (format " [%s]" (alist-get consult--narrow - consult--narrow-keys)) - 'face 'consult-narrow-indicator)))) - (run-hooks 'consult--completion-refresh-hook)) - -(defconst consult--narrow-delete - `(menu-item - "" nil :filter - ,(lambda (&optional _) - (when (equal (minibuffer-contents-no-properties) "") - (lambda () - (interactive) - (consult-narrow nil)))))) - -(defconst consult--narrow-space - `(menu-item - "" nil :filter - ,(lambda (&optional _) - (let ((str (minibuffer-contents-no-properties))) - (when-let (pair (or (and (length= str 1) - (assoc (aref str 0) consult--narrow-keys)) - (and (equal str "") - (assoc ?\s consult--narrow-keys)))) - (lambda () - (interactive) - (delete-minibuffer-contents) - (consult-narrow (car pair)))))))) - -(defun consult-narrow-help () - "Print narrowing help as a `minibuffer-message'. - -This command can be bound to a key in `consult-narrow-map', -to make it available for commands with narrowing." - (interactive) - (consult--require-minibuffer) - (let ((minibuffer-message-timeout 1000000)) - (minibuffer-message - (mapconcat (lambda (x) - (concat - (propertize (key-description (list (car x))) 'face 'consult-key) - " " - (propertize (cdr x) 'face 'consult-help))) - consult--narrow-keys - " ")))) - -(defun consult--narrow-setup (settings map) - "Setup narrowing with SETTINGS and keymap MAP." - (if (memq :keys settings) - (setq consult--narrow-predicate (plist-get settings :predicate) - consult--narrow-keys (plist-get settings :keys)) - (setq consult--narrow-predicate nil - consult--narrow-keys settings)) - (when-let ((key consult-narrow-key)) - (setq key (consult--key-parse key)) - (dolist (pair consult--narrow-keys) - (define-key map (vconcat key (vector (car pair))) - (cons (cdr pair) #'consult-narrow)))) - (when-let ((widen (consult--widen-key))) - (define-key map widen (cons "All" #'consult-narrow))) - (when-let ((init (and (memq :keys settings) (plist-get settings :initial)))) - (consult-narrow init))) - -;; Emacs 28: hide in M-X -(put #'consult-narrow-help 'completion-predicate #'ignore) -(put #'consult-narrow 'completion-predicate #'ignore) - -;;;; Splitting completion style - -(defun consult--split-perl (str &optional _plist) - "Split input STR in async input and filtering part. - -The function returns a list with three elements: The async -string, the start position of the completion filter string and a -force flag. If the first character is a punctuation character it -determines the separator. Examples: \"/async/filter\", -\"#async#filter\"." - (if (string-match-p "^[[:punct:]]" str) - (save-match-data - (let ((q (regexp-quote (substring str 0 1)))) - (string-match (concat "^" q "\\([^" q "]*\\)\\(" q "\\)?") str) - `(,(match-string 1 str) - ,(match-end 0) - ;; Force update it two punctuation characters are entered. - ,(match-end 2) - ;; List of highlights - (0 . ,(match-beginning 1)) - ,@(and (match-end 2) `((,(match-beginning 2) . ,(match-end 2))))))) - `(,str ,(length str)))) - -(defun consult--split-nil (str &optional _plist) - "Treat the complete input STR as async input." - `(,str ,(length str))) - -(defun consult--split-separator (str plist) - "Split input STR in async input and filtering part at first separator. -PLIST is the splitter configuration, including the separator." - (let ((sep (regexp-quote (char-to-string (plist-get plist :separator))))) - (save-match-data - (if (string-match (format "^\\([^%s]+\\)\\(%s\\)?" sep sep) str) - `(,(match-string 1 str) - ,(match-end 0) - ;; Force update it space is entered. - ,(match-end 2) - ;; List of highlights - ,@(and (match-end 2) `((,(match-beginning 2) . ,(match-end 2))))) - `(,str ,(length str)))))) - -(defun consult--split-setup (split) - "Setup splitting completion style with splitter function SPLIT." - (let* ((styles completion-styles) - (catdef completion-category-defaults) - (catovr completion-category-overrides) - (try (lambda (str table pred point) - (let ((completion-styles styles) - (completion-category-defaults catdef) - (completion-category-overrides catovr) - (pos (cadr (funcall split str)))) - (pcase (completion-try-completion (substring str pos) table pred - (max 0 (- point pos))) - ('t t) - (`(,newstr . ,newpt) - (cons (concat (substring str 0 pos) newstr) - (+ pos newpt))))))) - (all (lambda (str table pred point) - (let ((completion-styles styles) - (completion-category-defaults catdef) - (completion-category-overrides catovr) - (pos (cadr (funcall split str)))) - (completion-all-completions (substring str pos) table pred - (max 0 (- point pos))))))) - (setq-local completion-styles-alist (cons `(consult--split ,try ,all "") - completion-styles-alist) - completion-styles '(consult--split) - completion-category-defaults nil - completion-category-overrides nil))) - -;;;; Asynchronous filtering functions - -(defun consult--async-p (fun) - "Return t if FUN is an asynchronous completion function." - (and (functionp fun) - (condition-case nil - (progn (funcall fun "" nil 'metadata) nil) - (wrong-number-of-arguments t)))) - -(defmacro consult--with-async (bind &rest body) - "Setup asynchronous completion in BODY. - -BIND is the asynchronous function binding." - (declare (indent 1)) - (let ((async (car bind))) - `(let ((,async ,@(cdr bind)) - (new-chunk (max read-process-output-max consult--process-chunk)) - orig-chunk) - (consult--minibuffer-with-setup-hook - ;; Append such that we overwrite the completion style setting of - ;; `fido-mode'. See `consult--async-split' and - ;; `consult--split-setup'. - (:append - (lambda () - (when (consult--async-p ,async) - (setq orig-chunk read-process-output-max - read-process-output-max new-chunk) - (funcall ,async 'setup) - (let* ((mb (current-buffer)) - (fun (lambda () - (when-let (win (active-minibuffer-window)) - (when (eq (window-buffer win) mb) - (with-current-buffer mb - (let ((inhibit-modification-hooks t)) - ;; Push input string to request refresh. - (funcall ,async (minibuffer-contents-no-properties)))))))) - ;; We use a symbol in order to avoid adding lambdas to - ;; the hook variable. Symbol indirection because of - ;; bug#46407. - (hook (make-symbol "consult--async-after-change-hook"))) - ;; Delay modification hook to ensure that minibuffer is still - ;; alive after the change, such that we don't restart a new - ;; asynchronous search right before exiting the minibuffer. - (fset hook (lambda (&rest _) (run-at-time 0 nil fun))) - (add-hook 'after-change-functions hook nil 'local) - (funcall hook))))) - (let ((,async (if (consult--async-p ,async) ,async (lambda (_) ,async)))) - (unwind-protect - ,(macroexp-progn body) - (funcall ,async 'destroy) - (when (and orig-chunk (eq read-process-output-max new-chunk)) - (setq read-process-output-max orig-chunk)))))))) - -(defun consult--async-sink () - "Create ASYNC sink function. - -An async function must accept a single action argument. For the -\\='setup action it is guaranteed that the call originates from -the minibuffer. For the other actions no assumption about the -context can be made. - -\\='setup Setup the internal closure state. Return nil. -\\='destroy Destroy the internal closure state. Return nil. -\\='flush Flush the list of candidates. Return nil. -\\='refresh Request UI refresh. Return nil. -nil Return the list of candidates. -list Append the list to the already existing candidates list and return it. -string Update with the current user input string. Return nil." - (let (candidates last buffer) - (lambda (action) - (pcase-exhaustive action - ('setup - (setq buffer (current-buffer)) - nil) - ((or (pred stringp) 'destroy) nil) - ('flush (setq candidates nil last nil)) - ('refresh - ;; Refresh the UI when the current minibuffer window belongs - ;; to the current asynchronous completion session. - (when-let (win (active-minibuffer-window)) - (when (eq (window-buffer win) buffer) - (with-selected-window win - (run-hooks 'consult--completion-refresh-hook) - ;; Interaction between asynchronous completion functions and - ;; preview: We have to trigger preview immediately when - ;; candidates arrive (gh:minad/consult#436). - (when (and consult--preview-function candidates) - (funcall consult--preview-function))))) - nil) - ('nil candidates) - ((pred consp) - (setq last (last (if last (setcdr last action) (setq candidates action)))) - candidates))))) - -(defun consult--async-split-style () - "Return the async splitting style function and initial string." - (or (alist-get consult-async-split-style consult-async-split-styles-alist) - (user-error "Splitting style `%s' not found" consult-async-split-style))) - -(defun consult--async-split-initial (initial) - "Return initial string for async command. -INITIAL is the additional initial string." - (concat (plist-get (consult--async-split-style) :initial) initial)) - -(defun consult--async-split-thingatpt (thing) - "Return THING at point with async initial prefix." - (when-let (str (thing-at-point thing)) - (consult--async-split-initial str))) - -(defun consult--async-split (async &optional split) - "Create async function, which splits the input string. -ASYNC is the async sink. -SPLIT is the splitting function." - (unless split - (let* ((style (consult--async-split-style)) - (fn (plist-get style :function))) - (setq split (lambda (str) (funcall fn str style))))) - (lambda (action) - (pcase action - ('setup - (consult--split-setup split) - (funcall async 'setup)) - ((pred stringp) - (pcase-let* ((`(,async-str ,_ ,force . ,highlights) - (funcall split action)) - (async-len (length async-str)) - (input-len (length action)) - (end (minibuffer-prompt-end))) - ;; Highlight punctuation characters - (remove-list-of-text-properties end (+ end input-len) '(face)) - (dolist (hl highlights) - (put-text-property (+ end (car hl)) (+ end (cdr hl)) - 'face 'consult-async-split)) - (funcall async - ;; Pass through if the input is long enough! - (if (or force (>= async-len consult-async-min-input)) - async-str - ;; Pretend that there is no input - "")))) - (_ (funcall async action))))) - -(defun consult--async-indicator (async) - "Create async function with a state indicator overlay. -ASYNC is the async sink." - (let (ov) - (lambda (action &optional state) - (pcase action - ('indicator - (overlay-put ov 'display - (pcase-exhaustive state - ('running #("*" 0 1 (face consult-async-running))) - ('finished #(":" 0 1 (face consult-async-finished))) - ('killed #(";" 0 1 (face consult-async-failed))) - ('failed #("!" 0 1 (face consult-async-failed)))))) - ('setup - (setq ov (make-overlay (- (minibuffer-prompt-end) 2) - (- (minibuffer-prompt-end) 1))) - (funcall async 'setup)) - ('destroy - (delete-overlay ov) - (funcall async 'destroy)) - (_ (funcall async action)))))) - -(defun consult--async-log (formatted &rest args) - "Log FORMATTED ARGS to variable `consult--async-log'." - (with-current-buffer (get-buffer-create consult--async-log) - (goto-char (point-max)) - (insert (apply #'format formatted args)))) - -(defun consult--async-process (async builder &rest props) - "Create process source async function. - -ASYNC is the async function which receives the candidates. -BUILDER is the command line builder function. -PROPS are optional properties passed to `make-process'." - (setq async (consult--async-indicator async)) - (let (proc proc-buf last-args count) - (lambda (action) - (pcase action - ("" ;; If no input is provided kill current process - (when proc - (delete-process proc) - (kill-buffer proc-buf) - (setq proc nil proc-buf nil)) - (setq last-args nil)) - ((pred stringp) - (funcall async action) - (let* ((args (funcall builder action))) - (unless (stringp (car args)) - (setq args (car args))) - (unless (equal args last-args) - (setq last-args args) - (when proc - (delete-process proc) - (kill-buffer proc-buf) - (setq proc nil proc-buf nil)) - (when args - (let* ((flush t) - (rest "") - (proc-filter - (lambda (_ out) - (when flush - (setq flush nil) - (funcall async 'flush)) - (let ((lines (split-string out "[\r\n]+"))) - (if (not (cdr lines)) - (setq rest (concat rest (car lines))) - (setcar lines (concat rest (car lines))) - (let* ((len (length lines)) - (last (nthcdr (- len 2) lines))) - (setq rest (cadr last) - count (+ count len -1)) - (setcdr last nil) - (funcall async lines)))))) - (proc-sentinel - (lambda (_ event) - (when flush - (setq flush nil) - (funcall async 'flush)) - (funcall async 'indicator - (cond - ((string-prefix-p "killed" event) 'killed) - ((string-prefix-p "finished" event) 'finished) - (t 'failed))) - (when (and (string-prefix-p "finished" event) (not (equal rest ""))) - (cl-incf count) - (funcall async (list rest))) - (consult--async-log - "consult--async-process sentinel: event=%s lines=%d\n" - (string-trim event) count) - (when (> (buffer-size proc-buf) 0) - (with-current-buffer (get-buffer-create consult--async-log) - (goto-char (point-max)) - (insert ">>>>> stderr >>>>>\n") - (let ((beg (point))) - (insert-buffer-substring proc-buf) - (save-excursion - (goto-char beg) - (message #("%s" 0 2 (face error)) - (buffer-substring-no-properties (pos-bol) (pos-eol))))) - (insert "<<<<< stderr <<<<<\n"))))) - (process-adaptive-read-buffering nil)) - (funcall async 'indicator 'running) - (consult--async-log "consult--async-process started %S\n" args) - (setq count 0 - proc-buf (generate-new-buffer " *consult-async-stderr*") - proc (apply #'make-process - `(,@props - :connection-type pipe - :name ,(car args) - ;;; XXX tramp bug, the stderr buffer must be empty - :stderr ,proc-buf - :noquery t - :command ,args - :filter ,proc-filter - :sentinel ,proc-sentinel))))))) - nil) - ('destroy - (when proc - (delete-process proc) - (kill-buffer proc-buf) - (setq proc nil proc-buf nil)) - (funcall async 'destroy)) - (_ (funcall async action)))))) - -(defun consult--async-highlight (async builder) - "Return a new ASYNC function with candidate highlighting. -BUILDER is the command line builder function." - (let (highlight) - (lambda (action) - (cond - ((stringp action) - (setq highlight (cdr (funcall builder action))) - (funcall async action)) - ((and (consp action) highlight) - (dolist (str action) - (funcall highlight str)) - (funcall async action)) - (t (funcall async action)))))) - -(defun consult--async-throttle (async &optional throttle debounce) - "Create async function from ASYNC which throttles input. - -The THROTTLE delay defaults to `consult-async-input-throttle'. -The DEBOUNCE delay defaults to `consult-async-input-debounce'." - (setq throttle (or throttle consult-async-input-throttle) - debounce (or debounce consult-async-input-debounce)) - (let* ((input "") (timer (timer-create)) (last 0)) - (lambda (action) - (pcase action - ((pred stringp) - (unless (equal action input) - (cancel-timer timer) - (funcall async "") ;; cancel running process - (setq input action) - (unless (equal action "") - (timer-set-function timer (lambda () - (setq last (float-time)) - (funcall async action))) - (timer-set-time - timer - (timer-relative-time - nil (max debounce (- (+ last throttle) (float-time))))) - (timer-activate timer))) - nil) - ('destroy - (cancel-timer timer) - (funcall async 'destroy)) - (_ (funcall async action)))))) - -(defun consult--async-refresh-immediate (async) - "Create async function from ASYNC, which refreshes the display. - -The refresh happens immediately when candidates are pushed." - (lambda (action) - (pcase action - ((or (pred consp) 'flush) - (prog1 (funcall async action) - (funcall async 'refresh))) - (_ (funcall async action))))) - -(defun consult--async-refresh-timer (async &optional delay) - "Create async function from ASYNC, which refreshes the display. - -The refresh happens after a DELAY, defaulting to `consult-async-refresh-delay'." - (let ((delay (or delay consult-async-refresh-delay)) - (timer (timer-create))) - (timer-set-function timer async '(refresh)) - (lambda (action) - (prog1 (funcall async action) - (pcase action - ((or (pred consp) 'flush) - (unless (memq timer timer-list) - (timer-set-time timer (timer-relative-time nil delay)) - (timer-activate timer))) - ('destroy - (cancel-timer timer))))))) - -(defmacro consult--async-command (builder &rest args) - "Asynchronous command pipeline. -ARGS is a list of `make-process' properties and transforms. -BUILDER is the command line builder function, which takes the -input string and must either return a list of command line -arguments or a pair of the command line argument list and a -highlighting function." - (declare (indent 1)) - `(thread-first - (consult--async-sink) - (consult--async-refresh-timer) - ,@(seq-take-while (lambda (x) (not (keywordp x))) args) - (consult--async-process - ,builder - ,@(seq-drop-while (lambda (x) (not (keywordp x))) args)) - (consult--async-throttle) - (consult--async-split))) - -(defmacro consult--async-transform (async &rest transform) - "Use FUN to TRANSFORM candidates of ASYNC." - (cl-with-gensyms (async-var action-var) - `(let ((,async-var ,async)) - (lambda (,action-var) - (funcall ,async-var (if (consp ,action-var) (,@transform ,action-var) ,action-var)))))) - -(defun consult--async-map (async fun) - "Map candidates of ASYNC by FUN." - (consult--async-transform async mapcar fun)) - -(defun consult--async-filter (async fun) - "Filter candidates of ASYNC by FUN." - (consult--async-transform async seq-filter fun)) - -;;;; Dynamic collections based - -(defun consult--dynamic-compute (async fun &optional debounce) - "Dynamic computation of candidates. -ASYNC is the sink. -FUN computes the candidates given the input. -DEBOUNCE is the time after which an interrupted computation -should be restarted." - (setq debounce (or debounce consult-async-input-debounce)) - (setq async (consult--async-indicator async)) - (let* ((request) (current) (timer) - (cancel (lambda () (when timer (cancel-timer timer) (setq timer nil)))) - (start (lambda (req) (setq request req) (funcall async 'refresh)))) - (lambda (action) - (pcase action - ((and 'nil (guard (not request))) - (funcall async nil)) - ('nil - (funcall cancel) - (let ((state 'killed)) - (unwind-protect - (progn - (funcall async 'indicator 'running) - (redisplay) - ;; Run computation - (let ((response (funcall fun request))) - ;; Flush and update candidate list - (funcall async 'flush) - (setq state 'finished current request) - (funcall async response))) - (funcall async 'indicator state) - ;; If the computation was killed, restart it after some time. - (when (eq state 'killed) - (setq timer (run-at-time debounce nil start request))) - (setq request nil)))) - ((pred stringp) - (funcall cancel) - (if (or (equal action "") (equal action current)) - (funcall async 'indicator 'finished) - (funcall start action))) - ('destroy - (funcall cancel) - (funcall async 'destroy)) - (_ (funcall async action)))))) - -(defun consult--dynamic-collection (fun) - "Dynamic collection with input splitting. -FUN computes the candidates given the input." - (thread-first - (consult--async-sink) - (consult--dynamic-compute fun) - (consult--async-throttle) - (consult--async-split))) - -;;;; Special keymaps - -(defvar-keymap consult-async-map - :doc "Keymap added for commands with asynchronous candidates." - ;; Overwriting some unusable defaults of default minibuffer completion. - " " #'self-insert-command - ;; Remap Emacs 29 history and default completion for now - ;; (gh:minad/consult#613). - " " #'ignore - " " #'consult-history) - -(defvar-keymap consult-narrow-map - :doc "Narrowing keymap which is added to the local minibuffer map. -Note that `consult-narrow-key' and `consult-widen-key' are bound dynamically." - "SPC" consult--narrow-space - "DEL" consult--narrow-delete) - -;;;; Internal API: consult--read - -(defun consult--annotate-align (cand ann) - "Align annotation ANN by computing the maximum CAND width." - (setq consult--annotate-align-width - (max consult--annotate-align-width - (* (ceiling (consult--display-width cand) - consult--annotate-align-step) - consult--annotate-align-step))) - (when ann - (concat - #(" " 0 1 (display (space :align-to (+ left consult--annotate-align-width)))) - ann))) - -(defun consult--add-history (async items) - "Add ITEMS to the minibuffer future history. -ASYNC must be non-nil for async completion functions." - (delete-dups - (append - ;; the defaults are at the beginning of the future history - (ensure-list minibuffer-default) - ;; then our custom items - (remove "" (remq nil (ensure-list items))) - ;; Add all the completions for non-async commands. For async commands this - ;; feature is not useful, since if one selects a completion candidate, the - ;; async search is restarted using that candidate string. This usually does - ;; not yield a desired result since the async input uses a special format, - ;; e.g., `#grep#filter'. - (unless async - (all-completions "" - minibuffer-completion-table - minibuffer-completion-predicate))))) - -(defun consult--setup-keymap (keymap async narrow preview-key) - "Setup minibuffer keymap. - -KEYMAP is a command-specific keymap. -ASYNC must be non-nil for async completion functions. -NARROW are the narrow settings. -PREVIEW-KEY are the preview keys." - (let ((old-map (current-local-map)) - (map (make-sparse-keymap))) - - ;; Add narrow keys - (when narrow - (consult--narrow-setup narrow map)) - - ;; Preview trigger keys - (when (and (consp preview-key) (memq :keys preview-key)) - (setq preview-key (plist-get preview-key :keys))) - (setq preview-key (mapcar #'car (consult--preview-key-normalize preview-key))) - (when preview-key - (dolist (key preview-key) - (unless (or (eq key 'any) (lookup-key old-map key)) - (define-key map key #'ignore)))) - - ;; Put the keymap together - (use-local-map - (make-composed-keymap - (delq nil (list keymap - (and async consult-async-map) - (and narrow consult-narrow-map) - map)) - old-map)))) - -(defun consult--tofu-hide-in-minibuffer (&rest _) - "Hide the tofus in the minibuffer." - (let* ((min (minibuffer-prompt-end)) - (max (point-max)) - (pos max)) - (while (and (> pos min) (consult--tofu-p (char-before pos))) - (cl-decf pos)) - (when (< pos max) - (add-text-properties pos max '(invisible t rear-nonsticky t cursor-intangible t))))) - -(defun consult--read-annotate (fun cand) - "Annotate CAND with annotation function FUN." - (pcase (funcall fun cand) - (`(,_ ,_ ,suffix) suffix) - (ann ann))) - -(defun consult--read-affixate (fun cands) - "Affixate CANDS with annotation function FUN." - (mapcar (lambda (cand) - (let ((ann (funcall fun cand))) - (if (consp ann) - ann - (setq ann (or ann "")) - (list cand "" - ;; The default completion UI adds the - ;; `completions-annotations' face if no other faces are - ;; present. - (if (text-property-not-all 0 (length ann) 'face nil ann) - ann - (propertize ann 'face 'completions-annotations)))))) - cands)) - -(cl-defun consult--read-1 (table &key - prompt predicate require-match history default - keymap category initial narrow add-history annotate - state preview-key sort lookup group inherit-input-method) - "See `consult--read' for the documentation of the arguments." - (consult--minibuffer-with-setup-hook - (:append (lambda () - (add-hook 'after-change-functions #'consult--tofu-hide-in-minibuffer nil 'local) - (consult--setup-keymap keymap (consult--async-p table) narrow preview-key) - (setq-local minibuffer-default-add-function - (apply-partially #'consult--add-history (consult--async-p table) add-history)))) - (consult--with-async (async table) - (consult--with-preview - preview-key state - (lambda (narrow input cand) - (funcall lookup cand (funcall async nil) input narrow)) - (apply-partially #'run-hook-with-args-until-success - 'consult--completion-candidate-hook) - (pcase-exhaustive history - (`(:input ,var) var) - ((pred symbolp))) - ;; Do not unnecessarily let-bind the lambdas to avoid over-capturing in - ;; the interpreter. This will make closures and the lambda string - ;; representation larger, which makes debugging much worse. Fortunately - ;; the over-capturing problem does not affect the bytecode interpreter - ;; which does a proper scope analysis. - (let* ((metadata `(metadata - ,@(when category `((category . ,category))) - ,@(when group `((group-function . ,group))) - ,@(when annotate - `((affixation-function - . ,(apply-partially #'consult--read-affixate annotate)) - (annotation-function - . ,(apply-partially #'consult--read-annotate annotate)))) - ,@(unless sort '((cycle-sort-function . identity) - (display-sort-function . identity))))) - (consult--annotate-align-width 0) - (selected - (completing-read - prompt - (lambda (str pred action) - (let ((result (complete-with-action action (funcall async nil) str pred))) - (if (eq action 'metadata) - (if (and (eq (car result) 'metadata) (cdr result)) - ;; Merge metadata - `(metadata ,@(cdr metadata) ,@(cdr result)) - metadata) - result))) - predicate require-match initial - (if (symbolp history) history (cadr history)) - default - inherit-input-method))) - ;; Repair the null completion semantics. `completing-read' may return - ;; an empty string even if REQUIRE-MATCH is non-nil. One can always - ;; opt-in to null completion by passing the empty string for DEFAULT. - (when (and (eq require-match t) (not default) (equal selected "")) - (user-error "No selection")) - selected))))) - -(cl-defun consult--read (table &rest options &key - prompt predicate require-match history default - keymap category initial narrow add-history annotate - state preview-key sort lookup group inherit-input-method) - "Enhanced completing read function to select from TABLE. - -The function is a thin wrapper around `completing-read'. Keyword -arguments are used instead of positional arguments for code -clarity. On top of `completing-read' it additionally supports -computing the candidate list asynchronously, candidate preview -and narrowing. You should use `completing-read' instead of -`consult--read' if you don't use asynchronous candidate -computation or candidate preview. - -Keyword OPTIONS: - -PROMPT is the string which is shown as prompt in the minibuffer. -PREDICATE is a filter function called for each candidate, returns -nil or t. -REQUIRE-MATCH equals t means that an exact match is required. -HISTORY is the symbol of the history variable. -DEFAULT is the default selected value. -ADD-HISTORY is a list of items to add to the history. -CATEGORY is the completion category symbol. -SORT should be set to nil if the candidates are already sorted. -This will disable sorting in the completion UI. -LOOKUP is a lookup function passed the selected candidate string, -the list of candidates, the current input string and the current -narrowing value. -ANNOTATE is a function passed a candidate string. The function -should either return an annotation string or a list of three -strings (candidate prefix postfix). -INITIAL is the initial input string. -STATE is the state function, see `consult--with-preview'. -GROUP is a completion metadata `group-function' as documented in -the Elisp manual. -PREVIEW-KEY are the preview keys. Can be nil, `any', a single -key or a list of keys. -NARROW is an alist of narrowing prefix strings and description. -KEYMAP is a command-specific keymap. -INHERIT-INPUT-METHOD, if non-nil the minibuffer inherits the -input method." - ;; supported types - (cl-assert (or (functionp table) ;; dynamic table or asynchronous function - (obarrayp table) ;; obarray - (hash-table-p table) ;; hash table - (not table) ;; empty list - (stringp (car table)) ;; string list - (and (consp (car table)) (stringp (caar table))) ;; string alist - (and (consp (car table)) (symbolp (caar table))))) ;; symbol alist - (ignore prompt predicate require-match history default - keymap category initial narrow add-history annotate - state preview-key sort lookup group inherit-input-method) - (apply #'consult--read-1 table - (append - (consult--customize-get) - options - (list :prompt "Select: " - :preview-key consult-preview-key - :sort t - :lookup (lambda (selected &rest _) selected))))) - -;;;; Internal API: consult--prompt - -(cl-defun consult--prompt-1 (&key prompt history add-history initial default - keymap state preview-key transform inherit-input-method) - "See `consult--prompt' for documentation." - (consult--minibuffer-with-setup-hook - (:append (lambda () - (consult--setup-keymap keymap nil nil preview-key) - (setq-local minibuffer-default-add-function - (apply-partially #'consult--add-history nil add-history)))) - (consult--with-preview - preview-key state - (lambda (_narrow inp _cand) (funcall transform inp)) - (lambda () "") - history - (read-from-minibuffer prompt initial nil nil history default inherit-input-method)))) - -(cl-defun consult--prompt (&rest options &key prompt history add-history initial default - keymap state preview-key transform inherit-input-method) - "Read from minibuffer. - -Keyword OPTIONS: - -PROMPT is the string to prompt with. -TRANSFORM is a function which is applied to the current input string. -HISTORY is the symbol of the history variable. -INITIAL is initial input. -DEFAULT is the default selected value. -ADD-HISTORY is a list of items to add to the history. -STATE is the state function, see `consult--with-preview'. -PREVIEW-KEY are the preview keys (nil, `any', a single key or a list of keys). -KEYMAP is a command-specific keymap." - (ignore prompt history add-history initial default - keymap state preview-key transform inherit-input-method) - (apply #'consult--prompt-1 - (append - (consult--customize-get) - options - (list :prompt "Input: " - :preview-key consult-preview-key - :transform #'identity)))) - -;;;; Internal API: consult--multi - -(defsubst consult--multi-source (sources cand) - "Lookup source for CAND in SOURCES list." - (aref sources (consult--tofu-get cand))) - -(defun consult--multi-predicate (sources cand) - "Predicate function called for each candidate CAND given SOURCES." - (let* ((src (consult--multi-source sources cand)) - (narrow (plist-get src :narrow)) - (type (or (car-safe narrow) narrow -1))) - (or (eq consult--narrow type) - (not (or consult--narrow (plist-get src :hidden)))))) - -(defun consult--multi-narrow (sources) - "Return narrow list from SOURCES." - (thread-last sources - (mapcar (lambda (src) - (when-let (narrow (plist-get src :narrow)) - (if (consp narrow) - narrow - (when-let (name (plist-get src :name)) - (cons narrow name)))))) - (delq nil) - (delete-dups))) - -(defun consult--multi-annotate (sources cand) - "Annotate candidate CAND from multi SOURCES." - (consult--annotate-align - cand - (let ((src (consult--multi-source sources cand))) - (if-let ((fun (plist-get src :annotate))) - (funcall fun (cdr (get-text-property 0 'multi-category cand))) - (plist-get src :name))))) - -(defun consult--multi-group (sources cand transform) - "Return title of candidate CAND or TRANSFORM the candidate given SOURCES." - (if transform cand - (plist-get (consult--multi-source sources cand) :name))) - -(defun consult--multi-preview-key (sources) - "Return preview keys from SOURCES." - (list :predicate - (lambda (cand) - (if (plist-member (cdr cand) :preview-key) - (plist-get (cdr cand) :preview-key) - consult-preview-key)) - :keys - (delete-dups - (seq-mapcat (lambda (src) - (let ((key (if (plist-member src :preview-key) - (plist-get src :preview-key) - consult-preview-key))) - (ensure-list key))) - sources)))) - -(defun consult--multi-lookup (sources selected candidates _input narrow &rest _) - "Lookup SELECTED in CANDIDATES given SOURCES, with potential NARROW." - (if (or (string-blank-p selected) - (not (consult--tofu-p (aref selected (1- (length selected)))))) - ;; Non-existing candidate without Tofu or default submitted (empty string) - (let* ((src (cond - (narrow (seq-find (lambda (src) - (let ((n (plist-get src :narrow))) - (eq (or (car-safe n) n -1) narrow))) - sources)) - ((seq-find (lambda (src) (plist-get src :default)) sources)) - ((seq-find (lambda (src) (not (plist-get src :hidden))) sources)) - ((aref sources 0)))) - (idx (seq-position sources src)) - (def (and (string-blank-p selected) ;; default candidate - (seq-find (lambda (cand) (eq idx (consult--tofu-get cand))) candidates)))) - (if def - (cons (cdr (get-text-property 0 'multi-category def)) src) - `(,selected :match nil ,@src))) - (if-let (found (member selected candidates)) - ;; Existing candidate submitted - (cons (cdr (get-text-property 0 'multi-category (car found))) - (consult--multi-source sources selected)) - ;; Non-existing Tofu'ed candidate submitted, e.g., via Embark - `(,(substring selected 0 -1) :match nil ,@(consult--multi-source sources selected))))) - -(defun consult--multi-candidates (sources) - "Return `consult--multi' candidates from SOURCES." - (let ((idx 0) candidates) - (seq-doseq (src sources) - (let* ((face (and (plist-member src :face) `(face ,(plist-get src :face)))) - (cat (plist-get src :category)) - (items (plist-get src :items)) - (items (if (functionp items) (funcall items) items))) - (dolist (item items) - (let ((cand (consult--tofu-append item idx))) - ;; Preserve existing `multi-category' datum of the candidate. - (if (get-text-property 0 'multi-category cand) - (when face (add-text-properties 0 (length item) face cand)) - ;; Attach `multi-category' datum and face. - (add-text-properties 0 (length item) - `(multi-category (,cat . ,item) ,@face) cand)) - (push cand candidates)))) - (cl-incf idx)) - (nreverse candidates))) - -(defun consult--multi-enabled-sources (sources) - "Return vector of enabled SOURCES." - (vconcat - (seq-filter (lambda (src) - (if-let (pred (plist-get src :enabled)) - (funcall pred) - t)) - (mapcar (lambda (src) - (if (symbolp src) (symbol-value src) src)) - sources)))) - -(defun consult--multi-state (sources) - "State function given SOURCES." - (when-let (states (delq nil (mapcar (lambda (src) - (when-let (fun (plist-get src :state)) - (cons src (funcall fun)))) - sources))) - (let (last-fun) - (pcase-lambda (action `(,cand . ,src)) - (pcase action - ('setup - (pcase-dolist (`(,_ . ,fun) states) - (funcall fun 'setup nil))) - ('exit - (pcase-dolist (`(,_ . ,fun) states) - (funcall fun 'exit nil))) - ('preview - (let ((selected-fun (cdr (assq src states)))) - ;; If the candidate source changed during preview communicate to - ;; the last source, that none of its candidates is previewed anymore. - (when (and last-fun (not (eq last-fun selected-fun))) - (funcall last-fun 'preview nil)) - (setq last-fun selected-fun) - (when selected-fun - (funcall selected-fun 'preview cand)))) - ('return - (let ((selected-fun (cdr (assq src states)))) - ;; Finish all the sources, except the selected one. - (pcase-dolist (`(,_ . ,fun) states) - (unless (eq fun selected-fun) - (funcall fun 'return nil))) - ;; Finish the source with the selected candidate - (when selected-fun - (funcall selected-fun 'return cand))))))))) - -(defun consult--multi (sources &rest options) - "Select from candidates taken from a list of SOURCES. - -OPTIONS is the plist of options passed to `consult--read'. The following -options are supported: :require-match, :history, :keymap, :initial, -:add-history, :sort and :inherit-input-method. The other options of -`consult--read' are used by the implementation of `consult--multi' and -should not be overwritten, except in in special scenarios. - -The function returns the selected candidate in the form (cons candidate -source-plist). The plist has the key :match with a value nil if the -candidate does not exist, t if the candidate exists and `new' if the -candidate has been created. The sources of the source list can either be -symbols of source variables or source values. Source values must be -plists with fields from the following list. - -Required source fields: -* :category - Completion category symbol. -* :items - List of strings to select from or function returning - list of strings. Note that the strings can use text properties - to carry metadata, which is then available to the :annotate, - :action and :state functions. - -Optional source fields: -* :name - Name of the source as a string, used for narrowing, - group titles and annotations. -* :narrow - Narrowing character or (character . string) pair. -* :enabled - Function which must return t if the source is enabled. -* :hidden - When t candidates of this source are hidden by default. -* :face - Face used for highlighting the candidates. -* :annotate - Annotation function called for each candidate, returns string. -* :history - Name of history variable to add selected candidate. -* :default - Must be t if the first item of the source is the default value. -* :action - Function called with the selected candidate. -* :new - Function called with new candidate name, only if :require-match is nil. -* :state - State constructor for the source, must return the - state function. The state function is informed about state - changes of the UI and can be used to implement preview. -* Other custom source fields can be added depending on the use - case. Note that the source is returned by `consult--multi' - together with the selected candidate." - (let* ((sources (consult--multi-enabled-sources sources)) - (candidates (consult--with-increased-gc - (consult--multi-candidates sources))) - (selected - (apply #'consult--read - candidates - (append - options - (list - :category 'multi-category - :predicate (apply-partially #'consult--multi-predicate sources) - :annotate (apply-partially #'consult--multi-annotate sources) - :group (apply-partially #'consult--multi-group sources) - :lookup (apply-partially #'consult--multi-lookup sources) - :preview-key (consult--multi-preview-key sources) - :narrow (consult--multi-narrow sources) - :state (consult--multi-state sources)))))) - (when-let (history (plist-get (cdr selected) :history)) - (add-to-history history (car selected))) - (if (plist-member (cdr selected) :match) - (when-let (fun (plist-get (cdr selected) :new)) - (funcall fun (car selected)) - (plist-put (cdr selected) :match 'new)) - (when-let (fun (plist-get (cdr selected) :action)) - (funcall fun (car selected))) - (setq selected `(,(car selected) :match t ,@(cdr selected)))) - selected)) - -;;;; Customization macro - -(defun consult--customize-put (cmds prop form) - "Set property PROP to FORM of commands CMDS." - (dolist (cmd cmds) - (cond - ((and (boundp cmd) (consp (symbol-value cmd))) - (setf (plist-get (symbol-value cmd) prop) (eval form 'lexical))) - ((functionp cmd) - (setf (plist-get (alist-get cmd consult--customize-alist) prop) form)) - (t (user-error "%s is neither a Command command nor a source" cmd)))) - nil) - -(defmacro consult-customize (&rest args) - "Set properties of commands or sources. -ARGS is a list of commands or sources followed by the list of -keyword-value pairs. For `consult-customize' to succeed, the -customized sources and commands must exist. When a command is -invoked, the value of `this-command' is used to lookup the -corresponding customization options." - (let (setter) - (while args - (let ((cmds (seq-take-while (lambda (x) (not (keywordp x))) args))) - (setq args (seq-drop-while (lambda (x) (not (keywordp x))) args)) - (while (keywordp (car args)) - (push `(consult--customize-put ',cmds ,(car args) ',(cadr args)) setter) - (setq args (cddr args))))) - (macroexp-progn setter))) - -(defun consult--customize-get (&optional cmd) - "Get configuration from `consult--customize-alist' for CMD." - (mapcar (lambda (x) (eval x 'lexical)) - (alist-get (or cmd this-command) consult--customize-alist))) - -;;;; Commands - -;;;;; Command: consult-completion-in-region - -(defun consult--insertion-preview (start end) - "State function for previewing a candidate in a specific region. -The candidates are previewed in the region from START to END. This function is -used as the `:state' argument for `consult--read' in the `consult-yank' family -of functions and in `consult-completion-in-region'." - (unless (or (minibufferp) - ;; Disable preview if anything odd is going on with the markers. - ;; Otherwise we get "Marker points into wrong buffer errors". See - ;; gh:minad/consult#375, where Org mode source blocks are - ;; completed in a different buffer than the original buffer. This - ;; completion is probably also problematic in my Corfu completion - ;; package. - (not (eq (window-buffer) (current-buffer))) - (and (markerp start) (not (eq (marker-buffer start) (current-buffer)))) - (and (markerp end) (not (eq (marker-buffer end) (current-buffer))))) - (let (ov) - (lambda (action cand) - (cond - ((and (not cand) ov) - (delete-overlay ov) - (setq ov nil)) - ((and (eq action 'preview) cand) - (unless ov - (setq ov (consult--make-overlay start end - 'invisible t - 'window (selected-window)))) - ;; Use `add-face-text-property' on a copy of "cand in order to merge face properties - (setq cand (copy-sequence cand)) - (add-face-text-property 0 (length cand) 'consult-preview-insertion t cand) - ;; Use the `before-string' property since the overlay might be empty. - (overlay-put ov 'before-string cand))))))) - -;;;###autoload -(defun consult-completion-in-region (start end collection &optional predicate) - "Use minibuffer completion as the UI for `completion-at-point'. - -The function is called with 4 arguments: START END COLLECTION -PREDICATE. The arguments and expected return value are as -specified for `completion-in-region'. Use this function as a -value for `completion-in-region-function'." - (barf-if-buffer-read-only) - (let* ((initial (buffer-substring-no-properties start end)) - (metadata (completion-metadata initial collection predicate)) - ;; TODO: `minibuffer-completing-file-name' is mostly deprecated, but - ;; still in use. Packages should instead use the completion metadata. - (minibuffer-completing-file-name - (eq 'file (completion-metadata-get metadata 'category))) - (threshold (completion--cycle-threshold metadata)) - (all (completion-all-completions initial collection predicate (length initial))) - ;; Wrap all annotation functions to ensure that they are executed - ;; in the original buffer. - (exit-fun (plist-get completion-extra-properties :exit-function)) - (ann-fun (plist-get completion-extra-properties :annotation-function)) - (aff-fun (plist-get completion-extra-properties :affixation-function)) - (docsig-fun (plist-get completion-extra-properties :company-docsig)) - (completion-extra-properties - `(,@(and ann-fun (list :annotation-function (consult--in-buffer ann-fun))) - ,@(and aff-fun (list :affixation-function (consult--in-buffer aff-fun))) - ;; Provide `:annotation-function' if `:company-docsig' is specified. - ,@(and docsig-fun (not ann-fun) (not aff-fun) - (list :annotation-function - (consult--in-buffer - (lambda (cand) - (concat (propertize " " 'display '(space :align-to center)) - (funcall docsig-fun cand))))))))) - ;; error if `threshold' is t or the improper list `all' is too short - (if (and threshold - (or (not (consp (ignore-errors (nthcdr threshold all)))) - (and completion-cycling completion-all-sorted-completions))) - (completion--in-region start end collection predicate) - (let* ((limit (car (completion-boundaries initial collection predicate ""))) - (this-command #'consult-completion-in-region) - (completion - (cond - ((atom all) nil) - ((and (consp all) (atom (cdr all))) - (concat (substring initial 0 limit) (car all))) - (t - (consult--local-let ((enable-recursive-minibuffers t)) - ;; Evaluate completion table in the original buffer. - ;; This is a reasonable thing to do and required by - ;; some completion tables in particular by lsp-mode. - ;; See gh:minad/vertico#61. - (consult--read (consult--completion-table-in-buffer collection) - :prompt "Completion: " - :state (consult--insertion-preview start end) - :predicate predicate - :initial initial)))))) - (if completion - (progn - ;; bug#55205: completion--replace removes properties! - (completion--replace start end (setq completion (concat completion))) - (when exit-fun - (funcall exit-fun completion - ;; If completion is finished and cannot be further - ;; completed, return `finished'. Otherwise return - ;; `exact'. - (if (eq (try-completion completion collection predicate) t) - 'finished 'exact))) - t) - (message "No completion") - nil))))) - -;;;;; Command: consult-outline - -(defun consult--outline-candidates () - "Return alist of outline headings and positions." - (consult--forbid-minibuffer) - (let* ((line (line-number-at-pos (point-min) consult-line-numbers-widen)) - (heading-regexp (concat "^\\(?:" - ;; default definition from outline.el - (or (bound-and-true-p outline-regexp) "[*\^L]+") - "\\)")) - (heading-alist (bound-and-true-p outline-heading-alist)) - (level-fun (or (bound-and-true-p outline-level) - (lambda () ;; as in the default from outline.el - (or (cdr (assoc (match-string 0) heading-alist)) - (- (match-end 0) (match-beginning 0)))))) - (buffer (current-buffer)) - candidates) - (save-excursion - (goto-char (point-min)) - (while (save-excursion - (if-let (fun (bound-and-true-p outline-search-function)) - (funcall fun) - (re-search-forward heading-regexp nil t))) - (cl-incf line (consult--count-lines (match-beginning 0))) - (push (consult--location-candidate - (consult--buffer-substring (pos-bol) (pos-eol) 'fontify) - (cons buffer (point)) (1- line) (1- line) - 'consult--outline-level (funcall level-fun)) - candidates) - (goto-char (1+ (pos-eol))))) - (unless candidates - (user-error "No headings")) - (nreverse candidates))) - -;;;###autoload -(defun consult-outline (&optional level) - "Jump to an outline heading, obtained by matching against `outline-regexp'. - -This command supports narrowing to a heading level and candidate -preview. The initial narrowing LEVEL can be given as prefix -argument. The symbol at point is added to the future history." - (interactive - (list (and current-prefix-arg (prefix-numeric-value current-prefix-arg)))) - (let* ((candidates (consult--slow-operation - "Collecting headings..." - (consult--outline-candidates))) - (min-level (- (cl-loop for cand in candidates minimize - (get-text-property 0 'consult--outline-level cand)) - ?1)) - (narrow-pred (lambda (cand) - (<= (get-text-property 0 'consult--outline-level cand) - (+ consult--narrow min-level)))) - (narrow-keys (mapcar (lambda (c) (cons c (format "Level %c" c))) - (number-sequence ?1 ?9))) - (narrow-init (and level (max ?1 (min ?9 (+ level ?0)))))) - (consult--read - candidates - :prompt "Go to heading: " - :annotate (consult--line-prefix) - :category 'consult-location - :sort nil - :require-match t - :lookup #'consult--line-match - :narrow `(:predicate ,narrow-pred :keys ,narrow-keys :initial ,narrow-init) - :history '(:input consult--line-history) - :add-history (thing-at-point 'symbol) - :state (consult--location-state candidates)))) - -;;;;; Command: consult-mark - -(defun consult--mark-candidates (markers) - "Return list of candidates strings for MARKERS." - (consult--forbid-minibuffer) - (let ((candidates) - (current-buf (current-buffer))) - (save-excursion - (dolist (marker markers) - (when-let ((pos (marker-position marker)) - (buf (marker-buffer marker))) - (when (and (eq buf current-buf) - (consult--in-range-p pos)) - (goto-char pos) - ;; `line-number-at-pos' is a very slow function, which should be - ;; replaced everywhere. However in this case the slow - ;; line-number-at-pos does not hurt much, since the mark ring is - ;; usually small since it is limited by `mark-ring-max'. - (push (consult--location-candidate - (consult--line-with-mark marker) marker - (line-number-at-pos pos consult-line-numbers-widen) - marker) - candidates))))) - (unless candidates - (user-error "No marks")) - (nreverse (delete-dups candidates)))) - -;;;###autoload -(defun consult-mark (&optional markers) - "Jump to a marker in MARKERS list (defaults to buffer-local `mark-ring'). - -The command supports preview of the currently selected marker position. -The symbol at point is added to the future history." - (interactive) - (consult--read - (consult--mark-candidates - (or markers (cons (mark-marker) mark-ring))) - :prompt "Go to mark: " - :annotate (consult--line-prefix) - :category 'consult-location - :sort nil - :require-match t - :lookup #'consult--lookup-location - :history '(:input consult--line-history) - :add-history (thing-at-point 'symbol) - :state (consult--jump-state))) - -;;;;; Command: consult-global-mark - -(defun consult--global-mark-candidates (markers) - "Return list of candidates strings for MARKERS." - (consult--forbid-minibuffer) - (let ((candidates)) - (save-excursion - (dolist (marker markers) - (when-let ((pos (marker-position marker)) - (buf (marker-buffer marker))) - (unless (minibufferp buf) - (with-current-buffer buf - (when (consult--in-range-p pos) - (goto-char pos) - ;; `line-number-at-pos' is slow, see comment in `consult--mark-candidates'. - (let* ((line (line-number-at-pos pos consult-line-numbers-widen)) - (prefix (consult--format-file-line-match (buffer-name buf) line "")) - (cand (concat prefix (consult--line-with-mark marker) (consult--tofu-encode marker)))) - (put-text-property 0 (length prefix) 'consult-strip t cand) - (put-text-property 0 (length cand) 'consult-location (cons marker line) cand) - (push cand candidates)))))))) - (unless candidates - (user-error "No global marks")) - (nreverse (delete-dups candidates)))) - -;;;###autoload -(defun consult-global-mark (&optional markers) - "Jump to a marker in MARKERS list (defaults to `global-mark-ring'). - -The command supports preview of the currently selected marker position. -The symbol at point is added to the future history." - (interactive) - (consult--read - (consult--global-mark-candidates - (or markers global-mark-ring)) - :prompt "Go to global mark: " - ;; Despite `consult-global-mark' formatting the candidates in grep-like - ;; style, we are not using the `consult-grep' category, since the candidates - ;; have location markers attached. - :category 'consult-location - :sort nil - :require-match t - :lookup #'consult--lookup-location - :history '(:input consult--line-history) - :add-history (thing-at-point 'symbol) - :state (consult--jump-state))) - -;;;;; Command: consult-line - -(defun consult--line-candidates (top curr-line) - "Return list of line candidates. -Start from top if TOP non-nil. -CURR-LINE is the current line number." - (consult--forbid-minibuffer) - (consult--fontify-all) - (let* ((buffer (current-buffer)) - (line (line-number-at-pos (point-min) consult-line-numbers-widen)) - default-cand candidates) - (consult--each-line beg end - (unless (looking-at-p "^\\s-*$") - (push (consult--location-candidate - (consult--buffer-substring beg end) - (cons buffer beg) line line) - candidates) - (when (and (not default-cand) (>= line curr-line)) - (setq default-cand candidates))) - (cl-incf line)) - (unless candidates - (user-error "No lines")) - (nreverse - (if (or top (not default-cand)) - candidates - (let ((before (cdr default-cand))) - (setcdr default-cand nil) - (nconc before candidates)))))) - -(defun consult--line-point-placement (selected candidates highlighted &rest ignored-faces) - "Find point position on matching line. -SELECTED is the currently selected candidate. -CANDIDATES is the list of candidates. -HIGHLIGHTED is the highlighted string to determine the match position. -IGNORED-FACES are ignored when determining the match position." - (when-let (pos (consult--lookup-location selected candidates)) - (if highlighted - (let* ((matches (apply #'consult--point-placement highlighted 0 ignored-faces)) - (dest (+ pos (car matches)))) - ;; Only create a new marker when jumping across buffers (for example - ;; `consult-line-multi'). Avoid creating unnecessary markers, when - ;; scrolling through candidates, since creating markers is not free. - (when (and (markerp pos) (not (eq (marker-buffer pos) (current-buffer)))) - (setq dest (move-marker (make-marker) dest (marker-buffer pos)))) - (cons dest (cdr matches))) - pos))) - -(defun consult--line-match (selected candidates input &rest _) - "Lookup position of match. -SELECTED is the currently selected candidate. -CANDIDATES is the list of candidates. -INPUT is the input string entered by the user." - (consult--line-point-placement selected candidates - (and (not (string-blank-p input)) - (car (consult--completion-filter - input - (list (substring-no-properties selected)) - 'consult-location 'highlight))) - 'completions-first-difference)) - -;;;###autoload -(defun consult-line (&optional initial start) - "Search for a matching line. - -Depending on the setting `consult-point-placement' the command -jumps to the beginning or the end of the first match on the line -or the line beginning. The default candidate is the non-empty -line next to point. This command obeys narrowing. Optional -INITIAL input can be provided. The search starting point is -changed if the START prefix argument is set. The symbol at point -and the last `isearch-string' is added to the future history." - (interactive (list nil (not (not current-prefix-arg)))) - (let* ((curr-line (line-number-at-pos (point) consult-line-numbers-widen)) - (top (not (eq start consult-line-start-from-top))) - (candidates (consult--slow-operation "Collecting lines..." - (consult--line-candidates top curr-line)))) - (consult--read - candidates - :prompt (if top "Go to line from top: " "Go to line: ") - :annotate (consult--line-prefix curr-line) - :category 'consult-location - :sort nil - :require-match t - ;; Always add last `isearch-string' to future history - :add-history (list (thing-at-point 'symbol) isearch-string) - :history '(:input consult--line-history) - :lookup #'consult--line-match - :default (car candidates) - ;; Add `isearch-string' as initial input if starting from Isearch - :initial (or initial - (and isearch-mode - (prog1 isearch-string (isearch-done)))) - :state (consult--location-state candidates)))) - -;;;;; Command: consult-line-multi - -(defun consult--line-multi-match (selected candidates &rest _) - "Lookup position of match. -SELECTED is the currently selected candidate. -CANDIDATES is the list of candidates." - (consult--line-point-placement selected candidates - (car (member selected candidates)))) - -(defun consult--line-multi-group (cand transform) - "Group function used by `consult-line-multi'. -If TRANSFORM non-nil, return transformed CAND, otherwise return title." - (if transform cand - (let* ((marker (car (get-text-property 0 'consult-location cand))) - (buf (if (consp marker) - (car marker) ;; Handle cheap marker - (marker-buffer marker)))) - (if buf (buffer-name buf) "Dead buffer")))) - -(defun consult--line-multi-candidates (buffers input) - "Collect matching candidates from multiple buffers. -INPUT is the user input which should be matched. -BUFFERS is the list of buffers." - (pcase-let ((`(,regexps . ,hl) - (funcall consult--regexp-compiler - input 'emacs completion-ignore-case)) - (candidates nil) - (cand-idx 0)) - (save-match-data - (dolist (buf buffers (nreverse candidates)) - (with-current-buffer buf - (save-excursion - (let ((line (line-number-at-pos (point-min) consult-line-numbers-widen))) - (goto-char (point-min)) - (while (and (not (eobp)) - (save-excursion (re-search-forward (car regexps) nil t))) - (cl-incf line (consult--count-lines (match-beginning 0))) - (let ((bol (pos-bol)) - (eol (pos-eol))) - (goto-char bol) - (when (and (not (looking-at-p "^\\s-*$")) - (seq-every-p (lambda (r) - (goto-char bol) - (re-search-forward r eol t)) - (cdr regexps))) - (push (consult--location-candidate - (funcall hl (buffer-substring-no-properties bol eol)) - (cons buf bol) (1- line) cand-idx) - candidates) - (cl-incf cand-idx)) - (goto-char (1+ eol))))))))))) - -;;;###autoload -(defun consult-line-multi (query &optional initial) - "Search for a matching line in multiple buffers. - -By default search across all project buffers. If the prefix -argument QUERY is non-nil, all buffers are searched. Optional -INITIAL input can be provided. The symbol at point and the last -`isearch-string' is added to the future history. In order to -search a subset of buffers, QUERY can be set to a plist according -to `consult--buffer-query'." - (interactive "P") - (unless (keywordp (car-safe query)) - (setq query (list :sort 'alpha-current :directory (and (not query) 'project)))) - (pcase-let* ((`(,prompt . ,buffers) (consult--buffer-query-prompt "Go to line" query)) - (collection (consult--dynamic-collection - (apply-partially #'consult--line-multi-candidates - buffers)))) - (consult--read - collection - :prompt prompt - :annotate (consult--line-prefix) - :category 'consult-location - :sort nil - :require-match t - ;; Always add last Isearch string to future history - :add-history (mapcar #'consult--async-split-initial - (delq nil (list (thing-at-point 'symbol) - isearch-string))) - :history '(:input consult--line-multi-history) - :lookup #'consult--line-multi-match - ;; Add `isearch-string' as initial input if starting from Isearch - :initial (consult--async-split-initial - (or initial - (and isearch-mode - (prog1 isearch-string (isearch-done))))) - :state (consult--location-state (lambda () (funcall collection nil))) - :group #'consult--line-multi-group))) - -;;;;; Command: consult-keep-lines - -(defun consult--keep-lines-state (filter) - "State function for `consult-keep-lines' with FILTER function." - (let ((font-lock-orig font-lock-mode) - (whitespace-orig (bound-and-true-p whitespace-mode)) - (hl-line-orig (bound-and-true-p hl-line-mode)) - (point-orig (point)) - lines content-orig replace last-input) - (if (use-region-p) - (save-restriction - ;; Use the same behavior as `keep-lines'. - (let ((rbeg (region-beginning)) - (rend (save-excursion - (goto-char (region-end)) - (unless (or (bolp) (eobp)) - (forward-line 0)) - (point)))) - (consult--fontify-region rbeg rend) - (narrow-to-region rbeg rend) - (consult--each-line beg end - (push (consult--buffer-substring beg end) lines)) - (setq content-orig (buffer-string) - replace (lambda (content &optional pos) - (delete-region rbeg rend) - (insert-before-markers content) - (goto-char (or pos rbeg)) - (setq rend (+ rbeg (length content))) - (add-face-text-property rbeg rend 'region t))))) - (consult--fontify-all) - (setq content-orig (buffer-string) - replace (lambda (content &optional pos) - (delete-region (point-min) (point-max)) - (insert content) - (goto-char (or pos (point-min))))) - (consult--each-line beg end - (push (consult--buffer-substring beg end) lines))) - (setq lines (nreverse lines)) - (lambda (action input) - ;; Restoring content and point position - (when (and (eq action 'return) last-input) - ;; No undo recording, modification hooks, buffer modified-status - (with-silent-modifications (funcall replace content-orig point-orig))) - ;; Committing or new input provided -> Update - (when (and input ;; Input has been provided - (or - ;; Committing, but not with empty input - (and (eq action 'return) (not (string-match-p "\\`!? ?\\'" input))) - ;; Input has changed - (not (equal input last-input)))) - (let ((filtered-content - (if (string-match-p "\\`!? ?\\'" input) - ;; Special case the empty input for performance. - ;; Otherwise it could happen that the minibuffer is empty, - ;; but the buffer has not been updated. - content-orig - (if (eq action 'return) - (apply #'concat (mapcan (lambda (x) (list x "\n")) - (funcall filter input lines))) - (while-no-input - ;; Heavy computation is interruptible if *not* committing! - ;; Allocate new string candidates since the matching function mutates! - (apply #'concat (mapcan (lambda (x) (list x "\n")) - (funcall filter input (mapcar #'copy-sequence lines))))))))) - (when (stringp filtered-content) - (when font-lock-mode (font-lock-mode -1)) - (when (bound-and-true-p whitespace-mode) (whitespace-mode -1)) - (when (bound-and-true-p hl-line-mode) (hl-line-mode -1)) - (if (eq action 'return) - (atomic-change-group - ;; Disable modification hooks for performance - (let ((inhibit-modification-hooks t)) - (funcall replace filtered-content))) - ;; No undo recording, modification hooks, buffer modified-status - (with-silent-modifications - (funcall replace filtered-content) - (setq last-input input)))))) - ;; Restore modes - (when (eq action 'return) - (when hl-line-orig (hl-line-mode 1)) - (when whitespace-orig (whitespace-mode 1)) - (when font-lock-orig (font-lock-mode 1)))))) - -;;;###autoload -(defun consult-keep-lines (filter &optional initial) - "Select a subset of the lines in the current buffer with live preview. - -The selected lines are kept and the other lines are deleted. When called -interactively, the lines selected are those that match the minibuffer input. In -order to match the inverse of the input, prefix the input with `! '. When -called from Elisp, the filtering is performed by a FILTER function. This -command obeys narrowing. - -FILTER is the filter function. -INITIAL is the initial input." - (interactive - (list (lambda (pattern cands) - ;; Use consult-location completion category when filtering lines - (consult--completion-filter-dispatch - pattern cands 'consult-location 'highlight)))) - (consult--forbid-minibuffer) - (let ((ro buffer-read-only)) - (unwind-protect - (consult--minibuffer-with-setup-hook - (lambda () - (when ro - (minibuffer-message - (substitute-command-keys - " [Unlocked read-only buffer. \\[minibuffer-keyboard-quit] to quit.]")))) - (setq buffer-read-only nil) - (consult--with-increased-gc - (consult--prompt - :prompt "Keep lines: " - :initial initial - :history 'consult--line-history - :state (consult--keep-lines-state filter)))) - (setq buffer-read-only ro)))) - -;;;;; Command: consult-focus-lines - -(defun consult--focus-lines-state (filter) - "State function for `consult-focus-lines' with FILTER function." - (let (lines overlays last-input pt-orig pt-min pt-max) - (save-excursion - (save-restriction - (if (not (use-region-p)) - (consult--fontify-all) - (consult--fontify-region (region-beginning) (region-end)) - (narrow-to-region - (region-beginning) - ;; Behave the same as `keep-lines'. - ;; Move to the next line. - (save-excursion - (goto-char (region-end)) - (unless (or (bolp) (eobp)) - (forward-line 0)) - (point)))) - (setq pt-orig (point) pt-min (point-min) pt-max (point-max)) - (let ((i 0)) - (consult--each-line beg end - ;; Use "\n" for empty lines, since we need a non-empty string to - ;; attach the text property to. - (let ((line (if (eq beg end) (char-to-string ?\n) - (buffer-substring-no-properties beg end)))) - (put-text-property 0 1 'consult--focus-line (cons (cl-incf i) beg) line) - (push line lines))) - (setq lines (nreverse lines))))) - (lambda (action input) - ;; New input provided -> Update - (when (and input (not (equal input last-input))) - (let (new-overlays) - (pcase (while-no-input - (unless (string-match-p "\\`!? ?\\'" input) ;; Empty input. - (let* ((inhibit-quit (eq action 'return)) ;; Non interruptible, when quitting! - (not (string-prefix-p "! " input)) - (stripped (string-remove-prefix "! " input)) - (matches (funcall filter stripped lines)) - (old-ind 0) - (block-beg pt-min) - (block-end pt-min)) - (while old-ind - (let ((match (pop matches)) (ind nil) (beg pt-max) (end pt-max) prop) - (when match - (setq prop (get-text-property 0 'consult--focus-line match) - ind (car prop) - beg (cdr prop) - ;; Check for empty lines, see above. - end (+ 1 beg (if (equal match "\n") 0 (length match))))) - (unless (eq ind (1+ old-ind)) - (let ((a (if not block-beg block-end)) - (b (if not block-end beg))) - (when (/= a b) - (push (consult--make-overlay a b 'invisible t) new-overlays))) - (setq block-beg beg)) - (setq block-end end old-ind ind))))) - 'commit) - ('commit - (mapc #'delete-overlay overlays) - (setq last-input input overlays new-overlays)) - (_ (mapc #'delete-overlay new-overlays))))) - (when (eq action 'return) - (cond - ((not input) - (mapc #'delete-overlay overlays) - (goto-char pt-orig)) - ((equal input "") - (consult-focus-lines nil 'show) - (goto-char pt-orig)) - (t - ;; Successfully terminated -> Remember invisible overlays - (setq consult--focus-lines-overlays - (nconc consult--focus-lines-overlays overlays)) - ;; move point past invisible - (goto-char (if-let (ov (and (invisible-p pt-orig) - (seq-find (lambda (ov) (overlay-get ov 'invisible)) - (overlays-at pt-orig)))) - (overlay-end ov) - pt-orig)))))))) - -;;;###autoload -(defun consult-focus-lines (filter &optional show initial) - "Hide or show lines using overlays. - -The selected lines are shown and the other lines hidden. When called -interactively, the lines selected are those that match the minibuffer input. In -order to match the inverse of the input, prefix the input with `! '. With -optional prefix argument SHOW reveal the hidden lines. Alternatively the -command can be restarted to reveal the lines. When called from Elisp, the -filtering is performed by a FILTER function. This command obeys narrowing. - -FILTER is the filter function. -INITIAL is the initial input." - (interactive - (list (lambda (pattern cands) - ;; Use consult-location completion category when filtering lines - (consult--completion-filter-dispatch - pattern cands 'consult-location nil)) - current-prefix-arg)) - (if show - (progn - (mapc #'delete-overlay consult--focus-lines-overlays) - (setq consult--focus-lines-overlays nil) - (message "All lines revealed")) - (consult--forbid-minibuffer) - (consult--with-increased-gc - (consult--prompt - :prompt - (if consult--focus-lines-overlays - "Focus on lines (RET to reveal): " - "Focus on lines: ") - :initial initial - :history 'consult--line-history - :state (consult--focus-lines-state filter))))) - -;;;;; Command: consult-goto-line - -(defun consult--goto-line-position (str msg) - "Transform input STR to line number. -Print an error message with MSG function." - (save-match-data - (if (and str (string-match "\\`\\([[:digit:]]+\\):?\\([[:digit:]]*\\)\\'" str)) - (let ((line (string-to-number (match-string 1 str))) - (col (string-to-number (match-string 2 str)))) - (save-excursion - (save-restriction - (when consult-line-numbers-widen - (widen)) - (goto-char (point-min)) - (forward-line (1- line)) - (goto-char (min (+ (point) col) (pos-eol))) - (point)))) - (when (and str (not (equal str ""))) - (funcall msg "Please enter a number.")) - nil))) - -;;;###autoload -(defun consult-goto-line (&optional arg) - "Read line number and jump to the line with preview. - -Enter either a line number to jump to the first column of the -given line or line:column in order to jump to a specific column. -Jump directly if a line number is given as prefix ARG. The -command respects narrowing and the settings -`consult-goto-line-numbers' and `consult-line-numbers-widen'." - (interactive "P") - (if arg - (call-interactively #'goto-line) - (consult--forbid-minibuffer) - (consult--local-let ((display-line-numbers consult-goto-line-numbers) - (display-line-numbers-widen consult-line-numbers-widen)) - (while (if-let (pos (consult--goto-line-position - (consult--prompt - :prompt "Go to line: " - :history 'goto-line-history - :state - (let ((preview (consult--jump-preview))) - (lambda (action str) - (funcall preview action - (consult--goto-line-position str #'ignore))))) - #'minibuffer-message)) - (consult--jump pos) - t))))) - -;;;;; Command: consult-recent-file - -(defun consult--file-preview () - "Create preview function for files." - (let ((open (consult--temporary-files)) - (preview (consult--buffer-preview))) - (lambda (action cand) - (unless cand - (funcall open)) - (funcall preview action - (and cand - (eq action 'preview) - (funcall open cand)))))) - -(defun consult--file-action (file) - "Open FILE via `consult--buffer-action'." - (consult--buffer-action (find-file-noselect file))) - -(consult--define-state file) - -;;;###autoload -(defun consult-recent-file () - "Find recent file using `completing-read'." - (interactive) - (find-file - (consult--read - (or - (mapcar #'consult--fast-abbreviate-file-name (bound-and-true-p recentf-list)) - (user-error "No recent files, `recentf-mode' is %s" - (if recentf-mode "enabled" "disabled"))) - :prompt "Find recent file: " - :sort nil - :require-match t - :category 'file - :state (consult--file-preview) - :history 'file-name-history))) - -;;;;; Command: consult-mode-command - -(defun consult--mode-name (mode) - "Return name part of MODE." - (replace-regexp-in-string - "global-\\(.*\\)-mode" "\\1" - (replace-regexp-in-string - "\\(-global\\)?-mode\\'" "" - (if (eq mode 'c-mode) - "cc" - (symbol-name mode)) - 'fixedcase) - 'fixedcase)) - -(defun consult--mode-command-candidates (modes) - "Extract commands from MODES. - -The list of features is searched for files belonging to the modes. -From these files, the commands are extracted." - (let* ((case-fold-search) - (buffer (current-buffer)) - (command-filter (consult--regexp-filter (seq-filter #'stringp consult-mode-command-filter))) - (feature-filter (seq-filter #'symbolp consult-mode-command-filter)) - (minor-hash (consult--string-hash minor-mode-list)) - (minor-local-modes (seq-filter (lambda (m) - (and (gethash m minor-hash) - (local-variable-if-set-p m))) - modes)) - (minor-global-modes (seq-filter (lambda (m) - (and (gethash m minor-hash) - (not (local-variable-if-set-p m)))) - modes)) - (major-modes (seq-remove (lambda (m) - (gethash m minor-hash)) - modes)) - (major-paths-hash (consult--string-hash (mapcar #'symbol-file major-modes))) - (minor-local-paths-hash (consult--string-hash (mapcar #'symbol-file minor-local-modes))) - (minor-global-paths-hash (consult--string-hash (mapcar #'symbol-file minor-global-modes))) - (major-name-regexp (regexp-opt (mapcar #'consult--mode-name major-modes))) - (minor-local-name-regexp (regexp-opt (mapcar #'consult--mode-name minor-local-modes))) - (minor-global-name-regexp (regexp-opt (mapcar #'consult--mode-name minor-global-modes))) - (commands)) - (dolist (feature load-history commands) - (when-let (name (alist-get 'provide feature)) - (let* ((path (car feature)) - (file (file-name-nondirectory path)) - (key (cond - ((memq name feature-filter) nil) - ((or (gethash path major-paths-hash) - (string-match-p major-name-regexp file)) - ?m) - ((or (gethash path minor-local-paths-hash) - (string-match-p minor-local-name-regexp file)) - ?l) - ((or (gethash path minor-global-paths-hash) - (string-match-p minor-global-name-regexp file)) - ?g)))) - (when key - (dolist (cmd (cdr feature)) - (let ((sym (cdr-safe cmd))) - (when (and (consp cmd) - (eq (car cmd) 'defun) - (commandp sym) - (not (get sym 'byte-obsolete-info)) - ;; Emacs 28 has a `read-extended-command-predicate' - (if (bound-and-true-p read-extended-command-predicate) - (funcall read-extended-command-predicate sym buffer) - t)) - (let ((name (symbol-name sym))) - (unless (string-match-p command-filter name) - (push (propertize name - 'consult--candidate sym - 'consult--type key) - commands)))))))))))) - -;;;###autoload -(defun consult-mode-command (&rest modes) - "Run a command from any of the given MODES. - -If no MODES are specified, use currently active major and minor modes." - (interactive) - (unless modes - (setq modes (cons major-mode - (seq-filter (lambda (m) - (and (boundp m) (symbol-value m))) - minor-mode-list)))) - (let ((narrow `((?m . ,(format "Major: %s" major-mode)) - (?l . "Local Minor") - (?g . "Global Minor")))) - (command-execute - (consult--read - (consult--mode-command-candidates modes) - :prompt "Mode command: " - :predicate - (lambda (cand) - (let ((key (get-text-property 0 'consult--type cand))) - (if consult--narrow - (= key consult--narrow) - (/= key ?g)))) - :lookup #'consult--lookup-candidate - :group (consult--type-group narrow) - :narrow narrow - :require-match t - :history 'extended-command-history - :category 'command)))) - -;;;;; Command: consult-yank - -(defun consult--read-from-kill-ring () - "Open kill ring menu and return selected string." - ;; `current-kill' updates `kill-ring' with interprogram paste, see - ;; gh:minad/consult#443. - (current-kill 0) - ;; Do not specify a :lookup function in order to preserve completion-styles - ;; highlighting of the current candidate. We have to perform a final lookup to - ;; obtain the original candidate which may be propertized with yank-specific - ;; properties, like 'yank-handler. - (consult--lookup-member - (consult--read - (consult--remove-dups - (or (if consult-yank-rotate - (append kill-ring-yank-pointer - (butlast kill-ring (length kill-ring-yank-pointer))) - kill-ring) - (user-error "Kill ring is empty"))) - :prompt "Yank from kill-ring: " - :history t ;; disable history - :sort nil - :category 'kill-ring - :require-match t - :state - (consult--insertion-preview - (point) - ;; If previous command is yank, hide previously yanked string - (or (and (eq last-command 'yank) (mark t)) (point)))) - kill-ring)) - -;; Adapted from the Emacs `yank-from-kill-ring' function. -;;;###autoload -(defun consult-yank-from-kill-ring (string &optional arg) - "Select STRING from the kill ring and insert it. -With prefix ARG, put point at beginning, and mark at end, like `yank' does. - -This command behaves like `yank-from-kill-ring' in Emacs 28, which also offers -a `completing-read' interface to the `kill-ring'. Additionally the Consult -version supports preview of the selected string." - (interactive (list (consult--read-from-kill-ring) current-prefix-arg)) - (when string - (setq yank-window-start (window-start)) - (push-mark) - (insert-for-yank string) - (setq this-command 'yank) - (when consult-yank-rotate - (if-let (pos (seq-position kill-ring string)) - (setq kill-ring-yank-pointer (nthcdr pos kill-ring)) - (kill-new string))) - (when (consp arg) - ;; Swap point and mark like in `yank'. - (goto-char (prog1 (mark t) - (set-marker (mark-marker) (point) (current-buffer))))))) - -(put 'consult-yank-replace 'delete-selection 'yank) -(put 'consult-yank-pop 'delete-selection 'yank) -(put 'consult-yank-from-kill-ring 'delete-selection 'yank) - -;;;###autoload -(defun consult-yank-pop (&optional arg) - "If there is a recent yank act like `yank-pop'. - -Otherwise select string from the kill ring and insert it. -See `yank-pop' for the meaning of ARG. - -This command behaves like `yank-pop' in Emacs 28, which also offers a -`completing-read' interface to the `kill-ring'. Additionally the Consult -version supports preview of the selected string." - (interactive "*p") - (if (eq last-command 'yank) - (yank-pop (or arg 1)) - (call-interactively #'consult-yank-from-kill-ring))) - -;; Adapted from the Emacs yank-pop function. -;;;###autoload -(defun consult-yank-replace (string) - "Select STRING from the kill ring. - -If there was no recent yank, insert the string. -Otherwise replace the just-yanked string with the selected string. - -There exists no equivalent of this command in Emacs 28." - (interactive (list (consult--read-from-kill-ring))) - (when string - (if (not (eq last-command 'yank)) - (consult-yank-from-kill-ring string) - (let ((inhibit-read-only t) - (pt (point)) - (mk (mark t))) - (setq this-command 'yank) - (funcall (or yank-undo-function 'delete-region) (min pt mk) (max pt mk)) - (setq yank-undo-function nil) - (set-marker (mark-marker) pt (current-buffer)) - (insert-for-yank string) - (set-window-start (selected-window) yank-window-start t) - (if (< pt mk) - (goto-char (prog1 (mark t) - (set-marker (mark-marker) (point) (current-buffer))))))))) - -;;;;; Command: consult-bookmark - -(defun consult--bookmark-preview () - "Create preview function for bookmarks." - (let ((preview (consult--jump-preview)) - (open (consult--temporary-files))) - (lambda (action cand) - (unless cand - (funcall open)) - (funcall - preview action - ;; Only preview bookmarks with the default handler. - (when-let ((bm (and cand (eq action 'preview) (assoc cand bookmark-alist))) - (handler (or (bookmark-get-handler bm) #'bookmark-default-handler)) - ((eq handler #'bookmark-default-handler)) - (file (bookmark-get-filename bm)) - (pos (bookmark-get-position bm)) - (buf (funcall open file))) - (set-marker (make-marker) pos buf)))))) - -(defun consult--bookmark-action (bm) - "Open BM via `consult--buffer-action'." - (bookmark-jump bm consult--buffer-display)) - -(consult--define-state bookmark) - -(defun consult--bookmark-candidates () - "Return bookmark candidates." - (bookmark-maybe-load-default-file) - (let ((narrow (cl-loop for (y _ . xs) in consult-bookmark-narrow nconc - (cl-loop for x in xs collect (cons x y))))) - (cl-loop for bm in bookmark-alist collect - (propertize (car bm) - 'consult--type - (alist-get - (or (bookmark-get-handler bm) #'bookmark-default-handler) - narrow))))) - -;;;###autoload -(defun consult-bookmark (name) - "If bookmark NAME exists, open it, otherwise create a new bookmark with NAME. - -The command supports preview of file bookmarks and narrowing. See the -variable `consult-bookmark-narrow' for the narrowing configuration." - (interactive - (list - (let ((narrow (cl-loop for (x y . _) in consult-bookmark-narrow collect (cons x y)))) - (consult--read - (consult--bookmark-candidates) - :prompt "Bookmark: " - :state (consult--bookmark-preview) - :category 'bookmark - :history 'bookmark-history - ;; Add default names to future history. - ;; Ignore errors such that `consult-bookmark' can be used in - ;; buffers which are not backed by a file. - :add-history (ignore-errors (bookmark-prop-get (bookmark-make-record) 'defaults)) - :group (consult--type-group narrow) - :narrow (consult--type-narrow narrow))))) - (bookmark-maybe-load-default-file) - (if (assoc name bookmark-alist) - (bookmark-jump name) - (bookmark-set name))) - -;;;;; Command: consult-complex-command - -;;;###autoload -(defun consult-complex-command () - "Select and evaluate command from the command history. - -This command can act as a drop-in replacement for `repeat-complex-command'." - (interactive) - (let* ((history (or (delete-dups (mapcar #'prin1-to-string command-history)) - (user-error "There are no previous complex commands"))) - (cmd (read (consult--read - history - :prompt "Command: " - :default (car history) - :sort nil - :history t ;; disable history - :category 'expression)))) - ;; Taken from `repeat-complex-command' - (add-to-history 'command-history cmd) - (apply #'funcall-interactively - (car cmd) - (mapcar (lambda (e) (eval e t)) (cdr cmd))))) - -;;;;; Command: consult-history - -(declare-function ring-elements "ring") - -(defun consult--current-history () - "Return the history and index variable relevant to the current buffer. -If the minibuffer is active, the minibuffer history is returned, -otherwise the history corresponding to the mode. There is a -special case for `repeat-complex-command', for which the command -history is used." - (cond - ;; In the minibuffer we use the current minibuffer history, - ;; which can be configured by setting `minibuffer-history-variable'. - ((minibufferp) - (when (eq minibuffer-history-variable t) - (user-error "Minibuffer history is disabled for `%s'" this-command)) - (list (mapcar #'consult--tofu-hide - (if (eq minibuffer-history-variable 'command-history) - ;; If pressing "C-x M-:", i.e., `repeat-complex-command', - ;; we are instead querying the `command-history' and get a - ;; full s-expression. Alternatively you might want to use - ;; `consult-complex-command', which can also be bound to - ;; "C-x M-:"! - (mapcar #'prin1-to-string command-history) - (symbol-value minibuffer-history-variable))))) - ;; Otherwise we use a mode-specific history, see `consult-mode-histories'. - (t (let ((found (seq-find (lambda (h) - (and (derived-mode-p (car h)) - (boundp (if (consp (cdr h)) (cadr h) (cdr h))))) - consult-mode-histories))) - (unless found - (user-error "No history configured for `%s', see `consult-mode-histories'" - major-mode)) - (cons (symbol-value (cadr found)) (cddr found)))))) - -;;;###autoload -(defun consult-history (&optional history index bol) - "Insert string from HISTORY of current buffer. -In order to select from a specific HISTORY, pass the history -variable as argument. INDEX is the name of the index variable to -update, if any. BOL is the function which jumps to the beginning -of the prompt. See also `cape-history' from the Cape package." - (interactive) - (pcase-let* ((`(,history ,index ,bol) (if history - (list history index bol) - (consult--current-history))) - (history (if (ring-p history) (ring-elements history) history)) - (`(,beg . ,end) - (if (minibufferp) - (cons (minibuffer-prompt-end) (point-max)) - (if bol - (save-excursion - (funcall bol) - (cons (point) (pos-eol))) - (cons (point) (point))))) - (str (consult--local-let ((enable-recursive-minibuffers t)) - (consult--read - (or (consult--remove-dups history) - (user-error "History is empty")) - :prompt "History: " - :history t ;; disable history - :category ;; Report category depending on history variable - (and (minibufferp) - (pcase minibuffer-history-variable - ('extended-command-history 'command) - ('buffer-name-history 'buffer) - ('face-name-history 'face) - ('read-envvar-name-history 'environment-variable) - ('bookmark-history 'bookmark) - ('file-name-history 'file))) - :sort nil - :initial (buffer-substring-no-properties beg end) - :state (consult--insertion-preview beg end))))) - (delete-region beg end) - (when index - (set index (seq-position history str))) - (insert (substring-no-properties str)))) - -;;;;; Command: consult-isearch-history - -(defun consult-isearch-forward (&optional reverse) - "Continue Isearch forward optionally in REVERSE." - (interactive) - (consult--require-minibuffer) - (setq isearch-new-forward (not reverse) isearch-new-nonincremental nil) - (funcall (or (command-remapping #'exit-minibuffer) #'exit-minibuffer))) - -(defun consult-isearch-backward (&optional reverse) - "Continue Isearch backward optionally in REVERSE." - (interactive) - (consult-isearch-forward (not reverse))) - -;; Emacs 28: hide in M-X -(put #'consult-isearch-backward 'completion-predicate #'ignore) -(put #'consult-isearch-forward 'completion-predicate #'ignore) - -(defvar-keymap consult-isearch-history-map - :doc "Additional keymap used by `consult-isearch-history'." - " " #'consult-isearch-forward - " " #'consult-isearch-backward) - -(defun consult--isearch-history-candidates () - "Return Isearch history candidates." - ;; Do not throw an error on empty history, in order to allow starting a - ;; search. We do not :require-match here. - (let ((history (if (eq t search-default-mode) - (append regexp-search-ring search-ring) - (append search-ring regexp-search-ring)))) - (delete-dups - (mapcar - (lambda (cand) - ;; The search type can be distinguished via text properties. - (let* ((props (plist-member (text-properties-at 0 cand) - 'isearch-regexp-function)) - (type (pcase (cadr props) - ((and 'nil (guard (not props))) ?r) - ('nil ?l) - ('word-search-regexp ?w) - ('isearch-symbol-regexp ?s) - ('char-fold-to-regexp ?c) - (_ ?u)))) - ;; Disambiguate history items. The same string could - ;; occur with different search types. - (consult--tofu-append cand type))) - history)))) - -(defconst consult--isearch-history-narrow - '((?c . "Char") - (?u . "Custom") - (?l . "Literal") - (?r . "Regexp") - (?s . "Symbol") - (?w . "Word"))) - -;;;###autoload -(defun consult-isearch-history () - "Read a search string with completion from the Isearch history. - -This replaces the current search string if Isearch is active, and -starts a new Isearch session otherwise." - (interactive) - (consult--forbid-minibuffer) - (let* ((isearch-message-function #'ignore) - (cursor-in-echo-area t) ;; Avoid cursor flickering - (candidates (consult--isearch-history-candidates))) - (unless isearch-mode (isearch-mode t)) - (with-isearch-suspended - (setq isearch-new-string - (consult--read - candidates - :prompt "I-search: " - :category 'consult-isearch-history - :history t ;; disable history - :sort nil - :initial isearch-string - :keymap consult-isearch-history-map - :annotate - (lambda (cand) - (consult--annotate-align - cand - (alist-get (consult--tofu-get cand) consult--isearch-history-narrow))) - :group - (lambda (cand transform) - (if transform - cand - (alist-get (consult--tofu-get cand) consult--isearch-history-narrow))) - :lookup - (lambda (selected candidates &rest _) - (if-let (found (member selected candidates)) - (substring (car found) 0 -1) - selected)) - :state - (lambda (action cand) - (when (and (eq action 'preview) cand) - (setq isearch-string cand) - (isearch-update-from-string-properties cand) - (isearch-update))) - :narrow - (list :predicate - (lambda (cand) (= (consult--tofu-get cand) consult--narrow)) - :keys consult--isearch-history-narrow)) - isearch-new-message - (mapconcat 'isearch-text-char-description isearch-new-string ""))) - ;; Setting `isearch-regexp' etc only works outside of `with-isearch-suspended'. - (unless (plist-member (text-properties-at 0 isearch-string) 'isearch-regexp-function) - (setq isearch-regexp t - isearch-regexp-function nil)))) - -;;;;; Command: consult-minor-mode-menu - -(defun consult--minor-mode-candidates () - "Return list of minor-mode candidate strings." - (mapcar - (pcase-lambda (`(,name . ,sym)) - (propertize - name - 'consult--candidate sym - 'consult--minor-mode-narrow - (logior - (ash (if (local-variable-if-set-p sym) ?l ?g) 8) - (if (and (boundp sym) (symbol-value sym)) ?i ?o)) - 'consult--minor-mode-group - (concat - (if (local-variable-if-set-p sym) "Local " "Global ") - (if (and (boundp sym) (symbol-value sym)) "On" "Off")))) - (nconc - ;; according to describe-minor-mode-completion-table-for-symbol - ;; the minor-mode-list contains *all* minor modes - (mapcar (lambda (sym) (cons (symbol-name sym) sym)) minor-mode-list) - ;; take the lighters from minor-mode-alist - (delq nil - (mapcar (pcase-lambda (`(,sym ,lighter)) - (when (and lighter (not (equal "" lighter))) - (let (message-log-max) - (setq lighter (string-trim (format-mode-line lighter))) - (unless (string-blank-p lighter) - (cons lighter sym))))) - minor-mode-alist))))) - -(defconst consult--minor-mode-menu-narrow - '((?l . "Local") - (?g . "Global") - (?i . "On") - (?o . "Off"))) - -;;;###autoload -(defun consult-minor-mode-menu () - "Enable or disable minor mode. - -This is an alternative to `minor-mode-menu-from-indicator'." - (interactive) - (call-interactively - (consult--read - (consult--minor-mode-candidates) - :prompt "Minor mode: " - :require-match t - :category 'minor-mode - :group - (lambda (cand transform) - (if transform cand (get-text-property 0 'consult--minor-mode-group cand))) - :narrow - (list :predicate - (lambda (cand) - (let ((narrow (get-text-property 0 'consult--minor-mode-narrow cand))) - (or (= (logand narrow 255) consult--narrow) - (= (ash narrow -8) consult--narrow)))) - :keys - consult--minor-mode-menu-narrow) - :lookup #'consult--lookup-candidate - :history 'consult--minor-mode-menu-history))) - -;;;;; Command: consult-theme - -;;;###autoload -(defun consult-theme (theme) - "Disable current themes and enable THEME from `consult-themes'. - -The command supports previewing the currently selected theme." - (interactive - (list - (let* ((regexp (consult--regexp-filter - (mapcar (lambda (x) (if (stringp x) x (format "\\`%s\\'" x))) - consult-themes))) - (avail-themes (seq-filter - (lambda (x) (string-match-p regexp (symbol-name x))) - (cons 'default (custom-available-themes)))) - (saved-theme (car custom-enabled-themes))) - (consult--read - (mapcar #'symbol-name avail-themes) - :prompt "Theme: " - :require-match t - :category 'theme - :history 'consult--theme-history - :lookup (lambda (selected &rest _) - (setq selected (and selected (intern-soft selected))) - (or (and selected (car (memq selected avail-themes))) - saved-theme)) - :state (lambda (action theme) - (pcase action - ('return (consult-theme (or theme saved-theme))) - ((and 'preview (guard theme)) (consult-theme theme)))) - :default (symbol-name (or saved-theme 'default)))))) - (when (eq theme 'default) (setq theme nil)) - (unless (eq theme (car custom-enabled-themes)) - (mapc #'disable-theme custom-enabled-themes) - (when theme - (if (custom-theme-p theme) - (enable-theme theme) - (load-theme theme :no-confirm))))) - -;;;;; Command: consult-buffer - -(defun consult--buffer-sort-alpha (buffers) - "Sort BUFFERS alphabetically, put starred buffers at the end." - (sort buffers - (lambda (x y) - (setq x (buffer-name x) y (buffer-name y)) - (let ((a (and (length> x 0) (eq (aref x 0) ?*))) - (b (and (length> y 0) (eq (aref y 0) ?*)))) - (if (eq a b) - (string< x y) - (not a)))))) - -(defun consult--buffer-sort-alpha-current (buffers) - "Sort BUFFERS alphabetically, put current at the beginning." - (let ((buffers (consult--buffer-sort-alpha buffers)) - (current (current-buffer))) - (if (memq current buffers) - (cons current (delq current buffers)) - buffers))) - -(defun consult--buffer-sort-visibility (buffers) - "Sort BUFFERS by visibility." - (let ((hidden) - (current (current-buffer))) - (consult--keep! buffers - (unless (eq it current) - (if (get-buffer-window it 'visible) - it - (push it hidden) - nil))) - (nconc (nreverse hidden) buffers (list current)))) - -(defun consult--normalize-directory (dir) - "Normalize directory DIR. -DIR can be project, nil or a path." - (cond - ((eq dir 'project) (consult--project-root)) - (dir (expand-file-name dir)))) - -(defun consult--buffer-query-prompt (prompt query) - "Return a list of buffers and create an appropriate prompt string. -Return a pair of a prompt string and a list of buffers. PROMPT -is the prefix of the prompt string. QUERY specifies the buffers -to search and is passed to `consult--buffer-query'." - (let* ((dir (plist-get query :directory)) - (ndir (consult--normalize-directory dir)) - (buffers (apply #'consult--buffer-query :directory ndir query)) - (count (length buffers))) - (cons (format "%s (%d buffer%s%s): " prompt count - (if (= count 1) "" "s") - (cond - ((and ndir (eq dir 'project)) - (format ", Project %s" (consult--project-name ndir))) - (ndir (concat ", " (consult--left-truncate-file ndir))) - (t ""))) - buffers))) - -(cl-defun consult--buffer-query (&key sort directory mode as predicate (filter t) - include (exclude consult-buffer-filter)) - "Query for a list of matching buffers. -The function supports filtering by various criteria which are -used throughout Consult. In particular it is the backbone of -most `consult-buffer-sources'. -DIRECTORY can either be the symbol project or a file name. -SORT can be visibility, alpha or nil. -FILTER can be either t, nil or invert. -EXCLUDE is a list of regexps. -INCLUDE is a list of regexps. -MODE can be a mode or a list of modes to restrict the returned buffers. -PREDICATE is a predicate function. -AS is a conversion function." - (let ((root (consult--normalize-directory directory)) - (buffers (buffer-list))) - (when sort - (setq buffers (funcall (intern (format "consult--buffer-sort-%s" sort)) buffers))) - (when (or filter mode as root) - (let ((exclude-re (consult--regexp-filter exclude)) - (include-re (consult--regexp-filter include)) - (case-fold-search)) - (consult--keep! buffers - (and - (or (not mode) - (let ((mm (buffer-local-value 'major-mode it))) - (if (consp mode) - (seq-some (lambda (m) (provided-mode-derived-p mm m)) mode) - (provided-mode-derived-p mm mode)))) - (pcase-exhaustive filter - ('nil t) - ((or 't 'invert) - (eq (eq filter t) - (and - (or (not exclude) - (not (string-match-p exclude-re (buffer-name it)))) - (or (not include) - (not (not (string-match-p include-re (buffer-name it))))))))) - (or (not root) - (when-let (dir (buffer-local-value 'default-directory it)) - (string-prefix-p root - (if (and (/= 0 (length dir)) (eq (aref dir 0) ?/)) - dir - (expand-file-name dir))))) - (or (not predicate) (funcall predicate it)) - (if as (funcall as it) it))))) - buffers)) - -(defun consult--buffer-file-hash () - "Return hash table of all buffer file names." - (consult--string-hash (consult--buffer-query :as #'buffer-file-name))) - -(defun consult--buffer-preview () - "Buffer preview function." - (let ((orig-buf (window-buffer (consult--original-window))) - (orig-prev (copy-sequence (window-prev-buffers))) - (orig-next (copy-sequence (window-next-buffers))) - other-win) - (lambda (action cand) - (pcase action - ('exit - (set-window-prev-buffers other-win orig-prev) - (set-window-next-buffers other-win orig-next)) - ('preview - (when (and (eq consult--buffer-display #'switch-to-buffer-other-window) - (not other-win)) - (switch-to-buffer-other-window orig-buf 'norecord) - (setq other-win (selected-window))) - (let ((win (or other-win (selected-window))) - (buf (or (and cand (get-buffer cand)) orig-buf))) - (when (and (window-live-p win) (buffer-live-p buf)) - (with-selected-window win - (unless (or orig-prev orig-next) - (setq orig-prev (copy-sequence (window-prev-buffers)) - orig-next (copy-sequence (window-next-buffers)))) - (switch-to-buffer buf 'norecord))))))))) - -(defun consult--buffer-action (buffer &optional norecord) - "Switch to BUFFER via `consult--buffer-display' function. -If NORECORD is non-nil, do not record the buffer switch in the buffer list." - (funcall consult--buffer-display buffer norecord)) - -(consult--define-state buffer) - -(defvar consult--source-bookmark - `(:name "Bookmark" - :narrow ?m - :category bookmark - :face consult-bookmark - :history bookmark-history - :items ,#'bookmark-all-names - :state ,#'consult--bookmark-state) - "Bookmark candidate source for `consult-buffer'.") - -(defvar consult--source-project-buffer - `(:name "Project Buffer" - :narrow ?b - :category buffer - :face consult-buffer - :history buffer-name-history - :state ,#'consult--buffer-state - :enabled ,(lambda () consult-project-function) - :items - ,(lambda () - (when-let (root (consult--project-root)) - (consult--buffer-query :sort 'visibility - :directory root - :as #'buffer-name)))) - "Project buffer candidate source for `consult-buffer'.") - -(defvar consult--source-project-recent-file - `(:name "Project File" - :narrow ?f - :category file - :face consult-file - :history file-name-history - :state ,#'consult--file-state - :new - ,(lambda (file) - (consult--file-action - (expand-file-name file (consult--project-root)))) - :enabled - ,(lambda () - (and consult-project-function - recentf-mode)) - :items - ,(lambda () - (when-let (root (consult--project-root)) - (let ((len (length root)) - (ht (consult--buffer-file-hash)) - items) - (dolist (file (bound-and-true-p recentf-list) (nreverse items)) - ;; Emacs 29 abbreviates file paths by default, see - ;; `recentf-filename-handlers'. I recommend to set - ;; `recentf-filename-handlers' to nil to avoid any slow down. - (unless (eq (aref file 0) ?/) - (let (file-name-handler-alist) ;; No Tramp slowdown please. - (setq file (expand-file-name file)))) - (when (and (not (gethash file ht)) (string-prefix-p root file)) - (let ((part (substring file len))) - (when (equal part "") (setq part "./")) - (put-text-property 0 1 'multi-category `(file . ,file) part) - (push part items)))))))) - "Project file candidate source for `consult-buffer'.") - -(defvar consult--source-project-buffer-hidden - `(:hidden t :narrow (?p . "Project") ,@consult--source-project-buffer) - "Like `consult--source-project-buffer' but hidden by default.") - -(defvar consult--source-project-recent-file-hidden - `(:hidden t :narrow (?p . "Project") ,@consult--source-project-recent-file) - "Like `consult--source-project-recent-file' but hidden by default.") - -(defvar consult--source-hidden-buffer - `(:name "Hidden Buffer" - :narrow ?\s - :hidden t - :category buffer - :face consult-buffer - :history buffer-name-history - :action ,#'consult--buffer-action - :items - ,(lambda () (consult--buffer-query :sort 'visibility - :filter 'invert - :as #'buffer-name))) - "Hidden buffer candidate source for `consult-buffer'.") - -(defvar consult--source-modified-buffer - `(:name "Modified Buffer" - :narrow ?* - :hidden t - :category buffer - :face consult-buffer - :history buffer-name-history - :state ,#'consult--buffer-state - :items - ,(lambda () (consult--buffer-query :sort 'visibility - :as #'buffer-name - :predicate - (lambda (buf) - (and (buffer-modified-p buf) - (buffer-file-name buf)))))) - "Modified buffer candidate source for `consult-buffer'.") - -(defvar consult--source-buffer - `(:name "Buffer" - :narrow ?b - :category buffer - :face consult-buffer - :history buffer-name-history - :state ,#'consult--buffer-state - :default t - :items - ,(lambda () (consult--buffer-query :sort 'visibility - :as #'buffer-name))) - "Buffer candidate source for `consult-buffer'.") - -(defun consult--file-register-p (reg) - "Return non-nil if REG is a file register." - (memq (car-safe (cdr reg)) '(file-query file))) - -(autoload 'consult-register--candidates "consult-register") -(defvar consult--source-file-register - `(:name "File Register" - :narrow (?r . "Register") - :category file - :state ,#'consult--file-state - :enabled ,(lambda () (seq-some #'consult--file-register-p register-alist)) - :items ,(lambda () (consult-register--candidates #'consult--file-register-p))) - "File register source.") - -(defvar consult--source-recent-file - `(:name "File" - :narrow ?f - :category file - :face consult-file - :history file-name-history - :state ,#'consult--file-state - :new ,#'consult--file-action - :enabled ,(lambda () recentf-mode) - :items - ,(lambda () - (let ((ht (consult--buffer-file-hash)) - items) - (dolist (file (bound-and-true-p recentf-list) (nreverse items)) - ;; Emacs 29 abbreviates file paths by default, see - ;; `recentf-filename-handlers'. I recommend to set - ;; `recentf-filename-handlers' to nil to avoid any slow down. - (unless (eq (aref file 0) ?/) - (let (file-name-handler-alist) ;; No Tramp slowdown please. - (setq file (expand-file-name file)))) - (unless (gethash file ht) - (push (consult--fast-abbreviate-file-name file) items)))))) - "Recent file candidate source for `consult-buffer'.") - -;;;###autoload -(defun consult-buffer (&optional sources) - "Enhanced `switch-to-buffer' command with support for virtual buffers. - -The command supports recent files, bookmarks, views and project files as -virtual buffers. Buffers are previewed. Narrowing to buffers (b), files (f), -bookmarks (m) and project files (p) is supported via the corresponding -keys. In order to determine the project-specific files and buffers, the -`consult-project-function' is used. The virtual buffer SOURCES -default to `consult-buffer-sources'. See `consult--multi' for the -configuration of the virtual buffer sources." - (interactive) - (let ((selected (consult--multi (or sources consult-buffer-sources) - :require-match - (confirm-nonexistent-file-or-buffer) - :prompt "Switch to: " - :history 'consult--buffer-history - :sort nil))) - ;; For non-matching candidates, fall back to buffer creation. - (unless (plist-get (cdr selected) :match) - (consult--buffer-action (car selected))))) - -(defmacro consult--with-project (&rest body) - "Ensure that BODY is executed with a project root." - ;; We have to work quite hard here to ensure that the project root is - ;; only overridden at the current recursion level. When entering a - ;; recursive minibuffer session, we should be able to still switch the - ;; project. But who does that? Working on the first level on project A - ;; and on the second level on project B and on the third level on project C? - ;; You mustn't be afraid to dream a little bigger, darling. - `(let ((consult-project-function - (let ((root (or (consult--project-root t) (user-error "No project found"))) - (depth (recursion-depth)) - (orig consult-project-function)) - (lambda (may-prompt) - (if (= depth (recursion-depth)) - root - (funcall orig may-prompt)))))) - ,@body)) - -;;;###autoload -(defun consult-project-buffer () - "Enhanced `project-switch-to-buffer' command with support for virtual buffers. -The command may prompt you for a project directory if it is invoked from -outside a project. See `consult-buffer' for more details." - (interactive) - (consult--with-project - (consult-buffer consult-project-buffer-sources))) - -;;;###autoload -(defun consult-buffer-other-window () - "Variant of `consult-buffer', switching to a buffer in another window." - (interactive) - (let ((consult--buffer-display #'switch-to-buffer-other-window)) - (consult-buffer))) - -;;;###autoload -(defun consult-buffer-other-frame () - "Variant of `consult-buffer', switching to a buffer in another frame." - (interactive) - (let ((consult--buffer-display #'switch-to-buffer-other-frame)) - (consult-buffer))) - -;;;###autoload -(defun consult-buffer-other-tab () - "Variant of `consult-buffer', switching to a buffer in another tab." - (interactive) - (let ((consult--buffer-display #'switch-to-buffer-other-tab)) - (consult-buffer))) - -;;;;; Command: consult-grep - -(defun consult--grep-format (async builder) - "Return ASYNC function highlighting grep match results. -BUILDER is the command line builder function." - (let (highlight) - (lambda (action) - (cond - ((stringp action) - (setq highlight (cdr (funcall builder action))) - (funcall async action)) - ((consp action) - (let ((file "") (file-len 0) result) - (save-match-data - (dolist (str action) - (when (and (string-match consult--grep-match-regexp str) - ;; Filter out empty context lines - (or (/= (aref str (match-beginning 3)) ?-) - (/= (match-end 0) (length str)))) - ;; We share the file name across candidates to reduce - ;; the amount of allocated memory. - (unless (and (= file-len (- (match-end 1) (match-beginning 1))) - (eq t (compare-strings - file 0 file-len - str (match-beginning 1) (match-end 1) nil))) - (setq file (match-string 1 str) - file-len (length file))) - (let* ((line (match-string 2 str)) - (ctx (= (aref str (match-beginning 3)) ?-)) - (sep (if ctx "-" ":")) - (content (substring str (match-end 0))) - (line-len (length line))) - (when (length> content consult-grep-max-columns) - (setq content (substring content 0 consult-grep-max-columns))) - (when highlight - (funcall highlight content)) - (setq str (concat file sep line sep content)) - ;; Store file name in order to avoid allocations in `consult--prefix-group' - (add-text-properties 0 file-len `(face consult-file consult--prefix-group ,file) str) - (put-text-property (1+ file-len) (+ 1 file-len line-len) 'face 'consult-line-number str) - (when ctx - (add-face-text-property (+ 2 file-len line-len) (length str) 'consult-grep-context 'append str)) - (push str result))))) - (funcall async (nreverse result)))) - (t (funcall async action)))))) - -(defun consult--grep-position (cand &optional find-file) - "Return the grep position marker for CAND. -FIND-FILE is the file open function, defaulting to `find-file-noselect'." - (when cand - (let* ((file-end (next-single-property-change 0 'face cand)) - (line-end (next-single-property-change (1+ file-end) 'face cand)) - (matches (consult--point-placement cand (1+ line-end) 'consult-grep-context)) - (file (substring-no-properties cand 0 file-end)) - (line (string-to-number (substring-no-properties cand (+ 1 file-end) line-end)))) - (when-let (pos (consult--marker-from-line-column - (funcall (or find-file #'find-file-noselect) file) - line (or (car matches) 0))) - (cons pos (cdr matches)))))) - -(defun consult--grep-state () - "Grep state function." - (let ((open (consult--temporary-files)) - (jump (consult--jump-state))) - (lambda (action cand) - (unless cand - (funcall open)) - (funcall jump action (consult--grep-position - cand - (and (not (eq action 'return)) open)))))) - -(defun consult--grep-exclude-args () - "Produce grep exclude arguments. -Take the variables `grep-find-ignored-directories' and -`grep-find-ignored-files' into account." - (unless (boundp 'grep-find-ignored-files) (require 'grep)) - (nconc (mapcar (lambda (s) (concat "--exclude=" s)) - (bound-and-true-p grep-find-ignored-files)) - (mapcar (lambda (s) (concat "--exclude-dir=" s)) - (bound-and-true-p grep-find-ignored-directories)))) - -(defun consult--grep (prompt make-builder dir initial) - "Run asynchronous grep. - -MAKE-BUILDER is the function that returns the command line -builder function. DIR is a directory or a list of file or -directories. PROMPT is the prompt string. INITIAL is initial -input." - (pcase-let* ((`(,prompt ,paths ,dir) (consult--directory-prompt prompt dir)) - (default-directory dir) - (builder (funcall make-builder paths))) - (consult--read - (consult--async-command builder - (consult--grep-format builder) - :file-handler t) ;; allow tramp - :prompt prompt - :lookup #'consult--lookup-member - :state (consult--grep-state) - :initial (consult--async-split-initial initial) - :add-history (consult--async-split-thingatpt 'symbol) - :require-match t - :category 'consult-grep - :group #'consult--prefix-group - :history '(:input consult--grep-history) - :sort nil))) - -(defun consult--grep-lookahead-p (&rest cmd) - "Return t if grep CMD supports look-ahead." - (eq 0 (process-file-shell-command - (concat "echo xaxbx | " - (mapconcat #'shell-quote-argument `(,@cmd "^(?=.*b)(?=.*a)") " "))))) - -(defun consult--grep-make-builder (paths) - "Build grep command line and grep across PATHS." - (let* ((cmd (consult--build-args consult-grep-args)) - (type (if (consult--grep-lookahead-p (car cmd) "-P") 'pcre 'extended))) - (lambda (input) - (pcase-let* ((`(,arg . ,opts) (consult--command-split input)) - (flags (append cmd opts)) - (ignore-case (or (member "-i" flags) (member "--ignore-case" flags)))) - (if (or (member "-F" flags) (member "--fixed-strings" flags)) - (cons (append cmd (list "-e" arg) opts paths) - (apply-partially #'consult--highlight-regexps - (list (regexp-quote arg)) ignore-case)) - (pcase-let ((`(,re . ,hl) (funcall consult--regexp-compiler arg type ignore-case))) - (when re - (cons (append cmd - (list (if (eq type 'pcre) "-P" "-E") ;; perl or extended - "-e" (consult--join-regexps re type)) - opts paths) - hl)))))))) - -;;;###autoload -(defun consult-grep (&optional dir initial) - "Search with `grep' for files in DIR where the content matches a regexp. - -The initial input is given by the INITIAL argument. DIR can be -nil, a directory string or a list of file/directory paths. If -`consult-grep' is called interactively with a prefix argument, -the user can specify the directories or files to search in. -Multiple directories must be separated by comma in the -minibuffer, since they are read via `completing-read-multiple'. -By default the project directory is used if -`consult-project-function' is defined and returns non-nil. -Otherwise the `default-directory' is searched. - -The input string is split, the first part of the string (grep -input) is passed to the asynchronous grep process and the second -part of the string is passed to the completion-style filtering. - -The input string is split at a punctuation character, which is -given as the first character of the input string. The format is -similar to Perl-style regular expressions, e.g., /regexp/. -Furthermore command line options can be passed to grep, specified -behind --. The overall prompt input has the form -`#async-input -- grep-opts#filter-string'. - -Note that the grep input string is transformed from Emacs regular -expressions to Posix regular expressions. Always enter Emacs -regular expressions at the prompt. `consult-grep' behaves like -builtin Emacs search commands, e.g., Isearch, which take Emacs -regular expressions. Furthermore the asynchronous input split -into words, each word must match separately and in any order. -See `consult--regexp-compiler' for the inner workings. In order -to disable transformations of the grep input, adjust -`consult--regexp-compiler' accordingly. - -Here we give a few example inputs: - -#alpha beta : Search for alpha and beta in any order. -#alpha.*beta : Search for alpha before beta. -#\\(alpha\\|beta\\) : Search for alpha or beta (Note Emacs syntax!) -#word -- -C3 : Search for word, include 3 lines as context -#first#second : Search for first, quick filter for second. - -The symbol at point is added to the future history." - (interactive "P") - (consult--grep "Grep" #'consult--grep-make-builder dir initial)) - -;;;;; Command: consult-git-grep - -(defun consult--git-grep-make-builder (paths) - "Create grep command line builder given PATHS." - (let ((cmd (consult--build-args consult-git-grep-args))) - (lambda (input) - (pcase-let* ((`(,arg . ,opts) (consult--command-split input)) - (flags (append cmd opts)) - (ignore-case (or (member "-i" flags) (member "--ignore-case" flags)))) - (if (or (member "-F" flags) (member "--fixed-strings" flags)) - (cons (append cmd (list "-e" arg) opts paths) - (apply-partially #'consult--highlight-regexps - (list (regexp-quote arg)) ignore-case)) - (pcase-let ((`(,re . ,hl) (funcall consult--regexp-compiler arg 'extended ignore-case))) - (when re - (cons (append cmd - (cdr (mapcan (lambda (x) (list "--and" "-e" x)) re)) - opts paths) - hl)))))))) - -;;;###autoload -(defun consult-git-grep (&optional dir initial) - "Search with `git grep' for files in DIR with INITIAL input. -See `consult-grep' for details." - (interactive "P") - (consult--grep "Git-grep" #'consult--git-grep-make-builder dir initial)) - -;;;;; Command: consult-ripgrep - -(defun consult--ripgrep-make-builder (paths) - "Create ripgrep command line builder given PATHS." - (let* ((cmd (consult--build-args consult-ripgrep-args)) - (type (if (consult--grep-lookahead-p (car cmd) "-P") 'pcre 'extended))) - (lambda (input) - (pcase-let* ((`(,arg . ,opts) (consult--command-split input)) - (flags (append cmd opts)) - (ignore-case - (and (not (or (member "-s" flags) (member "--case-sensitive" flags))) - (or (member "-i" flags) (member "--ignore-case" flags) - (and (or (member "-S" flags) (member "--smart-case" flags)) - (let (case-fold-search) - ;; Case insensitive if there are no uppercase letters - (not (string-match-p "[[:upper:]]" arg)))))))) - (if (or (member "-F" flags) (member "--fixed-strings" flags)) - (cons (append cmd (list "-e" arg) opts paths) - (apply-partially #'consult--highlight-regexps - (list (regexp-quote arg)) ignore-case)) - (pcase-let ((`(,re . ,hl) (funcall consult--regexp-compiler arg type ignore-case))) - (when re - (cons (append cmd (and (eq type 'pcre) '("-P")) - (list "-e" (consult--join-regexps re type)) - opts paths) - hl)))))))) - -;;;###autoload -(defun consult-ripgrep (&optional dir initial) - "Search with `rg' for files in DIR with INITIAL input. -See `consult-grep' for details." - (interactive "P") - (consult--grep "Ripgrep" #'consult--ripgrep-make-builder dir initial)) - -;;;;; Command: consult-find - -(defun consult--find (prompt builder initial) - "Run find command in current directory. - -The function returns the selected file. -The filename at point is added to the future history. - -BUILDER is the command line builder function. -PROMPT is the prompt. -INITIAL is initial input." - (consult--read - (consult--async-command builder - (consult--async-map (lambda (x) (string-remove-prefix "./" x))) - (consult--async-highlight builder) - :file-handler t) ;; allow tramp - :prompt prompt - :sort nil - :require-match t - :initial (consult--async-split-initial initial) - :add-history (consult--async-split-thingatpt 'filename) - :category 'file - :history '(:input consult--find-history))) - -(defun consult--find-make-builder (paths) - "Build find command line, finding across PATHS." - (let* ((cmd (seq-mapcat (lambda (x) - (if (equal x ".") paths (list x))) - (consult--build-args consult-find-args))) - (type (if (eq 0 (process-file-shell-command - (concat (car cmd) " -regextype emacs -version"))) - 'emacs 'basic))) - (lambda (input) - (pcase-let* ((`(,arg . ,opts) (consult--command-split input)) - ;; ignore-case=t since -iregex is used below - (`(,re . ,hl) (funcall consult--regexp-compiler arg type t))) - (when re - (cons (append cmd - (cdr (mapcan - (lambda (x) - `("-and" "-iregex" - ,(format ".*%s.*" - ;; Replace non-capturing groups with capturing groups. - ;; GNU find does not support non-capturing groups. - (replace-regexp-in-string - "\\\\(\\?:" "\\(" x 'fixedcase 'literal)))) - re)) - opts) - hl)))))) - -;;;###autoload -(defun consult-find (&optional dir initial) - "Search for files with `find' in DIR. -The file names must match the input regexp. INITIAL is the -initial minibuffer input. See `consult-grep' for details -regarding the asynchronous search and the arguments." - (interactive "P") - (pcase-let* ((`(,prompt ,paths ,dir) (consult--directory-prompt "Find" dir)) - (default-directory dir) - (builder (consult--find-make-builder paths))) - (find-file (consult--find prompt builder initial)))) - -;;;;; Command: consult-fd - -(defun consult--fd-make-builder (paths) - "Build find command line, finding across PATHS." - (let ((cmd (consult--build-args consult-fd-args))) - (lambda (input) - (pcase-let* ((`(,arg . ,opts) (consult--command-split input)) - (flags (append cmd opts)) - (ignore-case - (and (not (or (member "-s" flags) (member "--case-sensitive" flags))) - (or (member "-i" flags) (member "--ignore-case" flags) - (let (case-fold-search) - ;; Case insensitive if there are no uppercase letters - (not (string-match-p "[[:upper:]]" arg))))))) - (if (or (member "-F" flags) (member "--fixed-strings" flags)) - (cons (append cmd (list arg) opts paths) - (apply-partially #'consult--highlight-regexps - (list (regexp-quote arg)) ignore-case)) - (pcase-let ((`(,re . ,hl) (funcall consult--regexp-compiler arg 'pcre ignore-case))) - (when re - (cons (append cmd - (cdr (mapcan (lambda (x) `("--and" ,x)) re)) - opts paths) - hl)))))))) - -;;;###autoload -(defun consult-fd (&optional dir initial) - "Search for files with `fd' in DIR. -The file names must match the input regexp. INITIAL is the -initial minibuffer input. See `consult-grep' for details -regarding the asynchronous search and the arguments." - (interactive "P") - (pcase-let* ((`(,prompt ,paths ,dir) (consult--directory-prompt "Fd" dir)) - (default-directory dir) - (builder (consult--fd-make-builder paths))) - (find-file (consult--find prompt builder initial)))) - -;;;;; Command: consult-locate - -(defun consult--locate-builder (input) - "Build command line from INPUT." - (pcase-let ((`(,arg . ,opts) (consult--command-split input))) - (unless (string-blank-p arg) - (cons (append (consult--build-args consult-locate-args) - (consult--split-escaped arg) opts) - (cdr (consult--default-regexp-compiler input 'basic t)))))) - -;;;###autoload -(defun consult-locate (&optional initial) - "Search with `locate' for files which match input given INITIAL input. - -The input is treated literally such that locate can take advantage of -the locate database index. Regular expressions would often force a slow -linear search through the entire database. The locate process is started -asynchronously, similar to `consult-grep'. See `consult-grep' for more -details regarding the asynchronous search." - (interactive) - (find-file (consult--find "Locate: " #'consult--locate-builder initial))) - -;;;;; Command: consult-man - -(defun consult--man-builder (input) - "Build command line from INPUT." - (pcase-let* ((`(,arg . ,opts) (consult--command-split input)) - (`(,re . ,hl) (funcall consult--regexp-compiler arg 'extended t))) - (when re - (cons (append (consult--build-args consult-man-args) - (list (consult--join-regexps re 'extended)) - opts) - hl)))) - -(defun consult--man-format (lines) - "Format man candidates from LINES." - (let ((candidates)) - (save-match-data - (dolist (str lines) - (when (string-match "\\`\\(.*?\\([^ ]+\\) *(\\([^,)]+\\)[^)]*).*?\\) +- +\\(.*\\)\\'" str) - (let* ((names (match-string 1 str)) - (name (match-string 2 str)) - (section (match-string 3 str)) - (desc (match-string 4 str)) - (cand (format "%s - %s" names desc))) - (add-text-properties 0 (length names) - (list 'face 'consult-file - 'consult-man (concat section " " name)) - cand) - (push cand candidates))))) - (nreverse candidates))) - -;;;###autoload -(defun consult-man (&optional initial) - "Search for man page given INITIAL input. - -The input string is not preprocessed and passed literally to the -underlying man commands. The man process is started asynchronously, -similar to `consult-grep'. See `consult-grep' for more details regarding -the asynchronous search." - (interactive) - (man (consult--read - (consult--async-command #'consult--man-builder - (consult--async-transform consult--man-format) - (consult--async-highlight #'consult--man-builder)) - :prompt "Manual entry: " - :require-match t - :category 'consult-man - :lookup (apply-partially #'consult--lookup-prop 'consult-man) - :initial (consult--async-split-initial initial) - :add-history (consult--async-split-thingatpt 'symbol) - :history '(:input consult--man-history)))) - -;;;; Preview at point in completions buffers - -(define-minor-mode consult-preview-at-point-mode - "Preview minor mode for *Completions* buffers. -When moving around in the *Completions* buffer, the candidate at point is -automatically previewed." - :group 'consult - (if consult-preview-at-point-mode - (add-hook 'post-command-hook #'consult-preview-at-point nil 'local) - (remove-hook 'post-command-hook #'consult-preview-at-point 'local))) - -(defun consult-preview-at-point () - "Preview candidate at point in *Completions* buffer." - (interactive) - (when-let ((win (active-minibuffer-window)) - (buf (window-buffer win)) - (fun (buffer-local-value 'consult--preview-function buf))) - (funcall fun))) - -;;;; Integration with completion systems - -;;;;; Integration: Default *Completions* - -(defun consult--default-completion-minibuffer-candidate () - "Return current minibuffer candidate from default completion system or Icomplete." - (when (and (minibufferp) - (eq completing-read-function #'completing-read-default)) - (let ((content (minibuffer-contents-no-properties))) - ;; When the current minibuffer content matches a candidate, return it! - (if (test-completion content - minibuffer-completion-table - minibuffer-completion-predicate) - content - ;; Return the full first candidate of the sorted completion list. - (when-let ((completions (completion-all-sorted-completions))) - (concat - (substring content 0 (or (cdr (last completions)) 0)) - (car completions))))))) - -(defun consult--default-completion-list-candidate () - "Return current candidate at point from completions buffer." - (let (beg end) - (when (and - (derived-mode-p 'completion-list-mode) - ;; Logic taken from `choose-completion'. - ;; TODO Upstream a `completion-list-get-candidate' function. - (cond - ((and (not (eobp)) (get-text-property (point) 'mouse-face)) - (setq end (point) beg (1+ (point)))) - ((and (not (bobp)) (get-text-property (1- (point)) 'mouse-face)) - (setq end (1- (point)) beg (point))))) - (setq beg (previous-single-property-change beg 'mouse-face) - end (or (next-single-property-change end 'mouse-face) (point-max))) - (or (get-text-property beg 'completion--string) - (buffer-substring-no-properties beg end))))) - -;;;;; Integration: Vertico - -(defvar vertico--input) -(declare-function vertico--exhibit "ext:vertico") -(declare-function vertico--candidate "ext:vertico") -(declare-function vertico--filter-completions "ext:vertico") - -(defun consult--vertico-candidate () - "Return current candidate for Consult preview." - (and vertico--input (vertico--candidate 'highlight))) - -(defun consult--vertico-refresh () - "Refresh completion UI." - (when vertico--input - (setq vertico--input t) - (vertico--exhibit))) - -(defun consult--vertico-filter-adv (orig pattern cands category highlight) - "Advice for ORIG `consult--completion-filter' function. -See `consult--completion-filter' for arguments PATTERN, CANDS, CATEGORY -and HIGHLIGHT." - (if (and (not highlight) (bound-and-true-p vertico-mode)) - ;; Optimize `consult--completion-filter' using the deferred highlighting - ;; from Vertico. The advice is not necessary - it is a pure optimization. - (nconc (car (vertico--filter-completions pattern cands nil (length pattern) - `(metadata (category . ,category)))) - nil) - (funcall orig pattern cands category highlight))) - -(with-eval-after-load 'vertico - (advice-add #'consult--completion-filter :around #'consult--vertico-filter-adv) - (add-hook 'consult--completion-candidate-hook #'consult--vertico-candidate) - (add-hook 'consult--completion-refresh-hook #'consult--vertico-refresh) - (define-key consult-async-map [remap vertico-insert] 'vertico-next-group)) - -;;;;; Integration: Mct - -(with-eval-after-load 'mct (add-hook 'consult--completion-refresh-hook - 'mct--live-completions-refresh)) - -;;;;; Integration: Icomplete - -(defvar icomplete-mode) -(declare-function icomplete-exhibit "icomplete") - -(defun consult--icomplete-refresh () - "Refresh icomplete view." - (when icomplete-mode - (let ((top (car completion-all-sorted-completions))) - (completion--flush-all-sorted-completions) - ;; force flushing, otherwise narrowing is broken! - (setq completion-all-sorted-completions nil) - (when top - (let* ((completions (completion-all-sorted-completions)) - (last (last completions)) - (before)) ;; completions before top - ;; warning: completions is an improper list - (while (consp completions) - (if (equal (car completions) top) - (progn - (setcdr last (append (nreverse before) (cdr last))) - (setq completion-all-sorted-completions completions - completions nil)) - (push (car completions) before) - (setq completions (cdr completions))))))) - (icomplete-exhibit))) - -(with-eval-after-load 'icomplete - (add-hook 'consult--completion-refresh-hook #'consult--icomplete-refresh)) - -(provide 'consult) -;;; consult.el ends here blob - 8837cb6c7308cf396c8e8f3718d2cc873ccdbd1f (mode 644) blob + /dev/null Binary files elpa/consult-1.4/consult.info and /dev/null differ blob - 8f854d786568c1933d455c28482e23663ccb4bec (mode 644) blob + /dev/null --- elpa/consult-1.4/dir +++ /dev/null @@ -1,18 +0,0 @@ -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 -* Consult: (consult). Useful commands built on completing-read. blob - /dev/null blob + 3b2af73102bd78274045b7544b50ede3c1f23bed (mode 644) --- /dev/null +++ elpa/consult-1.5/CHANGELOG.org @@ -0,0 +1,417 @@ +#+title: consult.el - Changelog +#+author: Daniel Mendler +#+language: en + +* Version 1.5 (2024-04-19) + +- Bugfix ~consult-buffer~: Handle buffer renaming during minibuffer completion + gracefully, by attaching the actual buffer objects to the completion candidate + strings. +- Bugfix ~consult-register~: Ignore marker registers pointing to dead buffers. + +* Version 1.4 (2024-03-08) + +- Bugfix: File preview: Ensure that binary files are not previewed partially. + Otherwise ~pdf-view-mode~ may observe corrupted PDF files. +- ~consult--async-refresh-timer~: Optimize timer reuse and efficiency. This change + improves the performance of commands like ~consult-ripgrep~ for small values of + ~consult-async-refresh-delay~. +- ~consult-completion-in-region~: Remove ~:cycle-threshold~ and ~:completion-styles~ + customization options. + +* Version 1.3 (2024-02-23) + +- ~consult-bookmark-narrow~: More flexible grouping which supports multiple + bookmark handlers per group. +- Bugfix: Ensure that preview is always executed in a non-minibuffer window. +- Bugfix: File preview: Do not preview ~hexl-mode~ buffers. +- Bugfix: File preview: use ~error-message-string~ to access error string. +- Bugfix: Buffer preview: Retrieve original window correctly. +- Bugfix: Fix ~consult-global-mark~ for ~embark-export~. + +* Version 1.2 (2024-01-23) + +- =consult-buffer=: Bugfix. Ensure that null completion works properly. +- File preview: Add indication if previewed file got truncated. + +* Version 1.1 (2023-12-27) + +- Bugfixes: + + ~consult-xref~: Do not error for an empty location list. + + ~consult--read~: Catch null completion if require-match is non-nil. + + ~consult--multi~: Ensure that :new action is invoked on visible source. +- File preview: Check for long lines when previewing files partially. +- Use ~minibuffer-local-filename-syntax~ and ~read-file-name-completion-ignore-case~ + for directory prompt of the ~consult-grep~ and ~consult--find~ family of commands. +- Remove obsolete variables ~consult-preview-max-size~ and + ~consult-preview-raw-size~. + +* Version 1.0 (2023-12-01) + +- Bugfixes. +- Preview large files partially. Add new customization variables + =consult-preview-partial-chunk= and =consult-preview-partial-limit=. This new + feature is experimental. Please report any issues you observe. +- Obsoleted =consult-preview-max-size= and =consult-preview-raw-size=. +- =consult-buffer-other-tab=: New command. +- =consult-fd=: New command based on the fast =fd/fdfind= search utility. +- =consult-outline=: New prefix argument to specify initial narrowing level. +- =consult-org-heading=: Specify category =org-heading= such that Embark provides + appropriate Org heading actions. +- =consult-org-heading=: Add annotation. +- =consult-locate=: Split input into multiple words. +- Remove unreliable =consult--maybe-recenter=. +- Save input history even when using =embark-export= or when aborting from a + command via C-g. This change affects commands like =consult-line= and + =consult-grep=. +- Unify history of =consult-line=, =consult-keep-lines= and =consult-focus-lines=. + +* Version 0.35 (2023-07-02) + +- Bugfixes. +- =consult--read= now accepts programmable completion tables as argument, e.g., + =completion-table-dynamic= or =completion--file-name-table=. This allows you to + reuse existing completion tables to write completion commands enhanced with + Consult candidate preview. +- Replace =consult-preview-cursor= face with =cursor-highlight-mark=. +- Change calling convention of =consult-focus-lines= and =consult-keep-lines=. +- The regexps in =consult-buffer-filter= are matched case sensitively now. + Similarly, the =INCLUDE= and =EXCLUDE= arguments of =consult--buffer-query= are also + case sensitive. +- Do not preview remote files by default, see =consult-preview-excluded-files=. +- Use =consult--maybe-recenter= instead of =recenter= in =consult-after-jump-hook=. +- =consult-goto-line=: Support =line:column= input. + +* Version 0.34 (2023-04-21) + +- Bugfixes. +- =consult-org-heading=: Support tag inheritance. +- Use pure =consult--fast-abbreviate-file-name= function to abbreviate file names + in =consult-buffer= and =consult-recent-file=. This ensures that abbreviation does + not access the file system (or worse remote hosts via Tramp) and is always + fast. The downside is that some paths may not get abbreviated. +- Introduce buffer sources =consult--source-project-buffer-hidden= and + =consult--source-project-recent-file-hidden=. Set the buffer sources of + =consult-project= to =consult--source-project-buffer= and + =consult--source-project-recent-file= to ease customization. +- =consult-buffer=: Explicitly save =window-next-buffers= and =window-prev-buffers=. +- When previewing files literally (=consult-preview-raw-size=), set the multi byte + flag of the previewed buffer, such that UTF-8 buffers are not garbled. +- Do not create preview cursor overlay. Instead display the actual point by + ensuring that =cursor-in-non-selected-windows= is set. + +* Version 0.33 (2023-03-11) + +- BREAKING: The key convention has been updated. The old key convention is not + supported anymore. Keys must now be strings valid according to =key-valid-p=. + This changes affects the keys =consult-narrow-key=, =consult-widen-key=, + =consult-preview-key= and the =:preview-key= of sources and passed as keyword + argument to =consult--read=. See the example configurations in the manual. +- BREAKING: Remove the "." argument from =consult-grep-args= and + =consult-ripgrep-args=, since directories or files to search are appended by the + command line builder. Take this change into account, when you use a customized + version of those variables. +- =consult-grep=: Add support for grep and find over multiple files or directory. + If the prefix argument DIR is a single C-u, prompt for comma separated + directories or files to search recursively via =completing-read-multiple=. +- =consult-buffer= and =consult-isearch-history=: Align annotations dynamically + depending on candidate width, instead of computing the alignment beforehand. +- Add the full path as =help-echo= property to abbreviated directory paths and + project names. Enable =tooltip-mode= and hover with the mouse over the + abbreviated directory path to see the full path. +- =consult-grep/find/etc=: Print first line of stderr output if command failed. + +* Version 0.32 (2023-02-06) + +- Bugfixes +- Deprecate the old key convention. Keys must now be strings valid according to + =key-valid-p=. This changes affects the keys =consult-narrow-key=, + =consult-widen-key=, =consult-preview-key= and the =:preview-key= of sources and + passed as keyword argument to =consult--read=. See the example configurations in + the manual. +- Add =consult-info= command (#634, #727). +- =consult-buffer=: Always select the first candidate when narrowing (#714). +- =consult-locate-args=: Remove =--existing=, which is not supported by =plocate= on + Debian stable. +- =consult-ripgrep-args=: Add =--search-zip= option to automatically search through + compressed files. This will allow you to search Elisp files bundled with your + Emacs installation. Move to an Elisp library via =find-library=, then invoke + =consult-ripgrep=. +- Drop obsolete =consult-apropos=. Alternatives: =describe-symbol= in combination + with =embark-export=. See also =consult-info= and =consult-ripgrep= to search + through info manuals and Elisp source code. +- Drop obsolete =consult-multi-occur=. Alternative: Built-in =multi-occur=, + =multi-occur-in-matching-buffers= or =consult-line-multi=. +- Drop obsolete =consult-file-externally=. The command has been moved to Embark + under the name =embark-open-externally=. + +* Version 0.31 (2023-01-06) + +- Version bump to update the Compat package dependency (29.1.0.1) + +* Version 0.30 (2023-01-02) + +- Bugfixes +- Drop Selectrum support +- Deprecate =consult-file-externally= in favor of =embark-open-externally= +- Deprecate =consult-multi-occur=. The =multi-occur= command should be improved + upstream to take advantage of =completing-read-multiple=. Consult provides the + command =consult-line-multi= as an alternative. +- =consult-history=: Use input as initial completion input + +* Version 0.29 (2022-12-03) + +- Bugfixes +- =consult-line-multi= has been rewritten completely. The candidates are computed + on demand based on the input. This reduces startup speed greatly. The command + behaves like =consult-grep=, but operates on buffers instead of files. +- Add =consult--source-file-register=, and make the registers available in + =consult-buffer=. Registers are often used as quick access keys for files, e.g., + =(add-to-list 'register-alist '(?i file . "~/.emacs.d/init.el")))=. +- Remove obsolete =consult-line-point-placement= +- =consult-grep/find=: Always show directory in the prompt +- Add variable =consult-yank-rotate=, =consult-yank-from-kill-ring= rotates kill ring +- Emacs 29: =consult-register= supports =buffer= register type +- Emacs 29: Support =outline-search-function= +- Org 9.6: Support new =org-fold-core= API (both overlays and text-properties) +- Support abbreviated file names in =recentf-list=, see =recentf-filename-handler=. +- Deprecate =consult-apropos= + +* Version 0.20 (2022-10-16) + +- Bugfixes +- Allow =consult-*-args= to be a string, or a list of strings or expressions. +- Introduce face =consult-highlight-match= to highlight grep matches in the + completion buffer. +- Highlight full matches in =consult-line=, =consult-outline=, =consult-*grep= and + =consult-flymake=. +- Remove face =consult-preview-error=. +- Deprecate =consult-line-point-placement= in favor of more general + =consult-point-placement=, which is also used by the =consult-*grep= commands. +- =consult-imenu=: Support imenu-after-jump-hook and non-default + =imenu-default-goto-function= +- =consult-history=: Add support for history index variables, which are updated + after selection. +- Deprecate support for Selectrum in favor of Vertico. If you use Selectrum + consider switching to Vertico, Icomplete, Mct or default completion. + +* Version 0.19 (2022-09-09) + +- Bugfixes +- Allow =consult-flymake= to work across all buffers in a project +- Remove deprecated =consult-completing-read-multiple= +- =consult-grep/git-grep/ripgrep=: Add =--fixed-strings= support +- =consult-grep=: Respect =grep-find-ignored-directories/files= +- =consult-org-heading=: Add tags to completion candidates +- Add =consult-preview-excluded-files= +- =consult-themes=: Support regexps + +* Version 0.18 (2022-05-25) + +- Bugfixes +- Removed obsolete =consult-recent-file-filter= and =consult-preview-excluded-hooks= +- Deprecate =consult-completing-read-multiple=. See #567 for details. +- Add =consult--source-modified-buffer= + +* Version 0.17 (2022-04-22) + +- Bugfixes +- Drop Emacs 26 support. +- =consult-goto-line=: Use =goto-line-history= on Emacs 28. +- =consult-customize=: Evaluate settings at runtime. This change makes it possible + to use =thing-at-point= to overwrite the =:initial= and =:add-history= settings. +- Rename =consult--read-config= to =consult--customize-alist= and change the format. + The configuration is an alist. The car must be a command symbol. The cdr must + be a plist of keys and expressions, where the expressions evaluate to the + actual configuration values. +- Mode hooks in previewed file buffers are delayed. The buffer is only fully + initialized when leaving the minibuffer for recursive editing. +- Increase =consult-preview-raw-size=. +- Replace =consult-preview-excluded-hooks= by =consult-preview-allowed-hooks=. +- Add =consult-preview-variables= to bind variables for file preview. +- BREAKING API CHANGE of =consult--read=, =consult--prompt=, =consult--multi=: The + state function protocol changed. The function gets notified of more completion + state changes. See the docstring of =consult--with-preview= for details. +- BREAKING API CHANGE of =consult--read=: The lookup function protocol changed. + The function must now accept four or more arguments. +- Remove unused =consult-preview-map=. +- Remove unnecessary =consult-recent-file-filter=. Use =recentf-exclude= instead. +- =consult--multi= sources can have a =:new= function to create candidates. + When narrowed to a source, new candidates will be created by calling the + respective =:new= function. +- =consult--multi= returns =:match= information. =:match= can be nil, t, or new, + depending on if the candidate does not exist, exists or has been created. +- =consult-locate= treats the input literally to take advantage of the db index. + +* Version 0.16 (2022-03-08) + +- Bugfixes +- Deprecate =consult-project-root-function= in favor of =consult-project-function=. +- Preconfigure =consult-project-function= with a default function based + on project.el. +- Add =consult-project-buffer=, a variant of =consult-buffer= restricted to the + current project. +- Add =consult-register-prefix= option. +- Introduced a generic and extensible =consult-register= implementation. +- Lazy marker creation in =consult-line/outline= (performance improvements) + +* Version 0.15 (2022-01-31) + +- Bugfixes +- =consult-xref=: Prettify the group titles, use =xref--group-name-for-display= + if available. +- =consult-focus-lines=: Thanks to @jdtsmith, the command is much faster and + actually useable in large files. +- Added Mct integration, auto refreshing of asynchronous Consult commands. + +* Version 0.14 (2021-12-31) + +- Bugfixes +- Add =consult-recent-file-filter= +- Rename =consult--source-(project-)file= to =consult-source-(project-)recent-file= +- =consult-keep-lines= makes read-only buffers temporarily writable if confirmed + +* Version 0.13 (2021-11-12) + +- Bugfixes +- =consult-register=: Add support for file register values. +- Rename =consult-isearch= to =consult-isearch-history=. The command is a history + browsing command and not a replacement for Isearch. +- =consult-grep= support -[ABC] grep options +- Add =consult-grep-context= face + +* Version 0.12 (2021-10-11) + +- Bugfixes +- Removed obsolete =consult-project-imenu= and =consult-x-command= variables +- =consult-grep=: Use ~--null~ argument to support file names with colons + +* Version 0.11 (2021-08-18) + +- Bugfixes only + +* Version 0.10 (2021-08-11) + +- =consult-mark=, =consult-global-mark=: Add optional marker list argument +- =consult-completing-read-multiple=: New function +- Rename =consult-project-imenu= to =consult-imenu-multi= +- Add =consult-line-multi= to search multiple buffers +- Removed obsolete =consult-yank=, =consult-async-default-split=, =consult-config= +- =consult-ripgrep=: Use =--smart-case= +- =consult-grep/git-grep=: Use =--ignore-case= +- Deprecate =consult--command= in favor of =consult--config.= +- =consult-find=: Use regular expressions instead of globbing/wildcards by default. + Due to the changes to =consult-find= it is not possible anymore to configure + =fd= as backend for =consult-find=. A replacement is documented in the wiki. +- =consult-find/locate/man=: Add highlighting to the matching file/man page names. +- =consult-grep/git-grep/ripgrep/find/locate=: Add support for multiple unordered + patterns. Each of the input patterns must be matched. For example, + =consult-find= transforms the input "first second third" to "first -and second + -and third". +- =consult-grep/git-grep/ripgrep=: Compute the highlighting based on the input, + instead of relying on the ANSI-escaped output. This works better with multiple + patterns, but may occasionally produce false highlighting. +- Deprecate =consult-x-command= configuration variables in favor of =consult-x-args=. + The variables have been renamed since the configuration format changed. +- =consult-async-split-styles-alist=: Remove the =space= splitting style, since + it has been obsoleted by the support for multiple unordered patterns. + +* Version 0.9 (2021-06-22) + +- Add =consult-preview-excluded-hooks= +- =consult--read/consult--prompt=: Add =:inherit-input-method= argument +- Add debouncing support for preview + +* Version 0.8 (2021-05-30) + +- Async commands: Do not fix vertical height in Selectrum. +- =consult-imenu=: Deduplicate items (some imenu backends generate duplicates). +- =consult-org-heading=: Deduplicate items. +- =consult-buffer-filter=: Hide more buffers. +- =consult-line=: Matching line preview overlay only in the selected window. +- =consult-yank/completion-in-region=: Insertion preview only in selected window. +- =consult-yank=: Rename to =consult-yank-from-kill-ring= (Emacs 28 naming). +- =consult-yank= commands: =delete-selection-mode= support, added properties. +- =consult-preview-at-point=, =consult-preview-at-point-mode=: New command and + minor mode to preview candidate at point in =*Completions*= buffer. +- Add =consult-async-split-style= and =consult-async-split-styles-alist=. +- =consult-async-default-split=: Obsoleted in favor of =consult-async-split-style=. +- Deprecate =consult-config= in favor of new =consult-customize= macro. +- =consult-buffer=: Enable previews for files and bookmarks by default. +- =consult-buffer=/=consult--multi=: Add support for =:preview-key= per source. +- =consult-buffer=: Push visible buffers down in the buffer list. +- =consult-flycheck=: Moved to separate repository prior to ELPA submission. +- Submitted Consult to ELPA. + +* Version 0.7 (2021-04-29) + +- Bugfixes +- =consult-buffer=: Respect =confirm-nonexistent-file-or-buffer= +- =consult-widen-key=: Change default setting to twice the =consult-narrow-key= +- =consult-flycheck=: Sort errors first +- Added support for the Vertico completion system +- Consult adds disambiguation suffixes as suffix instead of as prefix now + for the commands =consult-line=, =consult-buffer=, etc. + This enables support for the =basic= completion style and TAB completion. +- =consult--read=: The =:title= function must accept two arguments now, + the candidate string and a flag. If the flag is nil, the function should + return the title of the candidate, otherwise the function should return the + transformed candidate. +- =consult-grep= and related commands: Strip the file name if grouping is used. +- =consult-find/grep=: Ensure that the commands work with Tramp +- =consult-outline=: Add narrowing +- Added =consult-org-heading= and =consult-org-agenda= +- =consult-line=: Highlight visual line during jump preview +- =consult-line=: Start search at current line, add configuration variable + =consult-start-from-top=. The starting point can be toggled by the prefix + argument =C-u=. + +* Version 0.6 (2021-03-02) + +- Bugfixes +- =consult-keep/focus-lines=: Align behavior on regions with built-in =keep-lines=. +- =consult-buffer=: Enable file sources only when =recentf-mode= is enabled +- =consult--multi=: Add =:default= flag, use flag for =consult--source-buffer= +- Add =consult-grep-max-columns= to prevent performance issues for long lines +- Add =consult-fontify-preserve= customization variable +- =consult-line=: Quits Isearch, when started from an Isearch session +- =consult-register-load=: Align prefix argument handling with =insert-register= +- Rename =consult-error= to =consult-compile-error= +- =consult-compile-error=: Allow calling the command from any buffer, + use the errors from all compilation buffers related to the current buffer. +- =consult-man=: Handle aggregated entries returned by mandoc +- =consult-completion-in-region=: Added preview and =consult-preview-region= face +- Added =consult-completion-in-region-styles= customization variable +- Added =consult-xref=. The function can be set as =xref-show-xrefs-function= + and =xref-show-definitions-function=. +- Added support for the candidate grouping function =x-group-function= + +* Version 0.5 (2021-02-09) + +- Bugfixes +- =consult-keep/focus-lines=: If region is active, operate only on the region. +- =consult-register-format=: Do not truncate register strings. +- =consult-buffer= multi sources: Ensure that original buffer is + shown, when the currently selected source does not perform preview. +- Add =consult-preview-raw-size= +- Expose preview functionality for multi-source bookmarks/files +- Multi sources: Add =:enabled=, =:state= and =:action= fields +- =consult-imenu=: Add faces depending on item types + +* Version 0.4 (2021-02-01) + +- Bugfixes +- Introduce multi sources, reimplement =consult-buffer= with multi sources +- =consult-isearch=: Add preview highlighting +- =consult-line=: Use =isearch-string= when invoked from running isearch + +* Version 0.3 (2021-01-28) + +- Bugfixes +- New command =consult-isearch= +- New functions =consult-register-format=, =consult-register-window=, + removed =consult-register-preview= + +* Version 0.2 (2021-01-16) + +- Initial stable release blob - /dev/null blob + 086f993074dc1f9bc5f978b28586a1bd203cf3e6 (mode 644) --- /dev/null +++ elpa/consult-1.5/README-elpa @@ -0,0 +1,1332 @@ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + CONSULT.EL - CONSULTING COMPLETING-READ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + +Consult provides search and navigation commands based on the Emacs +completion function [completing-read]. Completion allows you to quickly +select an item from a list of candidates. Consult offers asynchronous +and interactive `consult-grep' and `consult-ripgrep' commands, and the +line-based search command `consult-line'. Furthermore Consult provides +an advanced buffer switching command `consult-buffer' to switch between +buffers, recently opened files, bookmarks and buffer-like candidates +from other sources. Some of the Consult commands are enhanced versions +of built-in Emacs commands. For example the command `consult-imenu' +presents a flat list of the Imenu with [live preview], [grouping and +narrowing]. Please take a look at the [full list of commands]. + +Consult is fully compatible with completion systems centered around the +standard Emacs `completing-read' API, notably the default completion +system, [Vertico], [Mct], and [Icomplete]. + +This package keeps the completion system specifics to a minimum. The +ability of the Consult commands to work well with arbitrary completion +systems is one of the main advantages of the package. Consult fits well +into existing setups and it helps you to create a full completion +environment out of small and independent components. + +You can combine the complementary packages [Marginalia], [Embark] and +[Orderless] with Consult. Marginalia enriches the completion display +with annotations, e.g., documentation strings or file information. The +versatile Embark package provides local actions, comparable to a context +menu. These actions operate on the selected candidate in the minibuffer +or at point in normal buffers. For example, when selecting from a list +of files, Embark offers an action to delete the file. Additionally +Embark offers a facility to collect completion candidates in a collect +buffer. The section [Embark integration] documents in detail how Consult +and Embark work together. + +Table of Contents +───────────────── + +1. Available commands +.. 1. Virtual Buffers +.. 2. Editing +.. 3. Register +.. 4. Navigation +.. 5. Search +.. 6. Grep and Find +.. 7. Compilation +.. 8. Histories +.. 9. Modes +.. 10. Org Mode +.. 11. Help +.. 12. Miscellaneous +2. Special features +.. 1. Live previews +.. 2. Narrowing and grouping +.. 3. Asynchronous search +.. 4. Multiple sources +.. 5. Embark integration +3. Configuration +.. 1. Use-package example +.. 2. Custom variables +.. 3. Fine-tuning +4. Recommended packages +5. Bug reports +6. Contributions +7. Acknowledgments +8. Indices +.. 1. Function index +.. 2. Concept index + + +[completing-read] + + +[live preview] See section 2.1 + +[grouping and narrowing] See section 2.2 + +[full list of commands] See section 1 + +[Vertico] + +[Mct] + +[Icomplete] + + +[Marginalia] + +[Embark] + +[Orderless] + +[Embark integration] See section 2.5 + + +1 Available commands +════════════════════ + + Most Consult commands follow the meaningful naming scheme + `consult-'. Many commands implement a little known but + convenient Emacs feature called "future history", which guesses what + input the user wants. At a command prompt type `M-n' and typically + Consult will insert the symbol or thing at point into the input. + + *TIP:* If you have [Marginalia] annotators activated, type `M-x + ^consult' to see all Consult commands with their abbreviated + description. Alternatively, type `C-h a ^consult' to get an overview + of all Consult variables and functions with their descriptions. + + +[Marginalia] + +1.1 Virtual Buffers +─────────────────── + + • `consult-buffer': Enhanced version of `switch-to-buffer' with + support for virtual buffers. Supports live preview of buffers and + narrowing to the virtual buffer types. You can type `f SPC' in order + to narrow to recent files. Press `SPC' to show ephemeral + buffers. Supported narrowing keys: + • b Buffers + • SPC Hidden buffers + • * Modified buffers + • f Files (Requires `recentf-mode') + • r File registers + • m Bookmarks + • p Project + • Custom [other sources] configured in `consult-buffer-sources'. + • `consult-buffer-other-window', `consult-buffer-other-frame', + `consult-buffer-other-tab': Variants of `consult-buffer'. + • `consult-project-buffer': Variant of `consult-buffer' restricted to + buffers and recent files of the current project. You can add custom + sources to `consult-project-buffer-sources'. The command may prompt + you for a project if you invoke it from outside a project. + • `consult-bookmark': Select or create bookmark. To select bookmarks + you might use the `consult-buffer' as an alternative, which can + include a bookmark virtual buffer source. Note that + `consult-bookmark' supports preview of bookmarks and narrowing. + • `consult-recent-file': Select from recent files with preview. You + might prefer the powerful `consult-buffer' instead, which can + include recent files as a virtual buffer source. The `recentf-mode' + enables tracking of recent files. + + +[other sources] See section 2.4 + + +1.2 Editing +─────────── + + • `consult-yank-from-kill-ring': Enhanced version of `yank' to select + an item from the `kill-ring'. The selected text previewed as overlay + in the buffer. + • `consult-yank-pop': Enhanced version of `yank-pop' with + DWIM-behavior, which either replaces the last `yank' by cycling + through the `kill-ring', or if there has not been a last `yank' + consults the `kill-ring'. The selected text previewed as overlay in + the buffer. + • `consult-yank-replace': Like `consult-yank-pop', but always replaces + the last `yank' with an item from the `kill-ring'. + • `consult-kmacro': Select macro from the macro ring and execute it. + + +1.3 Register +──────────── + + • `consult-register': Select from list of registers. The command + supports narrowing to register types and preview of marker + positions. This command is useful to search the register + contents. For quick access use the commands `consult-register-load', + `consult-register-store' or the built-in Emacs register commands. + • `consult-register-format': Set `register-preview-function' to this + function for an enhanced register formatting. See the [example + configuration]. + • `consult-register-window': Replace `register-preview' with this + function for a better register window. See the [example + configuration]. + • `consult-register-load': Utility command to quickly load a register. + The command either jumps to the register value or inserts it. + • `consult-register-store': Improved UI to store registers depending + on the current context with an action menu. With an active region, + store/append/prepend the contents, optionally deleting the region + when a prefix argument is given. With a numeric prefix argument, + store/add the number. Otherwise store point, frameset, window or + kmacro. Usage examples: + ‣ `M-' x': If no region is active, store point in register `x'. If + a region is active, store the region in register `x'. + ‣ `M-' M-w x': Store window configuration in register `x'. + ‣ `C-u 100 M-' x': Store number in register `x'. + + +[example configuration] See section 3.1 + + +1.4 Navigation +────────────── + + • `consult-goto-line': Jump to line number enhanced with live + preview. This is a drop-in replacement for `goto-line'. Enter a line + number to jump to the first column of the given line. Alternatively + enter `line:column' in order to jump to a specific column. + • `consult-mark': Jump to a marker in the `mark-ring'. Supports live + preview and recursive editing. + • `consult-global-mark': Jump to a marker in the `global-mark-ring'. + Supports live preview and recursive editing. + • `consult-outline': Jump to a heading of the outline. Supports + narrowing to a heading level, live preview and recursive editing. + • `consult-imenu': Jump to imenu item in the current buffer. Supports + live preview, recursive editing and narrowing. + • `consult-imenu-multi': Jump to imenu item in project buffers, with + the same major mode as the current buffer. Supports live preview, + recursive editing and narrowing. This feature has been inspired by + [imenu-anywhere]. + + +[imenu-anywhere] + + +1.5 Search +────────── + + • `consult-line': Enter search string and select from matching lines. + Supports live preview and recursive editing. The symbol at point and + the recent Isearch string are added to the "future history" and can + be accessed by pressing `M-n'. When `consult-line' is bound to the + `isearch-mode-map' and is invoked during a running Isearch, it will + use the current Isearch string. + • `consult-line-multi': Search dynamically across multiple buffers. By + default search across project buffers. If invoked with a prefix + argument search across all buffers. The candidates are computed on + demand based on the input. The command behaves like `consult-grep', + but operates on buffers instead of files. + • `consult-keep-lines': Replacement for `keep/flush-lines' which uses + the current completion style for filtering the buffer. The function + updates the buffer while typing. In particular `consult-keep-lines' + can narrow down an exported Embark collect buffer further, relying + on the same completion filtering as `completing-read'. If the input + begins with the negation operator, i.e., `! SPC', the filter matches + the complement. If a region is active, the region restricts the + filtering. + • `consult-focus-lines': Temporarily hide lines by filtering them + using the current completion style. Call with `C-u' prefix argument + in order to show the hidden lines again. If the input begins with + the negation operator, i.e., `! SPC', the filter matches the + complement. In contrast to `consult-keep-lines' this function does + not edit the buffer. If a region is active, the region restricts the + filtering. + + +1.6 Grep and Find +───────────────── + + • `consult-grep', `consult-ripgrep', `consult-git-grep': Search for + regular expression in files. Consult invokes Grep asynchronously, + while you enter the search term. After at least + `consult-async-min-input' characters, the search gets + started. Consult splits the input string into two parts, if the + first character is a punctuation character, like `#'. For example + `#regexps#filter-string', is split at the second `#'. The string + `regexps' is passed to Grep. Note that Consult transforms Emacs + regular expressions to expressions understand by the search + program. Always use Emacs regular expressions at the prompt. If you + enter multiple regular expressions separated by space only lines + matching all regular expressions are shown. In order to match space + literally, escape the space with a backslash. The `filter-string' is + passed to the /fast/ Emacs filtering to further narrow down the list + of matches. This is particularly useful if you are using an advanced + completion style like orderless. `consult-grep' supports preview. If + the `consult-project-function' returns non-nil, `consult-grep' + searches the current project directory. Otherwise the + `default-directory' is searched. If `consult-grep' is invoked with + prefix argument `C-u M-s g', you can specify one or more + comma-separated files and directories manually. + • `consult-find', `consult-fd', `consult-locate': Find file by + matching the path against a regexp. Like for `consult-grep', either + the project root or the current directory is the root directory for + the search. The input string is treated similarly to `consult-grep', + where the first part is passed to find, and the second part is used + for Emacs filtering. Prefix arguments to `consult-find' work just + like those for the consult grep commands. + + +1.7 Compilation +─────────────── + + • `consult-compile-error': Jump to a compilation error. Supports live + preview narrowing and recursive editing. + • `consult-flymake': Jump to Flymake diagnostic. Supports live preview + and recursive editing. The command supports narrowing. Press `e + SPC', `w SPC', `n SPC' to only show errors, warnings and notes + respectively. + • `consult-xref': Integration with xref. This function can be set as + `xref-show-xrefs-function' and `xref-show-definitions-function'. + + +1.8 Histories +───────────── + + • `consult-complex-command': Select a command from the + `command-history'. This command is a `completing-read' version of + `repeat-complex-command' and is also a replacement for the + `command-history' command from chistory.el. + • `consult-history': Insert a string from the current buffer history, + for example the Eshell or Comint history. You can also invoke this + command from the minibuffer. In that case `consult-history' uses the + history stored in the `minibuffer-history-variable'. If you prefer + `completion-at-point', take a look at `cape-history' from the [Cape] + package. + • `consult-isearch-history': During an Isearch session, this command + picks a search string from history and continues the search with the + newly selected string. Outside of Isearch, the command allows you to + pick a string from the history and starts a new + Isearch. `consult-isearch-history' acts as a drop-in replacement for + `isearch-edit-string'. + + +[Cape] + + +1.9 Modes +───────── + + • `consult-minor-mode-menu': Enable/disable minor mode. Supports + narrowing to on/off/local/global modes by pressing `i/o/l/g SPC' + respectively. + • `consult-mode-command': Run a command from the currently active + minor or major modes. Supports narrowing to + local-minor/global-minor/major mode via the keys `l/g/m'. + + +1.10 Org Mode +───────────── + + • `consult-org-heading': Variant of `consult-imenu' or + `consult-outline' for Org buffers. The headline and its ancestors + headlines are separated by slashes. Supports narrowing by heading + level, priority and TODO keyword, as well as live preview and + recursive editing. + • `consult-org-agenda': Jump to an Org agenda heading. Supports + narrowing by heading level, priority and TODO keyword, as well as + live preview and recursive editing. + + +1.11 Help +───────── + + • `consult-man': Find Unix man page, via Unix `apropos' or `man + -k'. `consult-man' opens the selected man page using the Emacs `man' + command. + • `consult-info': Full text search through info pages. If the command + is invoked from within an `*info*' buffer, it will search through + the current manual. You may want to create your own commands which + search through a predefined set of info pages, for example: + ┌──── + │ (defun consult-info-emacs () + │ "Search through Emacs info pages." + │ (interactive) + │ (consult-info "emacs" "efaq" "elisp" "cl" "compat")) + │ + │ (defun consult-info-org () + │ "Search through the Org info page." + │ (interactive) + │ (consult-info "org")) + │ + │ (defun consult-info-completion () + │ "Search through completion info pages." + │ (interactive) + │ (consult-info "vertico" "consult" "marginalia" "orderless" "embark" + │ "corfu" "cape" "tempel")) + └──── + + +1.12 Miscellaneous +────────────────── + + • `consult-theme': Select a theme and disable all currently enabled + themes. Supports live preview of the theme while scrolling through + the candidates. + • `consult-preview-at-point' and `consult-preview-at-point-mode': + Command and minor mode which previews the candidate at point in the + `*Completions*' buffer. This mode is relevant if you use [Mct] or + the default `*Completions*' UI. + • `consult-completion-in-region': In case you don't use [Corfu] as + your in-buffer completion UI, this function can be set as + `completion-in-region-function'. Then your minibuffer completion UI + (e.g., Vertico or Icomplete) will be used for `completion-at-point'. + ┌──── + │ ;; Use `consult-completion-in-region' if Vertico is enabled. + │ ;; Otherwise use the default `completion--in-region' function. + │ (setq completion-in-region-function + │ (lambda (&rest args) + │ (apply (if vertico-mode + │ #'consult-completion-in-region + │ #'completion--in-region) + │ args))) + └──── + Instead of `consult-completion-in-region', you may prefer to see the + completions directly in the buffer as a small popup. In that case, I + recommend the [Corfu] package. There is a technical limitation of + `consult-completion-in-region' in combination with the Lsp + modes. The Lsp server relies on the input at point, in order to + generate refined candidate strings. Since the completion is + transferred from the original buffer to the minibuffer, the server + does not receive the updated input. In contrast, in-buffer Lsp + completion for example via Corfu works properly since the completion + takes place directly in the original buffer. + + +[Mct] + +[Corfu] + + +2 Special features +══════════════════ + + Consult enhances `completing-read' with live previews of candidates, + additional narrowing capabilities to candidate groups and + asynchronously generated candidate lists. The internal `consult--read' + function, which is used by most Consult commands, is a thin wrapper + around `completing-read' and provides the special functionality. In + order to support multiple candidate sources there exists the + high-level function `consult--multi'. The architecture of Consult + allows it to work with different completion systems in the backend, + while still offering advanced features. + + +2.1 Live previews +───────────────── + + Some Consult commands support live previews. For example when you + scroll through the items of `consult-line', the buffer will scroll to + the corresponding position. It is possible to jump back and forth + between the minibuffer and the buffer to perform recursive editing + while the search is ongoing. + + Consult enables previews by default. You can disable them by adjusting + the `consult-preview-key' variable. Furthermore it is possible to + specify keybindings which trigger the preview manually as shown in the + [example configuration]. The default setting of `consult-preview-key' + is `any' which means that Consult triggers the preview /immediately/ + on any key press when the selected candidate changes. You can + configure each command individually with its own `:preview-key'. The + following settings are possible: + + • Automatic and immediate `'any' + • Automatic and delayed `(list :debounce 0.5 'any)' + • Manual and immediate `"M-."' + • Manual and delayed `(list :debounce 0.5 "M-.")' + • Disabled `nil' + + A safe recommendation is to leave automatic immediate previews enabled + in general and disable the automatic preview only for commands where + the preview may be expensive due to file loading. Internally, Consult + uses the value of `this-command' to determine the `:preview-key' + customized. This means that if you wrap a `consult-*' command within + your own function or command, you will also need to add the name of + /your custom command/ to the `consult-customize' call in order for it + to be considered. + + ┌──── + │ (consult-customize + │ consult-ripgrep consult-git-grep consult-grep + │ consult-bookmark consult-recent-file consult-xref + │ consult--source-bookmark consult--source-file-register + │ consult--source-recent-file consult--source-project-recent-file + │ ;; my/command-wrapping-consult ;; disable auto previews inside my command + │ :preview-key '(:debounce 0.4 any) ;; Option 1: Delay preview + │ ;; :preview-key "M-.") ;; Option 2: Manual preview + └──── + + In this case one may wonder what the difference is between using an + Embark action on the current candidate in comparison to a manually + triggered preview. The main difference is that the files opened by + manual preview are closed again after the completion session. During + preview some functionality is disabled to improve the performance, see + for example the customization variables `consult-preview-variables' + and `consult-preview-allowed-hooks'. Only the hooks listed in + `consult-preview-allowed-hooks' are executed when a file is opened + (`find-file-hook'). In order to enable additional font locking during + preview, add the corresponding hooks to the allow list. The following + code demonstrates this for [org-modern] and [hl-todo]. + + ┌──── + │ (add-to-list 'consult-preview-allowed-hooks 'global-org-modern-mode-check-buffers) + │ (add-to-list 'consult-preview-allowed-hooks 'global-hl-todo-mode-check-buffers) + └──── + + Files larger than `consult-preview-partial-size' are previewed + partially. Delaying the preview is also useful for `consult-theme', + since the theme preview is slow. The delay results in a smoother UI + experience. + + ┌──── + │ ;; Preview on any key press, but delay 0.5s + │ (consult-customize consult-theme :preview-key '(:debounce 0.5 any)) + │ ;; Preview immediately on M-., on up/down after 0.5s, on any other key after 1s + │ (consult-customize consult-theme + │ :preview-key + │ '("M-." + │ :debounce 0.5 "" "" + │ :debounce 1 any)) + └──── + + +[example configuration] See section 3.1 + +[org-modern] + +[hl-todo] + + +2.2 Narrowing and grouping +────────────────────────── + + Consult has special support for candidate groups. If the completion UI + supports the grouping functionality, the UI separates the groups with + thin lines and shows group titles. Grouping is useful if the list of + candidates consists of candidates of multiple types or candidates from + [multiple sources], like the `consult-buffer' command, which shows + both buffers and recently opened files. Note that you can disable the + group titles by setting the `:group' property of the corresponding + command to nil using the `consult-customize' macro. + + By entering a narrowing prefix or by pressing a narrowing key it is + possible to restrict the completion candidates to a certain candidate + group. When you use the `consult-buffer' command, you can enter the + prefix `b SPC' to restrict list of candidates to buffers only. If you + press `DEL' afterwards, the full candidate list will be shown + again. Furthermore a narrowing prefix key and a widening key can be + configured which can be pressed to achieve the same effect, see the + configuration variables `consult-narrow-key' and `consult-widen-key'. + + After pressing `consult-narrow-key', the possible narrowing keys can + be shown by pressing `C-h'. When pressing `C-h' after some prefix key, + the `prefix-help-command' is invoked, which shows the keybinding help + window by default. As a more compact alternative, there is the + `consult-narrow-help' command which can be bound to a key, for example + `?' or `C-h' in the `consult-narrow-map', as shown in the [example + configuration]. If [which-key] is installed, the narrowing keys are + automatically shown in the which-key window after pressing the + `consult-narrow-key'. + + +[multiple sources] See section 2.4 + +[example configuration] See section 3.1 + +[which-key] + + +2.3 Asynchronous search +─────────────────────── + + Consult has support for asynchronous generation of candidate + lists. This feature is used for search commands like `consult-grep', + where the list of matches is generated dynamically while the user is + typing a regular expression. The grep process is executed in the + background. When modifying the regular expression, the background + process is terminated and a new process is started with the modified + regular expression. + + The matches, which have been found, can then be narrowed using the + installed Emacs completion-style. This can be powerful if you are + using for example the `orderless' completion style. + + This two-level filtering is possible by splitting the input + string. Part of the input string is treated as input to grep and part + of the input is used for filtering. There are multiple splitting + styles available, configured in `consult-async-split-styles-alist': + `nil', `comma', `semicolon' and `perl'. The default splitting style is + configured with the variable `consult-async-split-style'. + + With the `comma' and `semicolon' splitting styles, the first word + before the comma or semicolon is passed to grep, the remaining string + is used for filtering. The `nil' splitting style does not perform any + splitting, the whole input is passed to grep. + + The `perl' splitting style splits the input string at a punctuation + character, using a similar syntax as Perl regular expressions. + + Examples: + + • `#defun': Search for "defun" using grep. + • `#consult embark': Search for both "consult" and "embark" using grep + in any order. + • `#first.*second': Search for "first" followed by "second" using + grep. + • `#\(consult\|embark\)': Search for "consult" or "embark" using + grep. Note the usage of Emacs-style regular expressions. + • `#defun#consult': Search for "defun" using grep, filter with the + word "consult". + • `/defun/consult': It is also possible to use other punctuation + characters. + • `#to#': Force searching for "to" using grep, since the grep pattern + must be longer than `consult-async-min-input' characters by default. + • `#defun -- --invert-match#': Pass argument `--invert-match' to grep. + + Asynchronous processes like `find' and `grep' create an error log + buffer `_*consult-async*' (note the leading space), which is useful + for troubleshooting. The prompt has a small indicator showing the + process status: + + • `:' the usual prompt colon, before input is provided. + • `*' with warning face, the process is running. + • `:' with success face, success, process exited with an error code of + zero. + • `!' with error face, failure, process exited with a nonzero error + code. + • `;' with error face, interrupted, for example if more input is + provided. + + +2.4 Multiple sources +──────────────────── + + Multiple synchronous candidate sources can be combined. This feature + is used by the `consult-buffer' command to present buffer-like + candidates in a single menu for quick access. By default + `consult-buffer' includes buffers, bookmarks, recent files and + project-specific buffers and files. It is possible to configure the + list of sources via the `consult-buffer-sources' variable. Arbitrary + custom sources can be defined. + + As an example, the bookmark source is defined as follows: + + ┌──── + │ (defvar consult--source-bookmark + │ `(:name "Bookmark" + │ :narrow ?m + │ :category bookmark + │ :face consult-bookmark + │ :history bookmark-history + │ :items ,#'bookmark-all-names + │ :action ,#'consult--bookmark-action)) + └──── + + Required source fields: + • `:category' Completion category. + • `:items' List of strings to select from or function returning list + of strings. A list of cons cells is not supported. + + Optional source fields: + • `:name' Name of the source, used for narrowing, group titles and + annotations. + • `:narrow' Narrowing character or `(character . string)' pair. + • `:preview-key' Preview key or keys which trigger preview. + • `:enabled' Function which must return t if the source is enabled. + • `:hidden' When t candidates of this source are hidden by default. + • `:face' Face used for highlighting the candidates. + • `:annotate' Annotation function called for each candidate, returns + string. + • `:history' Name of history variable to add selected candidate. + • `:default' Must be t if the first item of the source is the default + value. + • `:action' Function called with the selected candidate. + • `:new' Function called with new candidate name, only if + `:require-match' is nil. + • `:state' State constructor for the source, must return the state + function. + • Other source fields can be added specifically to the use case. + + The `:state' and `:action' fields of the sources deserve a longer + explanation. The `:action' function takes a single argument and is + only called after selection with the selected candidate, if the + selection has not been aborted. This functionality is provided for + convenience and easy definition of sources. The `:state' field is more + general. The `:state' function is a constructor function without + arguments, which can perform some setup necessary for the preview. It + must return a closure which takes an ACTION and a CANDIDATE + argument. See the docstring of `consult--with-preview' for more + details about the ACTION argument. + + By default, `consult-buffer' previews buffers, bookmarks and + files. Loading recent files or bookmarks can result in expensive + operations. However it is possible to configure a manual preview as + follows. + + ┌──── + │ (consult-customize + │ consult--source-bookmark consult--source-file-register + │ consult--source-recent-file consult--source-project-recent-file + │ :preview-key "M-.") + └──── + + Sources can be added directly to the `consult-buffer-source' list for + convenience. For example, the following source lists all Org buffers + and lets you create new ones. + + ┌──── + │ (defvar org-source + │ (list :name "Org Buffer" + │ :category 'buffer + │ :narrow ?o + │ :face 'consult-buffer + │ :history 'buffer-name-history + │ :state #'consult--buffer-state + │ :new + │ (lambda (name) + │ (with-current-buffer (get-buffer-create name) + │ (insert "#+title: " name "\n\n") + │ (org-mode) + │ (consult--buffer-action (current-buffer)))) + │ :items + │ (lambda () + │ (consult--buffer-query :mode 'org-mode :as #'consult--buffer-pair)))) + │ + │ (add-to-list 'consult-buffer-sources 'org-source 'append) + └──── + + One can create similar sources for other major modes. See the [Consult + wiki] for many additional source examples. See also the documentation + of `consult-buffer' and of the internal `consult--multi' API. The + function `consult--multi' can be used to create new multi-source + commands. + + +[Consult wiki] + + +2.5 Embark integration +────────────────────── + + *NOTE*: Install the `embark-consult' package from MELPA, which + provides Consult-specific Embark actions and the Occur buffer export. + + Embark is a versatile package which offers context dependent actions, + comparable to a context menu. See the [Embark manual] for an extensive + description of its capabilities. + + Actions are commands which can operate on the currently selected + candidate (or target in Embark terminology). When completing files, + for example the `delete-file' command is offered. With Embark you can + execute arbitrary commands on the currently selected candidate via + `M-x'. + + Furthermore Embark provides the `embark-collect' command, which + collects candidates and presents them in an Embark collect buffer, + where further actions can be applied to them. A related feature is the + `embark-export' command, which exports candidate lists to a buffer of + a special type. For example in the case of file completion, a Dired + buffer is opened. + + In the context of Consult, particularly exciting is the possibility to + export the matching lines from `consult-line', `consult-outline', + `consult-mark' and `consult-global-mark'. The matching lines are + exported to an Occur buffer where they can be edited via the + `occur-edit-mode' (press key `e'). Similarly, Embark supports + exporting the matches found by `consult-grep', `consult-ripgrep' and + `consult-git-grep' to a Grep buffer, where the matches across files + can be edited, if the [wgrep] package is installed. These three + workflows are symmetric. + + ⁃ `consult-line' -> `embark-export' to `occur-mode' buffer -> + `occur-edit-mode' for editing of matches in buffer. + ⁃ `consult-grep' -> `embark-export' to `grep-mode' buffer -> `wgrep' + for editing of all matches. + ⁃ `consult-find' -> `embark-export' to `dired-mode' buffer -> + `wdired-change-to-wdired-mode' for editing. + + +[Embark manual] + +[wgrep] + + +3 Configuration +═══════════════ + + Consult can be installed from [ELPA] or [MELPA] via the Emacs built-in + package manager. Alternatively it can be directly installed from the + development repository via other non-standard package managers. + + There is the [Consult wiki], where additional configuration examples + can be contributed. + + *IMPORTANT:* It is recommended that you enable [lexical binding] in + your configuration. Many Consult-related code snippets require lexical + binding, since they use lambdas and closures. + + +[ELPA] + +[MELPA] + +[Consult wiki] + +[lexical binding] + + +3.1 Use-package example +─────────────────────── + + The Consult package only provides commands and does not add any + keybindings or modes. Therefore the package is non-intrusive but + requires a little setup effort. In order to use the Consult commands, + it is advised to add keybindings for commands which are accessed + often. Rarely used commands can be invoked via `M-x'. Feel free to + only bind the commands you consider useful to your workflow. The + configuration shown here relies on the `use-package' macro, which is a + convenient tool to manage package configurations. + + *NOTE:* There is the [Consult wiki], where you can contribute + additional configuration examples. + + ┌──── + │ ;; Example configuration for Consult + │ (use-package consult + │ ;; Replace bindings. Lazily loaded due by `use-package'. + │ :bind (;; C-c bindings in `mode-specific-map' + │ ("C-c M-x" . consult-mode-command) + │ ("C-c h" . consult-history) + │ ("C-c k" . consult-kmacro) + │ ("C-c m" . consult-man) + │ ("C-c i" . consult-info) + │ ([remap Info-search] . consult-info) + │ ;; C-x bindings in `ctl-x-map' + │ ("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command + │ ("C-x b" . consult-buffer) ;; orig. switch-to-buffer + │ ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window + │ ("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame + │ ("C-x t b" . consult-buffer-other-tab) ;; orig. switch-to-buffer-other-tab + │ ("C-x r b" . consult-bookmark) ;; orig. bookmark-jump + │ ("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer + │ ;; Custom M-# bindings for fast register access + │ ("M-#" . consult-register-load) + │ ("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated) + │ ("C-M-#" . consult-register) + │ ;; Other custom bindings + │ ("M-y" . consult-yank-pop) ;; orig. yank-pop + │ ;; M-g bindings in `goto-map' + │ ("M-g e" . consult-compile-error) + │ ("M-g f" . consult-flymake) ;; Alternative: consult-flycheck + │ ("M-g g" . consult-goto-line) ;; orig. goto-line + │ ("M-g M-g" . consult-goto-line) ;; orig. goto-line + │ ("M-g o" . consult-outline) ;; Alternative: consult-org-heading + │ ("M-g m" . consult-mark) + │ ("M-g k" . consult-global-mark) + │ ("M-g i" . consult-imenu) + │ ("M-g I" . consult-imenu-multi) + │ ;; M-s bindings in `search-map' + │ ("M-s d" . consult-find) ;; Alternative: consult-fd + │ ("M-s c" . consult-locate) + │ ("M-s g" . consult-grep) + │ ("M-s G" . consult-git-grep) + │ ("M-s r" . consult-ripgrep) + │ ("M-s l" . consult-line) + │ ("M-s L" . consult-line-multi) + │ ("M-s k" . consult-keep-lines) + │ ("M-s u" . consult-focus-lines) + │ ;; Isearch integration + │ ("M-s e" . consult-isearch-history) + │ :map isearch-mode-map + │ ("M-e" . consult-isearch-history) ;; orig. isearch-edit-string + │ ("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string + │ ("M-s l" . consult-line) ;; needed by consult-line to detect isearch + │ ("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch + │ ;; Minibuffer history + │ :map minibuffer-local-map + │ ("M-s" . consult-history) ;; orig. next-matching-history-element + │ ("M-r" . consult-history)) ;; orig. previous-matching-history-element + │ + │ ;; Enable automatic preview at point in the *Completions* buffer. This is + │ ;; relevant when you use the default completion UI. + │ :hook (completion-list-mode . consult-preview-at-point-mode) + │ + │ ;; The :init configuration is always executed (Not lazy) + │ :init + │ + │ ;; Optionally configure the register formatting. This improves the register + │ ;; preview for `consult-register', `consult-register-load', + │ ;; `consult-register-store' and the Emacs built-ins. + │ (setq register-preview-delay 0.5 + │ register-preview-function #'consult-register-format) + │ + │ ;; Optionally tweak the register preview window. + │ ;; This adds thin lines, sorting and hides the mode line of the window. + │ (advice-add #'register-preview :override #'consult-register-window) + │ + │ ;; Use Consult to select xref locations with preview + │ (setq xref-show-xrefs-function #'consult-xref + │ xref-show-definitions-function #'consult-xref) + │ + │ ;; Configure other variables and modes in the :config section, + │ ;; after lazily loading the package. + │ :config + │ + │ ;; Optionally configure preview. The default value + │ ;; is 'any, such that any key triggers the preview. + │ ;; (setq consult-preview-key 'any) + │ ;; (setq consult-preview-key "M-.") + │ ;; (setq consult-preview-key '("S-" "S-")) + │ ;; For some commands and buffer sources it is useful to configure the + │ ;; :preview-key on a per-command basis using the `consult-customize' macro. + │ (consult-customize + │ consult-theme :preview-key '(:debounce 0.2 any) + │ consult-ripgrep consult-git-grep consult-grep + │ consult-bookmark consult-recent-file consult-xref + │ consult--source-bookmark consult--source-file-register + │ consult--source-recent-file consult--source-project-recent-file + │ ;; :preview-key "M-." + │ :preview-key '(:debounce 0.4 any)) + │ + │ ;; Optionally configure the narrowing key. + │ ;; Both < and C-+ work reasonably well. + │ (setq consult-narrow-key "<") ;; "C-+" + │ + │ ;; Optionally make narrowing help available in the minibuffer. + │ ;; You may want to use `embark-prefix-help-command' or which-key instead. + │ ;; (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help) + │ + │ ;; By default `consult-project-function' uses `project-root' from project.el. + │ ;; Optionally configure a different project root function. + │ ;;;; 1. project.el (the default) + │ ;; (setq consult-project-function #'consult--default-project--function) + │ ;;;; 2. vc.el (vc-root-dir) + │ ;; (setq consult-project-function (lambda (_) (vc-root-dir))) + │ ;;;; 3. locate-dominating-file + │ ;; (setq consult-project-function (lambda (_) (locate-dominating-file "." ".git"))) + │ ;;;; 4. projectile.el (projectile-project-root) + │ ;; (autoload 'projectile-project-root "projectile") + │ ;; (setq consult-project-function (lambda (_) (projectile-project-root))) + │ ;;;; 5. No project support + │ ;; (setq consult-project-function nil) + │ ) + └──── + + +[Consult wiki] + + +3.2 Custom variables +──────────────────── + + *TIP:* If you have [Marginalia] installed, type `M-x + customize-variable RET ^consult' to see all Consult-specific + customizable variables with their current values and abbreviated + description. Alternatively, type `C-h a ^consult' to get an overview + of all Consult variables and functions with their descriptions. + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Variable Description + ─────────────────────────────────────────────────────────────────────────────────────── + consult-after-jump-hook Functions to call after jumping to a location + consult-async-input-debounce Input debounce for asynchronous commands + consult-async-input-throttle Input throttle for asynchronous commands + consult-async-min-input Minimum numbers of input characters + consult-async-refresh-delay Refresh delay for asynchronous commands + consult-async-split-style Splitting style used for async commands + consult-async-split-styles-alist Available splitting styles used for async commands + consult-bookmark-narrow Narrowing configuration for `consult-bookmark' + consult-buffer-filter Filter for `consult-buffer' + consult-buffer-sources List of virtual buffer sources + consult-fd-args Command line arguments for fd + consult-find-args Command line arguments for find + consult-fontify-max-size Buffers larger than this limit are not fontified + consult-fontify-preserve Preserve fontification for line-based commands. + consult-git-grep-args Command line arguments for git-grep + consult-goto-line-numbers Show line numbers for `consult-goto-line' + consult-grep-max-columns Maximal number of columns of the matching lines + consult-grep-args Command line arguments for grep + consult-imenu-config Mode-specific configuration for `consult-imenu' + consult-line-numbers-widen Show absolute line numbers when narrowing is active + consult-line-start-from-top Start the `consult-line' search from the top + consult-locate-args Command line arguments for locate + consult-man-args Command line arguments for man + consult-mode-command-filter Filter for `consult-mode-command' + consult-mode-histories Mode-specific history variables + consult-narrow-key Narrowing prefix key during completion + consult-point-placement Placement of the point when jumping to matches + consult-preview-key Keys which triggers preview + consult-preview-allowed-hooks List of `find-file' hooks to enable during preview + consult-preview-excluded-files Regexps matched against file names during preview + consult-preview-max-count Maximum number of files to keep open during preview + consult-preview-partial-size Files larger than this size are previewed partially + consult-preview-partial-chunk Size of the file chunk which is previewed partially + consult-preview-variables Alist of variables to bind during preview + consult-project-buffer-sources List of virtual project buffer sources + consult-project-function Function which returns current project root + consult-register-prefix Prefix string for register keys during completion + consult-ripgrep-args Command line arguments for ripgrep + consult-themes List of themes to be presented for selection + consult-widen-key Widening key during completion + consult-yank-rotate Rotate kill ring + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + +[Marginalia] + + +3.3 Fine-tuning of individual commands +────────────────────────────────────── + + *NOTE:* Consult supports fine-grained customization of individual + commands. This configuration feature exists for experienced users with + special requirements. There is the [Consult wiki], where we collect + further configuration examples. + + Commands and buffer sources allow flexible, individual customization + by using the `consult-customize' macro. You can override any option + passed to the internal `consult--read' API. Note that since + `consult--read' is part of the internal API, options could be removed, + replaced or renamed in future versions of the package. + + Useful options are: + • `:prompt' set the prompt string + • `:preview-key' set the preview key, default is `consult-preview-key' + • `:initial' set the initial input + • `:default' set the default value + • `:history' set the history variable symbol + • `:add-history' add items to the future history, for example symbol + at point + • `:sort' enable or disable sorting + • `:group' set to nil to disable candidate grouping and titles. + • `:inherit-input-method' set to non-nil to inherit the input method. + + ┌──── + │ (consult-customize + │ ;; Disable preview for `consult-theme' completely. + │ consult-theme :preview-key nil + │ ;; Set preview for `consult-buffer' to key `M-.' + │ consult-buffer :preview-key "M-." + │ ;; For `consult-line' change the prompt and specify multiple preview + │ ;; keybindings. Note that you should bind and in the + │ ;; `minibuffer-local-completion-map' or `vertico-map' to the commands which + │ ;; select the previous or next candidate. + │ consult-line :prompt "Search: " + │ :preview-key '("S-" "S-")) + └──── + + The configuration values are evaluated at runtime, just before the + completion session is started. Therefore you can use for example + `thing-at-point' to adjust the initial input or the future history. + + ┌──── + │ (consult-customize + │ consult-line + │ :add-history (seq-some #'thing-at-point '(region symbol))) + │ + │ (defalias 'consult-line-thing-at-point 'consult-line) + │ + │ (consult-customize + │ consult-line-thing-at-point + │ :initial (thing-at-point 'symbol)) + └──── + + Generally it is possible to modify commands for your individual needs + by the following techniques: + + 1. Use `consult-customize' in order to change the command or source + settings. + 2. Create your own wrapper function which passes modified arguments to + the Consult functions. + 3. Create your own buffer [multi sources] for `consult-buffer'. + 4. Create advices to modify some internal behavior. + 5. Write or propose a patch. + + +[Consult wiki] + +[multi sources] See section 2.4 + + +4 Recommended packages +══════════════════════ + + I use and recommend this combination of packages: + + • consult: This package + • [vertico]: Fast and minimal vertical completion system + • [marginalia]: Annotations for the completion candidates + • [embark and embark-consult]: Action commands, which can act on the + completion candidates + • [orderless]: Completion style which offers flexible candidate + filtering + • [wgrep]: Editing of grep buffers. Use with `consult-grep' via + `embark-export'. + + There exist multiple fine completion UIs beside Vertico, which are + supported by Consult. Give them a try and find out which interaction + model fits best for you. + + • The builtin completion UI, which pops up the `*Completions*' buffer. + • The builtin `icomplete-vertical-mode' in Emacs 28 or newer. + • [mct by Protesilaos Stavrou]: Minibuffer and Completions in Tandem, + which builds on the default completion UI. + + Note that all packages are independent and can be exchanged with + alternative components, since there exist no hard + dependencies. Furthermore it is possible to get started with only + default completion and Consult and add more components later to the + mix. For example you can omit Marginalia if you don't need + annotations. I highly recommend the Embark package, but in order to + familiarize yourself with the other components, you can first start + without it - or you could use with Embark right away and add the other + components later on. + + We document a [list of auxiliary packages] in the Consult wiki. These + packages integrate Consult with special programs or with other + packages in the wider Emacs ecosystem. + + +[vertico] + +[marginalia] + +[embark and embark-consult] + +[orderless] + +[wgrep] + +[mct by Protesilaos Stavrou] + +[list of auxiliary packages] + + + +5 Bug reports +═════════════ + + If you find a bug or suspect that there is a problem with Consult, + please carry out the following steps: + + 1. *Search through the issue tracker* if your issue has been reported + before (and has been resolved eventually) in the meantime. + 2. *Remove all packages involved in the suspected bug from your + installation.* + 3. *Reinstall the newest version of all relevant packages*. Updating + alone is not sufficient, since package.el sometimes causes + miscompilation. The list of packages includes Consult, Compat, + Vertico or other completion UIs, Marginalia, Embark and Orderless. + 4. Either use the default completion UI or ensure that exactly one of + `vertico-mode', `mct-mode', or `icomplete-mode' is enabled. The + unsupported modes `selectrum-mode', `ivy-mode', `helm-mode', + `ido-mode' and `ido-ubiquitous-mode' must be disabled. + 5. Ensure that the `completion-styles' variable is properly + configured. Try to set `completion-styles' to a list including + `substring' or `orderless'. + 6. Try to reproduce the issue with the newest stable Emacs + version. Start a bare bone Emacs instance with `emacs -Q' on the + command line. Execute the following minimal code snippets in the + scratch buffer. This way we can exclude side effects due to + configuration settings. If other packages are relevant to reproduce + the issue, include them in the minimal configuration snippet. + + Minimal setup with Vertico for `emacs -Q': + ┌──── + │ (package-initialize) + │ (require 'consult) + │ (require 'vertico) + │ (vertico-mode) + │ (setq completion-styles '(substring basic)) + └──── + + Minimal setup with the default completion system for `emacs -Q': + ┌──── + │ (package-initialize) + │ (require 'consult) + │ (setq completion-styles '(substring basic)) + └──── + + Please provide the necessary important information with your bug + report: + + • The minimal configuration snippet used to reproduce the issue. + • Your completion UI (Default completion, Vertico, Mct or Icomplete). + • A stack trace in case the bug triggers an exception. + • Your Emacs version, since bugs may be fixed or introduced in newer + versions. + • Your operating system, since Emacs behavior varies subtly between + Linux, Mac and Windows. + • The package manager, e.g., straight.el or package.el, used to + install the Emacs packages, in order to exclude update issues. Did + you install Consult as part of the Doom Emacs distribution? + • Do you use Evil? Consult does not provide Evil integration out of + the box, but there is some support in [evil-collection]. + + When evaluating Consult-related code snippets you should enable + [lexical binding]. Consult often relies on lambdas and lexical + closures. + + +[evil-collection] + +[lexical binding] + + + +6 Contributions +═══════════════ + + Consult is a community effort, please participate in the discussions. + Contributions are welcome, but you may want to discuss potential + contributions first. Since this package is part of [GNU ELPA] + contributions require a copyright assignment to the FSF. + + If you have a proposal, take a look at the [Consult issue tracker] and + the [Consult wishlist]. There have been many prior feature + discussions. Please search through the issue tracker, maybe your issue + or feature request has already been discussed. You can contribute to + the [Consult wiki], in case you want to share small configuration or + command snippets. + + +[GNU ELPA] + +[Consult issue tracker] + +[Consult wishlist] + +[Consult wiki] + + +7 Acknowledgments +═════════════════ + + This package took inspiration from [Counsel] by Oleh Krehel. Some of + the Consult commands originated in the Counsel package or the wiki of + the Selectrum package. This package exists only thanks to the help of + these great contributors and thanks to the feedback of many + users. Thank you! + + Code contributions: [Aymeric Agon-Rambosson], [Amos Bird], [Ashton + Wiersdorf], [Adam Spiers], [Augusto Stoffel], [Clemens Radermacher], + [Zhengyi], [Geoffrey Lessel], [Illia Ostapyshyn], [jakanakaevangeli], + [JD Smith], [Jean-Philippe Bernardy], [mattiasdrp], [Mohamed + Abdelnour], [Mohsin Kaleem], [Fox Kiester], [Omar Antolín Camarena], + [Earl Hyatt], [Omar Polo], [Piotr Kwiecinski], [Robert Weiner], + [Sergey Kostyaev], [Alexandru Scvorțov], [Tecosaur], [Sylvain + Rousseau], [Tom Fitzhenry], [Iñigo Serna] and [Alex Kreisher]. + + Advice and useful discussions: [Enrique Kessler Martínez], [Adam + Porter], [Bruce d'Arcus], [Clemens Radermacher], [Dmitry Gutov], + [Howard Melman], [Itai Y. Efrat], [JD Smith], [Manuel Uberti], [Stefan + Monnier], [Omar Antolín Camarena], [Steve Purcell], [Radon + Rosborough], [Tom Fitzhenry] and [Protesilaos Stavrou]. + + +[Counsel] + +[Aymeric Agon-Rambosson] + +[Amos Bird] + +[Ashton Wiersdorf] + +[Adam Spiers] + +[Augusto Stoffel] + +[Clemens Radermacher] + +[Zhengyi] + +[Geoffrey Lessel] + +[Illia Ostapyshyn] + +[jakanakaevangeli] + +[JD Smith] + +[Jean-Philippe Bernardy] + +[mattiasdrp] + +[Mohamed Abdelnour] + +[Mohsin Kaleem] + +[Fox Kiester] + +[Omar Antolín Camarena] + +[Earl Hyatt] + +[Omar Polo] + +[Piotr Kwiecinski] + +[Robert Weiner] + +[Sergey Kostyaev] + +[Alexandru Scvorțov] + +[Tecosaur] + +[Sylvain Rousseau] + +[Tom Fitzhenry] + +[Iñigo Serna] + +[Alex Kreisher] + +[Enrique Kessler Martínez] + +[Adam Porter] + +[Bruce d'Arcus] + +[Dmitry Gutov] + +[Howard Melman] + +[Itai Y. Efrat] + +[Manuel Uberti] + +[Stefan Monnier] + +[Steve Purcell] + +[Radon Rosborough] + +[Protesilaos Stavrou] + + +8 Indices +═════════ + +8.1 Function index +────────────────── + + +8.2 Concept index +───────────────── blob - /dev/null blob + e4b2ee63932a248b8a00f99b4c276a8cae02f1a8 (mode 644) --- /dev/null +++ elpa/consult-1.5/README.org @@ -0,0 +1,1153 @@ +#+title: consult.el - Consulting completing-read +#+author: Daniel Mendler +#+language: en +#+export_file_name: consult.texi +#+texinfo_dir_category: Emacs misc features +#+texinfo_dir_title: Consult: (consult). +#+texinfo_dir_desc: Useful commands built on completing-read. + +#+html: GNU Emacs +#+html: GNU ELPA +#+html: GNU-devel ELPA +#+html: MELPA +#+html: MELPA Stable + +Consult provides search and navigation commands based on the Emacs completion +function [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Minibuffer-Completion.html][completing-read]]. Completion allows you to quickly select an item from a +list of candidates. Consult offers asynchronous and interactive =consult-grep= and +=consult-ripgrep= commands, and the line-based search command =consult-line=. +Furthermore Consult provides an advanced buffer switching command =consult-buffer= +to switch between buffers, recently opened files, bookmarks and buffer-like +candidates from other sources. Some of the Consult commands are enhanced +versions of built-in Emacs commands. For example the command =consult-imenu= +presents a flat list of the Imenu with [[#live-previews][live preview]], [[#narrowing-and-grouping][grouping and narrowing]]. +Please take a look at the [[#available-commands][full list of commands]]. + +Consult is fully compatible with completion systems centered around the standard +Emacs =completing-read= API, notably the default completion system, [[https://github.com/minad/vertico][Vertico]], [[https://github.com/protesilaos/mct][Mct]], +and [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Icomplete.html][Icomplete]]. + +This package keeps the completion system specifics to a minimum. The ability of +the Consult commands to work well with arbitrary completion systems is one of +the main advantages of the package. Consult fits well into existing setups and +it helps you to create a full completion environment out of small and +independent components. + +You can combine the complementary packages [[https://github.com/minad/marginalia/][Marginalia]], [[https://github.com/oantolin/embark/][Embark]] and [[https://github.com/oantolin/orderless][Orderless]] with +Consult. Marginalia enriches the completion display with annotations, e.g., +documentation strings or file information. The versatile Embark package provides +local actions, comparable to a context menu. These actions operate on the +selected candidate in the minibuffer or at point in normal buffers. For example, +when selecting from a list of files, Embark offers an action to delete the file. +Additionally Embark offers a facility to collect completion candidates in a +collect buffer. The section [[#embark-integration][Embark integration]] documents in detail how Consult +and Embark work together. + +#+toc: headlines 8 + +* Screenshots :noexport: + +#+caption: consult-grep +[[https://github.com/minad/consult/blob/screenshots/consult-grep.gif?raw=true]] +Fig. 1: Command =consult-git-grep= + +#+caption: consult-imenu +[[https://github.com/minad/consult/blob/screenshots/consult-imenu.png?raw=true]] +Fig. 2: Command =consult-imenu= + +#+caption: consult-line +[[https://github.com/minad/consult/blob/screenshots/consult-line.png?raw=true]] +Fig. 3: Command =consult-line= + +* Available commands +:properties: +:custom_id: available-commands +:description: Navigation, search, editing commands and more +:end: +#+cindex: commands + +Most Consult commands follow the meaningful naming scheme =consult-=. +Many commands implement a little known but convenient Emacs feature called +"future history", which guesses what input the user wants. At a command prompt +type =M-n= and typically Consult will insert the symbol or thing at point into +the input. + +*TIP:* If you have [[https://github.com/minad/marginalia][Marginalia]] annotators activated, type =M-x ^consult= to see +all Consult commands with their abbreviated description. Alternatively, type +=C-h a ^consult= to get an overview of all Consult variables and functions with +their descriptions. + +** Virtual Buffers +:properties: +:description: Buffers, bookmarks and recent files +:end: +#+cindex: virtual buffers + +#+findex: consult-buffer +#+findex: consult-buffer-other-window +#+findex: consult-buffer-other-frame +#+findex: consult-buffer-other-tab +#+findex: consult-project-buffer +#+findex: consult-recent-file +#+findex: consult-bookmark +- =consult-buffer=: Enhanced version of =switch-to-buffer= with support for virtual + buffers. Supports live preview of buffers and narrowing to the virtual buffer + types. You can type =f SPC= in order to narrow to recent files. Press =SPC= to + show ephemeral buffers. Supported narrowing keys: + - b Buffers + - SPC Hidden buffers + - * Modified buffers + - f Files (Requires =recentf-mode=) + - r File registers + - m Bookmarks + - p Project + - Custom [[#multiple-sources][other sources]] configured in =consult-buffer-sources=. +- =consult-buffer-other-window=, =consult-buffer-other-frame=, + =consult-buffer-other-tab=: Variants of =consult-buffer=. +- =consult-project-buffer=: Variant of =consult-buffer= restricted to buffers and + recent files of the current project. You can add custom sources to + =consult-project-buffer-sources=. The command may prompt you for a project if + you invoke it from outside a project. +- =consult-bookmark=: Select or create bookmark. To select bookmarks you might use the + =consult-buffer= as an alternative, which can include a bookmark virtual buffer + source. Note that =consult-bookmark= supports preview of bookmarks and + narrowing. +- =consult-recent-file=: Select from recent files with preview. + You might prefer the powerful =consult-buffer= instead, which can include + recent files as a virtual buffer source. The =recentf-mode= enables tracking of + recent files. + +** Editing +:properties: +:description: Commands useful for editing +:end: +#+cindex: editing + +#+findex: consult-yank-pop +#+findex: consult-yank-from-kill-ring +#+findex: consult-yank-replace +#+findex: consult-kmacro +- =consult-yank-from-kill-ring=: Enhanced version of =yank= to select an item + from the =kill-ring=. The selected text previewed as overlay in the buffer. +- =consult-yank-pop=: Enhanced version of =yank-pop= with DWIM-behavior, which + either replaces the last =yank= by cycling through the =kill-ring=, or if there + has not been a last =yank= consults the =kill-ring=. The selected text previewed + as overlay in the buffer. +- =consult-yank-replace=: Like =consult-yank-pop=, but always replaces the last + =yank= with an item from the =kill-ring=. +- =consult-kmacro=: Select macro from the macro ring and execute it. + +** Register +:properties: +:description: Searching through registers and fast access +:end: +#+cindex: register + +#+findex: consult-register +#+findex: consult-register-load +#+findex: consult-register-store +#+findex: consult-register-format +#+findex: consult-register-window +- =consult-register=: Select from list of registers. The command + supports narrowing to register types and preview of marker positions. This + command is useful to search the register contents. For quick access use the + commands =consult-register-load=, =consult-register-store= or the built-in Emacs + register commands. +- =consult-register-format=: Set =register-preview-function= to this function for + an enhanced register formatting. See the [[#use-package-example][example configuration]]. +- =consult-register-window=: Replace =register-preview= with this function for a + better register window. See the [[#use-package-example][example configuration]]. +- =consult-register-load=: Utility command to quickly load a register. + The command either jumps to the register value or inserts it. +- =consult-register-store=: Improved UI to store registers depending on the current + context with an action menu. With an active region, store/append/prepend the + contents, optionally deleting the region when a prefix argument is given. + With a numeric prefix argument, store/add the number. Otherwise store point, + frameset, window or kmacro. Usage examples: + * =M-' x=: If no region is active, store point in register =x=. + If a region is active, store the region in register =x=. + * =M-' M-w x=: Store window configuration in register =x=. + * =C-u 100 M-' x=: Store number in register =x=. + +** Navigation +:properties: +:description: Mark rings, outlines and imenu +:end: +#+cindex: navigation + +#+findex: consult-goto-line +#+findex: consult-mark +#+findex: consult-global-mark +#+findex: consult-outline +#+findex: consult-imenu +#+findex: consult-imenu-multi +- =consult-goto-line=: Jump to line number enhanced with live preview. This is a + drop-in replacement for =goto-line=. Enter a line number to jump to the first + column of the given line. Alternatively enter =line:column= in order to jump to + a specific column. +- =consult-mark=: Jump to a marker in the =mark-ring=. Supports live + preview and recursive editing. +- =consult-global-mark=: Jump to a marker in the =global-mark-ring=. + Supports live preview and recursive editing. +- =consult-outline=: Jump to a heading of the outline. Supports narrowing + to a heading level, live preview and recursive editing. +- =consult-imenu=: Jump to imenu item in the current buffer. Supports + live preview, recursive editing and narrowing. +- =consult-imenu-multi=: Jump to imenu item in project buffers, with + the same major mode as the current buffer. Supports live preview, + recursive editing and narrowing. This feature has been inspired by + [[https://github.com/vspinu/imenu-anywhere][imenu-anywhere]]. + +** Search +:properties: +:description: Line search, grep and file search +:end: +#+cindex: search + +#+findex: consult-line +#+findex: consult-line-multi +#+findex: consult-keep-lines +#+findex: consult-focus-lines +- =consult-line=: Enter search string and select from matching lines. + Supports live preview and recursive editing. The symbol at point and the + recent Isearch string are added to the "future history" and can be accessed + by pressing =M-n=. When =consult-line= is bound to the =isearch-mode-map= and + is invoked during a running Isearch, it will use the current Isearch string. +- =consult-line-multi=: Search dynamically across multiple buffers. By default + search across project buffers. If invoked with a prefix argument search across + all buffers. The candidates are computed on demand based on the input. The + command behaves like =consult-grep=, but operates on buffers instead of files. +- =consult-keep-lines=: Replacement for =keep/flush-lines= which uses the current + completion style for filtering the buffer. The function updates the buffer + while typing. In particular =consult-keep-lines= can narrow down an exported + Embark collect buffer further, relying on the same completion filtering as + ~completing-read~. If the input begins with the negation operator, i.e., ~! SPC~, + the filter matches the complement. If a region is active, the region restricts + the filtering. +- =consult-focus-lines=: Temporarily hide lines by filtering them using the + current completion style. Call with =C-u= prefix argument in order to show the + hidden lines again. If the input begins with the negation operator, i.e., ~! + SPC~, the filter matches the complement. In contrast to =consult-keep-lines= this + function does not edit the buffer. If a region is active, the region restricts + the filtering. + +** Grep and Find +:properties: +:description: Searching through the filesystem +:end: +#+cindex: grep +#+cindex: find +#+cindex: locate + +#+findex: consult-grep +#+findex: consult-ripgrep +#+findex: consult-git-grep +#+findex: consult-find +#+findex: consult-fd +#+findex: consult-locate +- =consult-grep=, =consult-ripgrep=, =consult-git-grep=: Search for regular expression + in files. Consult invokes Grep asynchronously, while you enter the search + term. After at least =consult-async-min-input= characters, the search gets + started. Consult splits the input string into two parts, if the first + character is a punctuation character, like =#=. For example + =#regexps#filter-string=, is split at the second =#=. The string =regexps= is passed + to Grep. Note that Consult transforms Emacs regular expressions to expressions + understand by the search program. Always use Emacs regular expressions at the + prompt. If you enter multiple regular expressions separated by space only + lines matching all regular expressions are shown. In order to match space + literally, escape the space with a backslash. The =filter-string= is passed to + the /fast/ Emacs filtering to further narrow down the list of matches. This is + particularly useful if you are using an advanced completion style like + orderless. =consult-grep= supports preview. If the =consult-project-function= + returns non-nil, =consult-grep= searches the current project directory. + Otherwise the =default-directory= is searched. If =consult-grep= is invoked + with prefix argument =C-u M-s g=, you can specify one or more comma-separated files + and directories manually. +- =consult-find=, =consult-fd=, =consult-locate=: Find file by matching the path + against a regexp. Like for =consult-grep=, either the project root or the + current directory is the root directory for the search. The input string is + treated similarly to =consult-grep=, where the first part is passed to find, and + the second part is used for Emacs filtering. Prefix arguments to =consult-find= + work just like those for the consult grep commands. + +** Compilation +:properties: +:description: Jumping to references and compilation errors +:end: +#+cindex: compilation errors + +#+findex: consult-compile-error +#+findex: consult-flymake +#+findex: consult-xref +- =consult-compile-error=: Jump to a compilation error. Supports live preview + narrowing and recursive editing. +- =consult-flymake=: Jump to Flymake diagnostic. Supports live preview and + recursive editing. The command supports narrowing. Press =e SPC=, =w SPC=, =n SPC= + to only show errors, warnings and notes respectively. +- =consult-xref=: Integration with xref. This function can be set as + =xref-show-xrefs-function= and =xref-show-definitions-function=. + +** Histories +:properties: +:description: Navigating histories +:end: +#+cindex: history + +#+findex: consult-complex-command +#+findex: consult-history +#+findex: consult-isearch-history +- =consult-complex-command=: Select a command from the + =command-history=. This command is a =completing-read= version of + =repeat-complex-command= and is also a replacement for the =command-history= + command from chistory.el. +- =consult-history=: Insert a string from the current buffer history, for example + the Eshell or Comint history. You can also invoke this command from the + minibuffer. In that case =consult-history= uses the history stored in the + =minibuffer-history-variable=. If you prefer =completion-at-point=, take a look at + =cape-history= from the [[https://github.com/minad/cape][Cape]] package. +- =consult-isearch-history=: During an Isearch session, this command picks a + search string from history and continues the search with the newly selected + string. Outside of Isearch, the command allows you to pick a string from the + history and starts a new Isearch. =consult-isearch-history= acts as a drop-in + replacement for =isearch-edit-string=. + +** Modes +:properties: +:description: Toggling minor modes and executing commands +:end: +#+cindex: minor mode +#+cindex: major mode + +#+findex: consult-minor-mode-menu +#+findex: consult-mode-command +- =consult-minor-mode-menu=: Enable/disable minor mode. Supports + narrowing to on/off/local/global modes by pressing =i/o/l/g SPC= + respectively. +- =consult-mode-command=: Run a command from the currently active minor or major + modes. Supports narrowing to local-minor/global-minor/major mode via the keys + =l/g/m=. + +** Org Mode +:properties: +:description: Org-specific commands +:end: + +#+findex: consult-org-heading +#+findex: consult-org-agenda +- =consult-org-heading=: Variant of =consult-imenu= or =consult-outline= for Org + buffers. The headline and its ancestors headlines are separated by slashes. + Supports narrowing by heading level, priority and TODO keyword, as well as live + preview and recursive editing. +- =consult-org-agenda=: Jump to an Org agenda heading. Supports narrowing by + heading level, priority and TODO keyword, as well as live preview and + recursive editing. +** Help +:properties: +:description: Searching through help +:end: + +#+findex: consult-info +#+findex: consult-man +- =consult-man=: Find Unix man page, via Unix =apropos= or =man -k=. =consult-man= opens + the selected man page using the Emacs =man= command. +- =consult-info=: Full text search through info pages. If the command is invoked + from within an ~*info*~ buffer, it will search through the current manual. You + may want to create your own commands which search through a predefined set of + info pages, for example: +#+begin_src emacs-lisp +(defun consult-info-emacs () + "Search through Emacs info pages." + (interactive) + (consult-info "emacs" "efaq" "elisp" "cl" "compat")) + +(defun consult-info-org () + "Search through the Org info page." + (interactive) + (consult-info "org")) + +(defun consult-info-completion () + "Search through completion info pages." + (interactive) + (consult-info "vertico" "consult" "marginalia" "orderless" "embark" + "corfu" "cape" "tempel")) +#+end_src + +** Miscellaneous +:properties: +:description: Various other useful commands +:end: + +#+findex: consult-completion-in-region +#+findex: consult-theme +#+findex: consult-preview-at-point +#+findex: consult-preview-at-point-mode +- =consult-theme=: Select a theme and disable all currently enabled themes. + Supports live preview of the theme while scrolling through the candidates. +- =consult-preview-at-point= and =consult-preview-at-point-mode=: Command and minor + mode which previews the candidate at point in the =*Completions*= buffer. This + mode is relevant if you use [[https://git.sr.ht/~protesilaos/mct][Mct]] or the default =*Completions*= UI. +- =consult-completion-in-region=: In case you don't use [[https://github.com/minad/corfu][Corfu]] as your in-buffer + completion UI, this function can be set as =completion-in-region-function=. Then + your minibuffer completion UI (e.g., Vertico or Icomplete) will be used for + =completion-at-point=. + #+begin_src emacs-lisp + ;; Use `consult-completion-in-region' if Vertico is enabled. + ;; Otherwise use the default `completion--in-region' function. + (setq completion-in-region-function + (lambda (&rest args) + (apply (if vertico-mode + #'consult-completion-in-region + #'completion--in-region) + args))) + #+end_src + Instead of =consult-completion-in-region=, you may prefer to see the completions + directly in the buffer as a small popup. In that case, I recommend the [[https://github.com/minad/corfu][Corfu]] + package. There is a technical limitation of =consult-completion-in-region= in + combination with the Lsp modes. The Lsp server relies on the input at point, + in order to generate refined candidate strings. Since the completion is + transferred from the original buffer to the minibuffer, the server does not + receive the updated input. In contrast, in-buffer Lsp completion for example + via Corfu works properly since the completion takes place directly in the + original buffer. + +* Special features +:properties: +:description: Enhancements over built-in `completing-read' +:end: + +Consult enhances =completing-read= with live previews of candidates, additional +narrowing capabilities to candidate groups and asynchronously generated +candidate lists. The internal =consult--read= function, which is used by most +Consult commands, is a thin wrapper around =completing-read= and provides the +special functionality. In order to support multiple candidate sources there +exists the high-level function =consult--multi=. The architecture of Consult +allows it to work with different completion systems in the backend, while still +offering advanced features. + +** Live previews +:properties: +:description: Preview the currently selected candidate +:custom_id: live-previews +:end: +#+cindex: preview + +Some Consult commands support live previews. For example when you scroll through +the items of =consult-line=, the buffer will scroll to the corresponding position. +It is possible to jump back and forth between the minibuffer and the buffer to +perform recursive editing while the search is ongoing. + +Consult enables previews by default. You can disable them by adjusting the +=consult-preview-key= variable. Furthermore it is possible to specify keybindings +which trigger the preview manually as shown in the [[#use-package-example][example configuration]]. The +default setting of =consult-preview-key= is =any= which means that Consult triggers +the preview /immediately/ on any key press when the selected candidate changes. +You can configure each command individually with its own =:preview-key=. The +following settings are possible: + +- Automatic and immediate ='any= +- Automatic and delayed =(list :debounce 0.5 'any)= +- Manual and immediate ="M-."= +- Manual and delayed =(list :debounce 0.5 "M-.")= +- Disabled =nil= + +A safe recommendation is to leave automatic immediate previews enabled in +general and disable the automatic preview only for commands where the preview +may be expensive due to file loading. Internally, Consult uses the +value of =this-command= to determine the =:preview-key= +customized. This means that if you wrap a =consult-*= command within +your own function or command, you will also need to add the name of +/your custom command/ to the =consult-customize= call in order for it +to be considered. + +#+begin_src emacs-lisp +(consult-customize + consult-ripgrep consult-git-grep consult-grep + consult-bookmark consult-recent-file consult-xref + consult--source-bookmark consult--source-file-register + consult--source-recent-file consult--source-project-recent-file + ;; my/command-wrapping-consult ;; disable auto previews inside my command + :preview-key '(:debounce 0.4 any) ;; Option 1: Delay preview + ;; :preview-key "M-.") ;; Option 2: Manual preview +#+end_src + +In this case one may wonder what the difference is between using an Embark +action on the current candidate in comparison to a manually triggered preview. +The main difference is that the files opened by manual preview are closed again +after the completion session. During preview some functionality is disabled to +improve the performance, see for example the customization variables +=consult-preview-variables= and =consult-preview-allowed-hooks=. Only the hooks +listed in =consult-preview-allowed-hooks= are executed when a file is opened +(=find-file-hook=). In order to enable additional font locking during preview, add +the corresponding hooks to the allow list. The following code demonstrates this +for [[https://github.com/minad/org-modern][org-modern]] and [[https://github.com/tarsius/hl-todo][hl-todo]]. + +#+begin_src emacs-lisp +(add-to-list 'consult-preview-allowed-hooks 'global-org-modern-mode-check-buffers) +(add-to-list 'consult-preview-allowed-hooks 'global-hl-todo-mode-check-buffers) +#+end_src + +Files larger than =consult-preview-partial-size= are previewed partially. Delaying +the preview is also useful for =consult-theme=, since the theme preview is slow. +The delay results in a smoother UI experience. + +#+begin_src emacs-lisp +;; Preview on any key press, but delay 0.5s +(consult-customize consult-theme :preview-key '(:debounce 0.5 any)) +;; Preview immediately on M-., on up/down after 0.5s, on any other key after 1s +(consult-customize consult-theme + :preview-key + '("M-." + :debounce 0.5 "" "" + :debounce 1 any)) +#+end_src + +** Narrowing and grouping +:properties: +:description: Restricting the completion to a candidate group +:custom_id: narrowing-and-grouping +:end: +#+cindex: narrowing + +Consult has special support for candidate groups. If the completion UI supports +the grouping functionality, the UI separates the groups with thin lines and +shows group titles. Grouping is useful if the list of candidates consists of +candidates of multiple types or candidates from [[#multiple-sources][multiple sources]], like the +=consult-buffer= command, which shows both buffers and recently opened files. Note +that you can disable the group titles by setting the =:group= property of the +corresponding command to nil using the =consult-customize= macro. + +By entering a narrowing prefix or by pressing a narrowing key it is possible to +restrict the completion candidates to a certain candidate group. When you use +the =consult-buffer= command, you can enter the prefix =b SPC= to restrict list of +candidates to buffers only. If you press =DEL= afterwards, the full candidate list +will be shown again. Furthermore a narrowing prefix key and a widening key can +be configured which can be pressed to achieve the same effect, see the +configuration variables =consult-narrow-key= and =consult-widen-key=. + +After pressing =consult-narrow-key=, the possible narrowing keys can be shown by +pressing =C-h=. When pressing =C-h= after some prefix key, the =prefix-help-command= +is invoked, which shows the keybinding help window by default. As a more compact +alternative, there is the =consult-narrow-help= command which can be bound to a +key, for example =?= or =C-h= in the =consult-narrow-map=, as shown in the [[#use-package-example][example +configuration]]. If [[https://github.com/justbur/emacs-which-key][which-key]] is installed, the narrowing keys are automatically +shown in the which-key window after pressing the =consult-narrow-key=. + +** Asynchronous search +:properties: +:description: Filtering asynchronously generated candidate lists +:end: +#+cindex: asynchronous search + +Consult has support for asynchronous generation of candidate lists. This feature +is used for search commands like =consult-grep=, where the list of matches is +generated dynamically while the user is typing a regular expression. The grep +process is executed in the background. When modifying the regular expression, +the background process is terminated and a new process is started with the +modified regular expression. + +The matches, which have been found, can then be narrowed using the installed +Emacs completion-style. This can be powerful if you are using for example the +=orderless= completion style. + +This two-level filtering is possible by splitting the input string. Part of the +input string is treated as input to grep and part of the input is used for +filtering. There are multiple splitting styles available, configured in +~consult-async-split-styles-alist~: =nil=, =comma=, =semicolon= and =perl=. The default +splitting style is configured with the variable ~consult-async-split-style~. + +With the =comma= and =semicolon= splitting styles, the first word before the comma +or semicolon is passed to grep, the remaining string is used for filtering. The +=nil= splitting style does not perform any splitting, the whole input is passed to +grep. + +The =perl= splitting style splits the input string at a punctuation character, +using a similar syntax as Perl regular expressions. + +Examples: + +- =#defun=: Search for "defun" using grep. +- =#consult embark=: Search for both "consult" and "embark" using grep in any order. +- =#first.*second=: Search for "first" followed by "second" using grep. +- =#\(consult\|embark\)=: Search for "consult" or "embark" using grep. Note the + usage of Emacs-style regular expressions. +- =#defun#consult=: Search for "defun" using grep, filter with the word + "consult". +- =/defun/consult=: It is also possible to use other punctuation + characters. +- =#to#=: Force searching for "to" using grep, since the grep pattern + must be longer than =consult-async-min-input= characters by default. +- =#defun -- --invert-match#=: Pass argument =--invert-match= to grep. + +Asynchronous processes like =find= and =grep= create an error log buffer +=_*consult-async*= (note the leading space), which is useful for +troubleshooting. The prompt has a small indicator showing the process status: + +- =:= the usual prompt colon, before input is provided. +- =*= with warning face, the process is running. +- =:= with success face, success, process exited with an error code of zero. +- =!= with error face, failure, process exited with a nonzero error code. +- =;= with error face, interrupted, for example if more input is provided. + +** Multiple sources +:properties: +:description: Combining candidates from different sources +:custom_id: multiple-sources +:end: +#+cindex: multiple sources + +Multiple synchronous candidate sources can be combined. This feature is used by +the =consult-buffer= command to present buffer-like candidates in a single menu +for quick access. By default =consult-buffer= includes buffers, bookmarks, recent +files and project-specific buffers and files. It is possible to configure the +list of sources via the =consult-buffer-sources= variable. Arbitrary custom +sources can be defined. + +As an example, the bookmark source is defined as follows: + +#+begin_src emacs-lisp +(defvar consult--source-bookmark + `(:name "Bookmark" + :narrow ?m + :category bookmark + :face consult-bookmark + :history bookmark-history + :items ,#'bookmark-all-names + :action ,#'consult--bookmark-action)) +#+end_src + +Required source fields: +- =:category= Completion category. +- =:items= List of strings to select from or function returning list of strings. + A list of cons cells is not supported. + +Optional source fields: +- =:name= Name of the source, used for narrowing, group titles and annotations. +- =:narrow= Narrowing character or =(character . string)= pair. +- =:preview-key= Preview key or keys which trigger preview. +- =:enabled= Function which must return t if the source is enabled. +- =:hidden= When t candidates of this source are hidden by default. +- =:face= Face used for highlighting the candidates. +- =:annotate= Annotation function called for each candidate, returns string. +- =:history= Name of history variable to add selected candidate. +- =:default= Must be t if the first item of the source is the default value. +- =:action= Function called with the selected candidate. +- =:new= Function called with new candidate name, only if =:require-match= is nil. +- =:state= State constructor for the source, must return the state function. +- Other source fields can be added specifically to the use case. + +The =:state= and =:action= fields of the sources deserve a longer explanation. The +=:action= function takes a single argument and is only called after selection with +the selected candidate, if the selection has not been aborted. This +functionality is provided for convenience and easy definition of sources. The +=:state= field is more general. The =:state= function is a constructor function +without arguments, which can perform some setup necessary for the preview. It +must return a closure which takes an ACTION and a CANDIDATE argument. See the +docstring of =consult--with-preview= for more details about the ACTION argument. + +By default, =consult-buffer= previews buffers, bookmarks and files. Loading recent +files or bookmarks can result in expensive operations. However it is possible to +configure a manual preview as follows. + +#+begin_src emacs-lisp +(consult-customize + consult--source-bookmark consult--source-file-register + consult--source-recent-file consult--source-project-recent-file + :preview-key "M-.") +#+end_src + +Sources can be added directly to the =consult-buffer-source= list for convenience. +For example, the following source lists all Org buffers and lets you create new +ones. + +#+begin_src emacs-lisp +(defvar org-source + (list :name "Org Buffer" + :category 'buffer + :narrow ?o + :face 'consult-buffer + :history 'buffer-name-history + :state #'consult--buffer-state + :new + (lambda (name) + (with-current-buffer (get-buffer-create name) + (insert "#+title: " name "\n\n") + (org-mode) + (consult--buffer-action (current-buffer)))) + :items + (lambda () + (consult--buffer-query :mode 'org-mode :as #'consult--buffer-pair)))) + +(add-to-list 'consult-buffer-sources 'org-source 'append) +#+end_src + +One can create similar sources for other major modes. See the [[https://github.com/minad/consult/wiki][Consult wiki]] for +many additional source examples. See also the documentation of =consult-buffer= +and of the internal =consult--multi= API. The function =consult--multi= can be used +to create new multi-source commands. + +** Embark integration +:properties: +:description: Actions, Grep/Occur-buffer export +:custom_id: embark-integration +:end: +#+cindex: embark + +*NOTE*: Install the =embark-consult= package from MELPA, which provides +Consult-specific Embark actions and the Occur buffer export. + +Embark is a versatile package which offers context dependent actions, comparable +to a context menu. See the [[https://github.com/oantolin/embark][Embark manual]] for an extensive description of its +capabilities. + +Actions are commands which can operate on the currently selected candidate (or +target in Embark terminology). When completing files, for example the +=delete-file= command is offered. With Embark you can execute arbitrary commands +on the currently selected candidate via =M-x=. + +Furthermore Embark provides the =embark-collect= command, which collects +candidates and presents them in an Embark collect buffer, where further actions +can be applied to them. A related feature is the =embark-export= command, which +exports candidate lists to a buffer of a special type. For example in the case +of file completion, a Dired buffer is opened. + +In the context of Consult, particularly exciting is the possibility to export +the matching lines from =consult-line=, =consult-outline=, =consult-mark= and +=consult-global-mark=. The matching lines are exported to an Occur buffer where +they can be edited via the =occur-edit-mode= (press key =e=). Similarly, Embark +supports exporting the matches found by =consult-grep=, =consult-ripgrep= and +=consult-git-grep= to a Grep buffer, where the matches across files can be edited, +if the [[https://github.com/mhayashi1120/Emacs-wgrep][wgrep]] package is installed. These three workflows are symmetric. + ++ =consult-line= -> =embark-export= to =occur-mode= buffer -> =occur-edit-mode= for editing of matches in buffer. ++ =consult-grep= -> =embark-export= to =grep-mode= buffer -> =wgrep= for editing of all matches. ++ =consult-find= -> =embark-export= to =dired-mode= buffer -> =wdired-change-to-wdired-mode= for editing. + +* Configuration +:properties: +:description: Example configuration and customization variables +:end: + +Consult can be installed from [[https://elpa.gnu.org/packages/consult.html][ELPA]] or [[https://melpa.org/#/consult][MELPA]] via the Emacs built-in package +manager. Alternatively it can be directly installed from the development +repository via other non-standard package managers. + +There is the [[https://github.com/minad/consult/wiki][Consult wiki]], where additional configuration examples can be +contributed. + +*IMPORTANT:* It is recommended that you enable [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Lexical-Binding.html][lexical binding]] in your +configuration. Many Consult-related code snippets require lexical binding, since +they use lambdas and closures. + +** Use-package example +:properties: +:description: Configuration example based on use-package +:custom_id: use-package-example +:end: +#+cindex: use-package + +The Consult package only provides commands and does not add any keybindings or +modes. Therefore the package is non-intrusive but requires a little setup +effort. In order to use the Consult commands, it is advised to add keybindings +for commands which are accessed often. Rarely used commands can be invoked via +=M-x=. Feel free to only bind the commands you consider useful to your workflow. +The configuration shown here relies on the =use-package= macro, which is a +convenient tool to manage package configurations. + +*NOTE:* There is the [[https://github.com/minad/consult/wiki][Consult wiki]], where you can contribute additional +configuration examples. + +#+begin_src emacs-lisp +;; Example configuration for Consult +(use-package consult + ;; Replace bindings. Lazily loaded due by `use-package'. + :bind (;; C-c bindings in `mode-specific-map' + ("C-c M-x" . consult-mode-command) + ("C-c h" . consult-history) + ("C-c k" . consult-kmacro) + ("C-c m" . consult-man) + ("C-c i" . consult-info) + ([remap Info-search] . consult-info) + ;; C-x bindings in `ctl-x-map' + ("C-x M-:" . consult-complex-command) ;; orig. repeat-complex-command + ("C-x b" . consult-buffer) ;; orig. switch-to-buffer + ("C-x 4 b" . consult-buffer-other-window) ;; orig. switch-to-buffer-other-window + ("C-x 5 b" . consult-buffer-other-frame) ;; orig. switch-to-buffer-other-frame + ("C-x t b" . consult-buffer-other-tab) ;; orig. switch-to-buffer-other-tab + ("C-x r b" . consult-bookmark) ;; orig. bookmark-jump + ("C-x p b" . consult-project-buffer) ;; orig. project-switch-to-buffer + ;; Custom M-# bindings for fast register access + ("M-#" . consult-register-load) + ("M-'" . consult-register-store) ;; orig. abbrev-prefix-mark (unrelated) + ("C-M-#" . consult-register) + ;; Other custom bindings + ("M-y" . consult-yank-pop) ;; orig. yank-pop + ;; M-g bindings in `goto-map' + ("M-g e" . consult-compile-error) + ("M-g f" . consult-flymake) ;; Alternative: consult-flycheck + ("M-g g" . consult-goto-line) ;; orig. goto-line + ("M-g M-g" . consult-goto-line) ;; orig. goto-line + ("M-g o" . consult-outline) ;; Alternative: consult-org-heading + ("M-g m" . consult-mark) + ("M-g k" . consult-global-mark) + ("M-g i" . consult-imenu) + ("M-g I" . consult-imenu-multi) + ;; M-s bindings in `search-map' + ("M-s d" . consult-find) ;; Alternative: consult-fd + ("M-s c" . consult-locate) + ("M-s g" . consult-grep) + ("M-s G" . consult-git-grep) + ("M-s r" . consult-ripgrep) + ("M-s l" . consult-line) + ("M-s L" . consult-line-multi) + ("M-s k" . consult-keep-lines) + ("M-s u" . consult-focus-lines) + ;; Isearch integration + ("M-s e" . consult-isearch-history) + :map isearch-mode-map + ("M-e" . consult-isearch-history) ;; orig. isearch-edit-string + ("M-s e" . consult-isearch-history) ;; orig. isearch-edit-string + ("M-s l" . consult-line) ;; needed by consult-line to detect isearch + ("M-s L" . consult-line-multi) ;; needed by consult-line to detect isearch + ;; Minibuffer history + :map minibuffer-local-map + ("M-s" . consult-history) ;; orig. next-matching-history-element + ("M-r" . consult-history)) ;; orig. previous-matching-history-element + + ;; Enable automatic preview at point in the *Completions* buffer. This is + ;; relevant when you use the default completion UI. + :hook (completion-list-mode . consult-preview-at-point-mode) + + ;; The :init configuration is always executed (Not lazy) + :init + + ;; Optionally configure the register formatting. This improves the register + ;; preview for `consult-register', `consult-register-load', + ;; `consult-register-store' and the Emacs built-ins. + (setq register-preview-delay 0.5 + register-preview-function #'consult-register-format) + + ;; Optionally tweak the register preview window. + ;; This adds thin lines, sorting and hides the mode line of the window. + (advice-add #'register-preview :override #'consult-register-window) + + ;; Use Consult to select xref locations with preview + (setq xref-show-xrefs-function #'consult-xref + xref-show-definitions-function #'consult-xref) + + ;; Configure other variables and modes in the :config section, + ;; after lazily loading the package. + :config + + ;; Optionally configure preview. The default value + ;; is 'any, such that any key triggers the preview. + ;; (setq consult-preview-key 'any) + ;; (setq consult-preview-key "M-.") + ;; (setq consult-preview-key '("S-" "S-")) + ;; For some commands and buffer sources it is useful to configure the + ;; :preview-key on a per-command basis using the `consult-customize' macro. + (consult-customize + consult-theme :preview-key '(:debounce 0.2 any) + consult-ripgrep consult-git-grep consult-grep + consult-bookmark consult-recent-file consult-xref + consult--source-bookmark consult--source-file-register + consult--source-recent-file consult--source-project-recent-file + ;; :preview-key "M-." + :preview-key '(:debounce 0.4 any)) + + ;; Optionally configure the narrowing key. + ;; Both < and C-+ work reasonably well. + (setq consult-narrow-key "<") ;; "C-+" + + ;; Optionally make narrowing help available in the minibuffer. + ;; You may want to use `embark-prefix-help-command' or which-key instead. + ;; (define-key consult-narrow-map (vconcat consult-narrow-key "?") #'consult-narrow-help) + + ;; By default `consult-project-function' uses `project-root' from project.el. + ;; Optionally configure a different project root function. + ;;;; 1. project.el (the default) + ;; (setq consult-project-function #'consult--default-project--function) + ;;;; 2. vc.el (vc-root-dir) + ;; (setq consult-project-function (lambda (_) (vc-root-dir))) + ;;;; 3. locate-dominating-file + ;; (setq consult-project-function (lambda (_) (locate-dominating-file "." ".git"))) + ;;;; 4. projectile.el (projectile-project-root) + ;; (autoload 'projectile-project-root "projectile") + ;; (setq consult-project-function (lambda (_) (projectile-project-root))) + ;;;; 5. No project support + ;; (setq consult-project-function nil) +) +#+end_src + +** Custom variables +:properties: +:description: Short description of all customization settings +:end: +#+cindex: customization + +*TIP:* If you have [[https://github.com/minad/marginalia][Marginalia]] installed, type =M-x customize-variable RET +^consult= to see all Consult-specific customizable variables with their current +values and abbreviated description. Alternatively, type =C-h a ^consult= to get +an overview of all Consult variables and functions with their descriptions. + +| Variable | Description | +|----------------------------------+-----------------------------------------------------| +| consult-after-jump-hook | Functions to call after jumping to a location | +| consult-async-input-debounce | Input debounce for asynchronous commands | +| consult-async-input-throttle | Input throttle for asynchronous commands | +| consult-async-min-input | Minimum numbers of input characters | +| consult-async-refresh-delay | Refresh delay for asynchronous commands | +| consult-async-split-style | Splitting style used for async commands | +| consult-async-split-styles-alist | Available splitting styles used for async commands | +| consult-bookmark-narrow | Narrowing configuration for =consult-bookmark= | +| consult-buffer-filter | Filter for =consult-buffer= | +| consult-buffer-sources | List of virtual buffer sources | +| consult-fd-args | Command line arguments for fd | +| consult-find-args | Command line arguments for find | +| consult-fontify-max-size | Buffers larger than this limit are not fontified | +| consult-fontify-preserve | Preserve fontification for line-based commands. | +| consult-git-grep-args | Command line arguments for git-grep | +| consult-goto-line-numbers | Show line numbers for =consult-goto-line= | +| consult-grep-max-columns | Maximal number of columns of the matching lines | +| consult-grep-args | Command line arguments for grep | +| consult-imenu-config | Mode-specific configuration for =consult-imenu= | +| consult-line-numbers-widen | Show absolute line numbers when narrowing is active | +| consult-line-start-from-top | Start the =consult-line= search from the top | +| consult-locate-args | Command line arguments for locate | +| consult-man-args | Command line arguments for man | +| consult-mode-command-filter | Filter for =consult-mode-command= | +| consult-mode-histories | Mode-specific history variables | +| consult-narrow-key | Narrowing prefix key during completion | +| consult-point-placement | Placement of the point when jumping to matches | +| consult-preview-key | Keys which triggers preview | +| consult-preview-allowed-hooks | List of =find-file= hooks to enable during preview | +| consult-preview-excluded-files | Regexps matched against file names during preview | +| consult-preview-max-count | Maximum number of files to keep open during preview | +| consult-preview-partial-size | Files larger than this size are previewed partially | +| consult-preview-partial-chunk | Size of the file chunk which is previewed partially | +| consult-preview-variables | Alist of variables to bind during preview | +| consult-project-buffer-sources | List of virtual project buffer sources | +| consult-project-function | Function which returns current project root | +| consult-register-prefix | Prefix string for register keys during completion | +| consult-ripgrep-args | Command line arguments for ripgrep | +| consult-themes | List of themes to be presented for selection | +| consult-widen-key | Widening key during completion | +| consult-yank-rotate | Rotate kill ring | + +** Fine-tuning of individual commands +:properties: +:alt_title: Fine-tuning +:description: Fine-grained configuration for special requirements +:end: + +*NOTE:* Consult supports fine-grained customization of individual commands. This +configuration feature exists for experienced users with special requirements. +There is the [[https://github.com/minad/consult/wiki][Consult wiki]], where we collect further configuration examples. + +Commands and buffer sources allow flexible, individual customization by using +the =consult-customize= macro. You can override any option passed to the internal +=consult--read= API. Note that since =consult--read= is part of the internal API, +options could be removed, replaced or renamed in future versions of the package. + +Useful options are: +- =:prompt= set the prompt string +- =:preview-key= set the preview key, default is =consult-preview-key= +- =:initial= set the initial input +- =:default= set the default value +- =:history= set the history variable symbol +- =:add-history= add items to the future history, for example symbol at point +- =:sort= enable or disable sorting +- =:group= set to nil to disable candidate grouping and titles. +- =:inherit-input-method= set to non-nil to inherit the input method. + +#+begin_src emacs-lisp +(consult-customize + ;; Disable preview for `consult-theme' completely. + consult-theme :preview-key nil + ;; Set preview for `consult-buffer' to key `M-.' + consult-buffer :preview-key "M-." + ;; For `consult-line' change the prompt and specify multiple preview + ;; keybindings. Note that you should bind and in the + ;; `minibuffer-local-completion-map' or `vertico-map' to the commands which + ;; select the previous or next candidate. + consult-line :prompt "Search: " + :preview-key '("S-" "S-")) +#+end_src + +The configuration values are evaluated at runtime, just before the completion +session is started. Therefore you can use for example =thing-at-point= to adjust +the initial input or the future history. + +#+begin_src emacs-lisp +(consult-customize + consult-line + :add-history (seq-some #'thing-at-point '(region symbol))) + +(defalias 'consult-line-thing-at-point 'consult-line) + +(consult-customize + consult-line-thing-at-point + :initial (thing-at-point 'symbol)) +#+end_src + +Generally it is possible to modify commands for your individual needs by the +following techniques: + +1. Use =consult-customize= in order to change the command or source settings. +2. Create your own wrapper function which passes modified arguments to the Consult functions. +3. Create your own buffer [[#multiple-sources][multi sources]] for =consult-buffer=. +4. Create advices to modify some internal behavior. +5. Write or propose a patch. + +* Recommended packages +:properties: +:description: Related packages recommended for installation +:end: + +I use and recommend this combination of packages: + +- consult: This package +- [[https://github.com/minad/vertico][vertico]]: Fast and minimal vertical completion system +- [[https://github.com/minad/marginalia][marginalia]]: Annotations for the completion candidates +- [[https://github.com/oantolin/embark][embark and embark-consult]]: Action commands, which can act on the completion candidates +- [[https://github.com/oantolin/orderless][orderless]]: Completion style which offers flexible candidate filtering +- [[https://github.com/mhayashi1120/Emacs-wgrep][wgrep]]: Editing of grep buffers. Use with =consult-grep= via =embark-export=. + +There exist multiple fine completion UIs beside Vertico, which are supported by +Consult. Give them a try and find out which interaction model fits best for you. + +- The builtin completion UI, which pops up the =*Completions*= buffer. +- The builtin =icomplete-vertical-mode= in Emacs 28 or newer. +- [[https://git.sr.ht/~protesilaos/mct][mct by Protesilaos Stavrou]]: Minibuffer and Completions in Tandem, which builds + on the default completion UI. + +Note that all packages are independent and can be exchanged with alternative +components, since there exist no hard dependencies. Furthermore it is possible +to get started with only default completion and Consult and add more components +later to the mix. For example you can omit Marginalia if you don't need +annotations. I highly recommend the Embark package, but in order to familiarize +yourself with the other components, you can first start without it - or you could +use with Embark right away and add the other components later on. + +We document a [[https://github.com/minad/consult/wiki/Auxiliary-packages][list of auxiliary packages]] in the Consult wiki. These packages +integrate Consult with special programs or with other packages in the wider +Emacs ecosystem. + +* Bug reports +:properties: +:description: How to create reproducible bug reports +:end: + +If you find a bug or suspect that there is a problem with Consult, please carry +out the following steps: + +1. *Search through the issue tracker* if your issue has been reported before (and + has been resolved eventually) in the meantime. +2. *Remove all packages involved in the suspected bug from your installation.* +3. *Reinstall the newest version of all relevant packages*. Updating alone is not + sufficient, since package.el sometimes causes miscompilation. The list of + packages includes Consult, Compat, Vertico or other completion UIs, + Marginalia, Embark and Orderless. +4. Either use the default completion UI or ensure that exactly one of + =vertico-mode=, =mct-mode=, or =icomplete-mode= is enabled. The unsupported modes + =selectrum-mode=, =ivy-mode=, =helm-mode=, =ido-mode= and =ido-ubiquitous-mode= must be + disabled. +5. Ensure that the =completion-styles= variable is properly configured. Try to set + =completion-styles= to a list including =substring= or =orderless=. +6. Try to reproduce the issue with the newest stable Emacs version. Start a bare + bone Emacs instance with =emacs -Q= on the command line. Execute the following + minimal code snippets in the scratch buffer. This way we can exclude side + effects due to configuration settings. If other packages are relevant to + reproduce the issue, include them in the minimal configuration snippet. + +Minimal setup with Vertico for =emacs -Q=: +#+begin_src emacs-lisp +(package-initialize) +(require 'consult) +(require 'vertico) +(vertico-mode) +(setq completion-styles '(substring basic)) +#+end_src + +Minimal setup with the default completion system for =emacs -Q=: +#+begin_src emacs-lisp +(package-initialize) +(require 'consult) +(setq completion-styles '(substring basic)) +#+end_src + +Please provide the necessary important information with your bug report: + +- The minimal configuration snippet used to reproduce the issue. +- Your completion UI (Default completion, Vertico, Mct or Icomplete). +- A stack trace in case the bug triggers an exception. +- Your Emacs version, since bugs may be fixed or introduced in newer versions. +- Your operating system, since Emacs behavior varies subtly between Linux, Mac + and Windows. +- The package manager, e.g., straight.el or package.el, used to install the + Emacs packages, in order to exclude update issues. Did you install Consult as + part of the Doom Emacs distribution? +- Do you use Evil? Consult does not provide Evil integration out of the box, but + there is some support in [[https://github.com/emacs-evil/evil-collection][evil-collection]]. + +When evaluating Consult-related code snippets you should enable [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Lexical-Binding.html][lexical binding]]. +Consult often relies on lambdas and lexical closures. + +* Contributions +:properties: +:description: Feature requests and pull requests +:end: + +Consult is a community effort, please participate in the discussions. +Contributions are welcome, but you may want to discuss potential contributions +first. Since this package is part of [[https://elpa.gnu.org/packages/consult.html][GNU ELPA]] contributions require a copyright +assignment to the FSF. + +If you have a proposal, take a look at the [[https://github.com/consult/issues][Consult issue tracker]] and the [[https://github.com/minad/consult/issues/6][Consult +wishlist]]. There have been many prior feature discussions. Please search through +the issue tracker, maybe your issue or feature request has already been +discussed. You can contribute to the [[https://github.com/minad/consult/wiki][Consult wiki]], in case you want to share +small configuration or command snippets. + +* Acknowledgments +:properties: +:description: Contributors and Sources of Inspiration +:end: + +This package took inspiration from [[https://github.com/abo-abo/swiper#counsel][Counsel]] by Oleh Krehel. Some of the Consult +commands originated in the Counsel package or the wiki of the Selectrum package. +This package exists only thanks to the help of these great contributors and +thanks to the feedback of many users. Thank you! + +Code contributions: [[https://github.com/aagon][Aymeric Agon-Rambosson]], [[https://github.com/amosbird][Amos Bird]], [[https://github.com/ashton314][Ashton Wiersdorf]], [[https://github.com/aspiers/][Adam +Spiers]], [[https://github.com/astoff][Augusto Stoffel]], [[https://github.com/clemera/][Clemens Radermacher]], [[https://github.com/fuzy112][Zhengyi]], [[https://github.com/geolessel][Geoffrey Lessel]], [[https://github.com/iostapyshyn][Illia +Ostapyshyn]], [[https://github.com/jakanakaevangeli][jakanakaevangeli]], [[https://github.com/jdtsmith][JD Smith]], [[https://github.com/jyp][Jean-Philippe Bernardy]], [[https://github.com/mattiasdrp][mattiasdrp]], +[[https://github.com/mohamed-abdelnour][Mohamed Abdelnour]], [[https://github.com/mohkale][Mohsin Kaleem]], [[https://github.com/noctuid][Fox Kiester]], [[https://github.com/oantolin/][Omar Antolín Camarena]], [[https://github.com/okamsn/][Earl +Hyatt]], [[https://github.com/omar-polo][Omar Polo]], [[https://github.com/piotrkwiecinski][Piotr Kwiecinski]], [[https://github.com/rswgnu][Robert Weiner]], [[https://github.com/s-kostyaev/][Sergey Kostyaev]], [[https://github.com/scvalex][Alexandru +Scvorțov]], [[https://github.com/tecosaur][Tecosaur]], [[https://github.com/thisirs][Sylvain Rousseau]], [[https://github.com/tomfitzhenry/][Tom Fitzhenry]], [[https://hg.serna.eu][Iñigo Serna]] and [[https://github.com/akreisher][Alex +Kreisher]]. + +Advice and useful discussions: [[https://github.com/Qkessler][Enrique Kessler Martínez]], [[https://github.com/alphapapa/][Adam Porter]], [[https://github.com/bdarcus][Bruce +d'Arcus]], [[https://github.com/clemera/][Clemens Radermacher]], [[https://github.com/dgutov/][Dmitry Gutov]], [[https://github.com/hmelman/][Howard Melman]], [[https://github.com/iyefrat][Itai Y. Efrat]], [[https://github.com/jdtsmith][JD +Smith]], [[https://github.com/manuel-uberti/][Manuel Uberti]], [[https://github.com/monnier/][Stefan Monnier]], [[https://github.com/oantolin/][Omar Antolín Camarena]], [[https://github.com/purcell/][Steve Purcell]], +[[https://github.com/raxod502][Radon Rosborough]], [[https://github.com/tomfitzhenry/][Tom Fitzhenry]] and [[https://protesilaos.com][Protesilaos Stavrou]]. + +#+html: blob - /dev/null blob + 2158c876254d5ff21b2fae26f9bbc13c9c8ff23c (mode 644) --- /dev/null +++ elpa/consult-1.5/consult-autoloads.el @@ -0,0 +1,445 @@ +;;; consult-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 consult.el + +(autoload 'consult-completion-in-region "consult" "\ +Use minibuffer completion as the UI for `completion-at-point'. + +The function is called with 4 arguments: START END COLLECTION +PREDICATE. The arguments and expected return value are as +specified for `completion-in-region'. Use this function as a +value for `completion-in-region-function'. + +(fn START END COLLECTION &optional PREDICATE)") +(autoload 'consult-outline "consult" "\ +Jump to an outline heading, obtained by matching against `outline-regexp'. + +This command supports narrowing to a heading level and candidate +preview. The initial narrowing LEVEL can be given as prefix +argument. The symbol at point is added to the future history. + +(fn &optional LEVEL)" t) +(autoload 'consult-mark "consult" "\ +Jump to a marker in MARKERS list (defaults to buffer-local `mark-ring'). + +The command supports preview of the currently selected marker position. +The symbol at point is added to the future history. + +(fn &optional MARKERS)" t) +(autoload 'consult-global-mark "consult" "\ +Jump to a marker in MARKERS list (defaults to `global-mark-ring'). + +The command supports preview of the currently selected marker position. +The symbol at point is added to the future history. + +(fn &optional MARKERS)" t) +(autoload 'consult-line "consult" "\ +Search for a matching line. + +Depending on the setting `consult-point-placement' the command +jumps to the beginning or the end of the first match on the line +or the line beginning. The default candidate is the non-empty +line next to point. This command obeys narrowing. Optional +INITIAL input can be provided. The search starting point is +changed if the START prefix argument is set. The symbol at point +and the last `isearch-string' is added to the future history. + +(fn &optional INITIAL START)" t) +(autoload 'consult-line-multi "consult" "\ +Search for a matching line in multiple buffers. + +By default search across all project buffers. If the prefix +argument QUERY is non-nil, all buffers are searched. Optional +INITIAL input can be provided. The symbol at point and the last +`isearch-string' is added to the future history. In order to +search a subset of buffers, QUERY can be set to a plist according +to `consult--buffer-query'. + +(fn QUERY &optional INITIAL)" t) +(autoload 'consult-keep-lines "consult" "\ +Select a subset of the lines in the current buffer with live preview. + +The selected lines are kept and the other lines are deleted. When called +interactively, the lines selected are those that match the minibuffer input. In +order to match the inverse of the input, prefix the input with `! '. When +called from Elisp, the filtering is performed by a FILTER function. This +command obeys narrowing. + +FILTER is the filter function. +INITIAL is the initial input. + +(fn FILTER &optional INITIAL)" t) +(autoload 'consult-focus-lines "consult" "\ +Hide or show lines using overlays. + +The selected lines are shown and the other lines hidden. When called +interactively, the lines selected are those that match the minibuffer input. In +order to match the inverse of the input, prefix the input with `! '. With +optional prefix argument SHOW reveal the hidden lines. Alternatively the +command can be restarted to reveal the lines. When called from Elisp, the +filtering is performed by a FILTER function. This command obeys narrowing. + +FILTER is the filter function. +INITIAL is the initial input. + +(fn FILTER &optional SHOW INITIAL)" t) +(autoload 'consult-goto-line "consult" "\ +Read line number and jump to the line with preview. + +Enter either a line number to jump to the first column of the +given line or line:column in order to jump to a specific column. +Jump directly if a line number is given as prefix ARG. The +command respects narrowing and the settings +`consult-goto-line-numbers' and `consult-line-numbers-widen'. + +(fn &optional ARG)" t) +(autoload 'consult-recent-file "consult" "\ +Find recent file using `completing-read'." t) +(autoload 'consult-mode-command "consult" "\ +Run a command from any of the given MODES. + +If no MODES are specified, use currently active major and minor modes. + +(fn &rest MODES)" t) +(autoload 'consult-yank-from-kill-ring "consult" "\ +Select STRING from the kill ring and insert it. +With prefix ARG, put point at beginning, and mark at end, like `yank' does. + +This command behaves like `yank-from-kill-ring' in Emacs 28, which also offers +a `completing-read' interface to the `kill-ring'. Additionally the Consult +version supports preview of the selected string. + +(fn STRING &optional ARG)" t) +(autoload 'consult-yank-pop "consult" "\ +If there is a recent yank act like `yank-pop'. + +Otherwise select string from the kill ring and insert it. +See `yank-pop' for the meaning of ARG. + +This command behaves like `yank-pop' in Emacs 28, which also offers a +`completing-read' interface to the `kill-ring'. Additionally the Consult +version supports preview of the selected string. + +(fn &optional ARG)" t) +(autoload 'consult-yank-replace "consult" "\ +Select STRING from the kill ring. + +If there was no recent yank, insert the string. +Otherwise replace the just-yanked string with the selected string. + +There exists no equivalent of this command in Emacs 28. + +(fn STRING)" t) +(autoload 'consult-bookmark "consult" "\ +If bookmark NAME exists, open it, otherwise create a new bookmark with NAME. + +The command supports preview of file bookmarks and narrowing. See the +variable `consult-bookmark-narrow' for the narrowing configuration. + +(fn NAME)" t) +(autoload 'consult-complex-command "consult" "\ +Select and evaluate command from the command history. + +This command can act as a drop-in replacement for `repeat-complex-command'." t) +(autoload 'consult-history "consult" "\ +Insert string from HISTORY of current buffer. +In order to select from a specific HISTORY, pass the history +variable as argument. INDEX is the name of the index variable to +update, if any. BOL is the function which jumps to the beginning +of the prompt. See also `cape-history' from the Cape package. + +(fn &optional HISTORY INDEX BOL)" t) +(autoload 'consult-isearch-history "consult" "\ +Read a search string with completion from the Isearch history. + +This replaces the current search string if Isearch is active, and +starts a new Isearch session otherwise." t) +(autoload 'consult-minor-mode-menu "consult" "\ +Enable or disable minor mode. + +This is an alternative to `minor-mode-menu-from-indicator'." t) +(autoload 'consult-theme "consult" "\ +Disable current themes and enable THEME from `consult-themes'. + +The command supports previewing the currently selected theme. + +(fn THEME)" t) +(autoload 'consult-buffer "consult" "\ +Enhanced `switch-to-buffer' command with support for virtual buffers. + +The command supports recent files, bookmarks, views and project files as +virtual buffers. Buffers are previewed. Narrowing to buffers (b), files (f), +bookmarks (m) and project files (p) is supported via the corresponding +keys. In order to determine the project-specific files and buffers, the +`consult-project-function' is used. The virtual buffer SOURCES +default to `consult-buffer-sources'. See `consult--multi' for the +configuration of the virtual buffer sources. + +(fn &optional SOURCES)" t) +(autoload 'consult-project-buffer "consult" "\ +Enhanced `project-switch-to-buffer' command with support for virtual buffers. +The command may prompt you for a project directory if it is invoked from +outside a project. See `consult-buffer' for more details." t) +(autoload 'consult-buffer-other-window "consult" "\ +Variant of `consult-buffer', switching to a buffer in another window." t) +(autoload 'consult-buffer-other-frame "consult" "\ +Variant of `consult-buffer', switching to a buffer in another frame." t) +(autoload 'consult-buffer-other-tab "consult" "\ +Variant of `consult-buffer', switching to a buffer in another tab." t) +(autoload 'consult-grep "consult" "\ +Search with `grep' for files in DIR where the content matches a regexp. + +The initial input is given by the INITIAL argument. DIR can be +nil, a directory string or a list of file/directory paths. If +`consult-grep' is called interactively with a prefix argument, +the user can specify the directories or files to search in. +Multiple directories must be separated by comma in the +minibuffer, since they are read via `completing-read-multiple'. +By default the project directory is used if +`consult-project-function' is defined and returns non-nil. +Otherwise the `default-directory' is searched. + +The input string is split, the first part of the string (grep +input) is passed to the asynchronous grep process and the second +part of the string is passed to the completion-style filtering. + +The input string is split at a punctuation character, which is +given as the first character of the input string. The format is +similar to Perl-style regular expressions, e.g., /regexp/. +Furthermore command line options can be passed to grep, specified +behind --. The overall prompt input has the form +`#async-input -- grep-opts#filter-string'. + +Note that the grep input string is transformed from Emacs regular +expressions to Posix regular expressions. Always enter Emacs +regular expressions at the prompt. `consult-grep' behaves like +builtin Emacs search commands, e.g., Isearch, which take Emacs +regular expressions. Furthermore the asynchronous input split +into words, each word must match separately and in any order. +See `consult--regexp-compiler' for the inner workings. In order +to disable transformations of the grep input, adjust +`consult--regexp-compiler' accordingly. + +Here we give a few example inputs: + +#alpha beta : Search for alpha and beta in any order. +#alpha.*beta : Search for alpha before beta. +#\\(alpha\\|beta\\) : Search for alpha or beta (Note Emacs syntax!) +#word -- -C3 : Search for word, include 3 lines as context +#first#second : Search for first, quick filter for second. + +The symbol at point is added to the future history. + +(fn &optional DIR INITIAL)" t) +(autoload 'consult-git-grep "consult" "\ +Search with `git grep' for files in DIR with INITIAL input. +See `consult-grep' for details. + +(fn &optional DIR INITIAL)" t) +(autoload 'consult-ripgrep "consult" "\ +Search with `rg' for files in DIR with INITIAL input. +See `consult-grep' for details. + +(fn &optional DIR INITIAL)" t) +(autoload 'consult-find "consult" "\ +Search for files with `find' in DIR. +The file names must match the input regexp. INITIAL is the +initial minibuffer input. See `consult-grep' for details +regarding the asynchronous search and the arguments. + +(fn &optional DIR INITIAL)" t) +(autoload 'consult-fd "consult" "\ +Search for files with `fd' in DIR. +The file names must match the input regexp. INITIAL is the +initial minibuffer input. See `consult-grep' for details +regarding the asynchronous search and the arguments. + +(fn &optional DIR INITIAL)" t) +(autoload 'consult-locate "consult" "\ +Search with `locate' for files which match input given INITIAL input. + +The input is treated literally such that locate can take advantage of +the locate database index. Regular expressions would often force a slow +linear search through the entire database. The locate process is started +asynchronously, similar to `consult-grep'. See `consult-grep' for more +details regarding the asynchronous search. + +(fn &optional INITIAL)" t) +(autoload 'consult-man "consult" "\ +Search for man page given INITIAL input. + +The input string is not preprocessed and passed literally to the +underlying man commands. The man process is started asynchronously, +similar to `consult-grep'. See `consult-grep' for more details regarding +the asynchronous search. + +(fn &optional INITIAL)" t) +(register-definition-prefixes "consult" '("consult-")) + + +;;; Generated autoloads from consult-compile.el + +(autoload 'consult-compile-error "consult-compile" "\ +Jump to a compilation error in the current buffer. + +This command collects entries from compilation buffers and grep +buffers related to the current buffer. The command supports +preview of the currently selected error." t) +(register-definition-prefixes "consult-compile" '("consult-compile--")) + + +;;; Generated autoloads from consult-flymake.el + +(autoload 'consult-flymake "consult-flymake" "\ +Jump to Flymake diagnostic. +When PROJECT is non-nil then prompt with diagnostics from all +buffers in the current project instead of just the current buffer. + +(fn &optional PROJECT)" t) +(register-definition-prefixes "consult-flymake" '("consult-flymake--")) + + +;;; Generated autoloads from consult-imenu.el + +(autoload 'consult-imenu "consult-imenu" "\ +Select item from flattened `imenu' using `completing-read' with preview. + +The command supports preview and narrowing. See the variable +`consult-imenu-config', which configures the narrowing. +The symbol at point is added to the future history. + +See also `consult-imenu-multi'." t) +(autoload 'consult-imenu-multi "consult-imenu" "\ +Select item from the imenus of all buffers from the same project. + +In order to determine the buffers belonging to the same project, the +`consult-project-function' is used. Only the buffers with the +same major mode as the current buffer are used. See also +`consult-imenu' for more details. In order to search a subset of buffers, +QUERY can be set to a plist according to `consult--buffer-query'. + +(fn &optional QUERY)" t) +(register-definition-prefixes "consult-imenu" '("consult-imenu-")) + + +;;; Generated autoloads from consult-info.el + +(autoload 'consult-info "consult-info" "\ +Full text search through info MANUALS. + +(fn &rest MANUALS)" t) +(register-definition-prefixes "consult-info" '("consult-info--")) + + +;;; Generated autoloads from consult-kmacro.el + +(autoload 'consult-kmacro "consult-kmacro" "\ +Run a chosen keyboard macro. + +With prefix ARG, run the macro that many times. +Macros containing mouse clicks are omitted. + +(fn ARG)" t) +(register-definition-prefixes "consult-kmacro" '("consult-kmacro--")) + + +;;; Generated autoloads from consult-org.el + +(autoload 'consult-org-heading "consult-org" "\ +Jump to an Org heading. + +MATCH and SCOPE are as in `org-map-entries' and determine which +entries are offered. By default, all entries of the current +buffer are offered. + +(fn &optional MATCH SCOPE)" t) +(autoload 'consult-org-agenda "consult-org" "\ +Jump to an Org agenda heading. + +By default, all agenda entries are offered. MATCH is as in +`org-map-entries' and can used to refine this. + +(fn &optional MATCH)" t) +(register-definition-prefixes "consult-org" '("consult-org--")) + + +;;; Generated autoloads from consult-register.el + +(autoload 'consult-register-window "consult-register" "\ +Enhanced drop-in replacement for `register-preview'. + +BUFFER is the window buffer. +SHOW-EMPTY must be t if the window should be shown for an empty register list. + +(fn BUFFER &optional SHOW-EMPTY)") +(autoload 'consult-register-format "consult-register" "\ +Enhanced preview of register REG. +This function can be used as `register-preview-function'. +If COMPLETION is non-nil format the register for completion. + +(fn REG &optional COMPLETION)") +(autoload 'consult-register "consult-register" "\ +Load register and either jump to location or insert the stored text. + +This command is useful to search the register contents. For quick access +to registers it is still recommended to use the register functions +`consult-register-load' and `consult-register-store' or the built-in +built-in register access functions. The command supports narrowing, see +`consult-register--narrow'. Marker positions are previewed. See +`jump-to-register' and `insert-register' for the meaning of prefix ARG. + +(fn &optional ARG)" t) +(autoload 'consult-register-load "consult-register" "\ +Do what I mean with a REG. + +For a window configuration, restore it. For a number or text, insert it. +For a location, jump to it. See `jump-to-register' and `insert-register' +for the meaning of prefix ARG. + +(fn REG &optional ARG)" t) +(autoload 'consult-register-store "consult-register" "\ +Store register dependent on current context, showing an action menu. + +With an active region, store/append/prepend the contents, optionally +deleting the region when a prefix ARG is given. With a numeric prefix +ARG, store or add the number. Otherwise store point, frameset, window or +kmacro. + +(fn ARG)" t) +(register-definition-prefixes "consult-register" '("consult-register-")) + + +;;; Generated autoloads from consult-xref.el + +(autoload 'consult-xref "consult-xref" "\ +Show xrefs with preview in the minibuffer. + +This function can be used for `xref-show-xrefs-function'. +See `xref-show-xrefs-function' for the description of the +FETCHER and ALIST arguments. + +(fn FETCHER &optional ALIST)") +(register-definition-prefixes "consult-xref" '("consult-xref--")) + +;;; End of scraped data + +(provide 'consult-autoloads) + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; no-native-compile: t +;; coding: utf-8-emacs-unix +;; End: + +;;; consult-autoloads.el ends here blob - /dev/null blob + 2d8c4bdf3c27f6c9b722e9376c583ebc6b926beb (mode 644) --- /dev/null +++ elpa/consult-1.5/consult-compile.el @@ -0,0 +1,127 @@ +;;; consult-compile.el --- Provides the command `consult-compile-error' -*- lexical-binding: t -*- + +;; Copyright (C) 2021-2024 Free Software Foundation, Inc. + +;; This file is 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: + +;; Provides the command `consult-compile-error'. This is an extra +;; package, to allow lazy loading of compile.el. The +;; `consult-compile-error' command is autoloaded. + +;;; Code: + +(require 'consult) +(require 'compile) + +(defvar consult-compile--history nil) + +(defconst consult-compile--narrow + '((?e . "Error") + (?w . "Warning") + (?i . "Info"))) + +(defun consult-compile--font-lock (str) + "Apply `font-lock' faces in STR, copy them to `face'." + (let ((pos 0) (len (length str))) + (while (< pos len) + (let* ((face (get-text-property pos 'font-lock-face str)) + (end (or (text-property-not-all pos len 'font-lock-face face str) len))) + (put-text-property pos end 'face face str) + (setq pos end))) + str)) + +(defun consult-compile--error-candidates (buffer) + "Return alist of errors and positions in BUFFER, a compilation buffer." + (with-current-buffer buffer + (let ((candidates) + (pos (point-min))) + (save-excursion + (while (setq pos (compilation-next-single-property-change pos 'compilation-message)) + (when-let (msg (get-text-property pos 'compilation-message)) + (goto-char pos) + (push (propertize + (consult-compile--font-lock (consult--buffer-substring pos (pos-eol))) + 'consult--type (pcase (compilation--message->type msg) + (0 ?i) + (1 ?w) + (_ ?e)) + 'consult--candidate (point-marker)) + candidates)))) + (nreverse candidates)))) + +(defun consult-compile--lookup (marker) + "Lookup error position given error MARKER." + (when-let (buffer (and marker (marker-buffer marker))) + (with-current-buffer buffer + (let ((next-error-highlight nil) + (compilation-current-error marker) + (overlay-arrow-position overlay-arrow-position)) + (ignore-errors + (save-window-excursion + (compilation-next-error-function 0) + (point-marker))))))) + +(defun consult-compile--compilation-buffers (file) + "Return a list of compilation buffers relevant to FILE." + (consult--buffer-query + :sort 'alpha :predicate + (lambda (buffer) + (with-current-buffer buffer + (and (compilation-buffer-internal-p) + (file-in-directory-p file default-directory)))))) + +(defun consult-compile--state () + "Like `consult--jump-state', also setting the current compilation error." + (let ((jump (consult--jump-state))) + (lambda (action marker) + (let ((pos (consult-compile--lookup marker))) + (when-let (buffer (and (eq action 'return) + marker + (marker-buffer marker))) + (with-current-buffer buffer + (setq compilation-current-error marker + overlay-arrow-position marker))) + (funcall jump action pos))))) + +;;;###autoload +(defun consult-compile-error () + "Jump to a compilation error in the current buffer. + +This command collects entries from compilation buffers and grep +buffers related to the current buffer. The command supports +preview of the currently selected error." + (interactive) + (consult--read + (or (mapcan #'consult-compile--error-candidates + (or (consult-compile--compilation-buffers + default-directory) + (user-error "No compilation buffers found for the current buffer"))) + (user-error "No compilation errors found")) + :prompt "Go to error: " + :category 'consult-compile-error + :sort nil + :require-match t + :history t ;; disable history + :lookup #'consult--lookup-candidate + :group (consult--type-group consult-compile--narrow) + :narrow (consult--type-narrow consult-compile--narrow) + :history '(:input consult-compile--history) + :state (consult-compile--state))) + +(provide 'consult-compile) +;;; consult-compile.el ends here blob - /dev/null blob + 3061ffe7426e58663cf9916e16b6e30e0783f04c (mode 644) --- /dev/null +++ elpa/consult-1.5/consult-flymake.el @@ -0,0 +1,116 @@ +;;; consult-flymake.el --- Provides the command `consult-flymake' -*- lexical-binding: t -*- + +;; Copyright (C) 2021-2024 Free Software Foundation, Inc. + +;; This file is 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: + +;; Provides the command `consult-flymake'. This is an extra package, +;; to allow lazy loading of flymake.el. The `consult-flymake' command +;; is autoloaded. + +;;; Code: + +(require 'consult) +(require 'flymake) +(eval-when-compile (require 'cl-lib)) + +(defconst consult-flymake--narrow + '((?e . "Error") + (?w . "Warning") + (?n . "Note"))) + +(defun consult-flymake--candidates (diags) + "Return Flymake errors from DIAGS as formatted candidates. +DIAGS should be a list of diagnostics as returned from `flymake-diagnostics'." + (let* ((diags + (mapcar + (lambda (diag) + (let ((buffer (flymake-diagnostic-buffer diag)) + (type (flymake-diagnostic-type diag))) + (when (buffer-live-p buffer) + (with-current-buffer buffer + (save-excursion + (without-restriction + (goto-char (flymake-diagnostic-beg diag)) + (list (buffer-name buffer) + (line-number-at-pos) + type + (flymake-diagnostic-text diag) + (point-marker) + (flymake-diagnostic-end diag) + (pcase (flymake--lookup-type-property type 'flymake-category) + ('flymake-error ?e) + ('flymake-warning ?w) + (_ ?n))))))))) + diags)) + (diags (or (delq nil diags) + (user-error "No flymake errors (Status: %s)" + (if (seq-difference (flymake-running-backends) + (flymake-reporting-backends)) + 'running 'finished)))) + (buffer-width (cl-loop for x in diags maximize (length (nth 0 x)))) + (line-width (cl-loop for x in diags maximize (length (number-to-string (nth 1 x))))) + (fmt (format "%%-%ds %%-%dd %%-7s %%s" buffer-width line-width))) + (mapcar + (pcase-lambda (`(,buffer ,line ,type ,text ,beg ,end ,narrow)) + (propertize (format fmt buffer line + (propertize (format "%s" (flymake--lookup-type-property + type 'flymake-type-name type)) + 'face (flymake--lookup-type-property + type 'mode-line-face 'flymake-error)) + text) + 'consult--candidate (list beg (cons 0 (- end beg))) + 'consult--type narrow)) + ;; Sort by buffer, severity and position. + (sort diags + (pcase-lambda (`(,b1 _ ,t1 _ ,m1 _) `(,b2 _ ,t2 _ ,m2 _)) + (let ((s1 (flymake--severity t1)) + (s2 (flymake--severity t2))) + (or + (string-lessp b1 b2) + (and (string-equal b1 b2) + (or + (> s1 s2) + (and (= s1 s2) + (< m1 m2))))))))))) + +;;;###autoload +(defun consult-flymake (&optional project) + "Jump to Flymake diagnostic. +When PROJECT is non-nil then prompt with diagnostics from all +buffers in the current project instead of just the current buffer." + (interactive "P") + (consult--forbid-minibuffer) + (consult--read + (consult-flymake--candidates + (if-let (((and project (fboundp 'flymake--project-diagnostics))) + (project (project-current))) + (flymake--project-diagnostics project) + (flymake-diagnostics))) + :prompt "Flymake diagnostic: " + :category 'consult-flymake-error + :history t ;; disable history + :require-match t + :sort nil + :group (consult--type-group consult-flymake--narrow) + :narrow (consult--type-narrow consult-flymake--narrow) + :lookup #'consult--lookup-candidate + :state (consult--jump-state))) + +(provide 'consult-flymake) +;;; consult-flymake.el ends here blob - /dev/null blob + 3b072a60bc07611d89c53e55be057b219c5c074a (mode 644) --- /dev/null +++ elpa/consult-1.5/consult-imenu.el @@ -0,0 +1,260 @@ +;;; consult-imenu.el --- Consult commands for imenu -*- lexical-binding: t -*- + +;; Copyright (C) 2021-2024 Free Software Foundation, Inc. + +;; This file is 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: + +;; Provides imenu-related Consult commands. + +;;; Code: + +(require 'consult) +(require 'imenu) + +(defcustom consult-imenu-config + '((emacs-lisp-mode :toplevel "Functions" + :types ((?f "Functions" font-lock-function-name-face) + (?m "Macros" font-lock-function-name-face) + (?p "Packages" font-lock-constant-face) + (?t "Types" font-lock-type-face) + (?v "Variables" font-lock-variable-name-face)))) + "Imenu configuration, faces and narrowing keys used by `consult-imenu'. + +For each type a narrowing key and a name must be specified. The +face is optional. The imenu representation provided by the +backend usually puts functions directly at the toplevel. +`consult-imenu' moves them instead under the type specified by +:toplevel." + :type '(repeat (cons symbol plist)) + :group 'consult) + +(defface consult-imenu-prefix + '((t :inherit consult-key)) + "Face used to highlight imenu prefix in `consult-imenu'." + :group 'consult-faces) + +(defvar consult-imenu--history nil) +(defvar-local consult-imenu--cache nil) + +(defun consult-imenu--switch-buffer (name pos buf fn &rest args) + "Switch buffer before invoking special menu items. +NAME is the item name. +POS is the position. +BUF is the buffer. +FN is the original special item function. +ARGS are the arguments to the special item function." + (funcall consult--buffer-display buf) + (apply fn name pos args)) + +(defun consult-imenu--normalize (pos) + "Return normalized imenu POS." + (pcase pos + ;; Create marker from integer item + ((pred integerp) (setq pos (copy-marker pos))) + ;; Semantic uses overlay for positions + ((pred overlayp) (setq pos (copy-marker (overlay-start pos)))) + ;; Wrap special item + (`(,pos ,fn . ,args) + (setq pos `(,pos ,#'consult-imenu--switch-buffer ,(current-buffer) + ,fn ,@args)))) + (if (or (consp pos) + (eq imenu-default-goto-function #'imenu-default-goto-function)) + pos + (list pos #'consult-imenu--switch-buffer (current-buffer) + imenu-default-goto-function))) + +(defun consult-imenu--flatten (prefix face list types) + "Flatten imenu LIST. +PREFIX is prepended in front of all items. +FACE is the item face. +TYPES is the mode-specific types configuration." + (mapcan + (lambda (item) + (if (imenu--subalist-p item) + (let* ((name (concat (car item))) + (next-prefix name) + (next-face face)) + (add-face-text-property 0 (length name) + 'consult-imenu-prefix 'append name) + (if prefix + (setq next-prefix (concat prefix "/" name)) + (when-let (type (cdr (assoc name types))) + (put-text-property 0 (length name) 'consult--type (car type) name) + (setq next-face (cadr type)))) + (consult-imenu--flatten next-prefix next-face (cdr item) types)) + (list (cons + (if prefix + (let ((key (concat prefix " " (car item)))) + (add-face-text-property (1+ (length prefix)) (length key) + face 'append key) + key) + (car item)) + (consult-imenu--normalize (cdr item)))))) + list)) + +(defun consult-imenu--compute () + "Compute imenu candidates." + (consult--forbid-minibuffer) + (let* ((imenu-use-markers t) + ;; Generate imenu, see `imenu--make-index-alist'. + (items (imenu--truncate-items + (save-excursion + (without-restriction + (funcall imenu-create-index-function))))) + (config (cdr (seq-find (lambda (x) (derived-mode-p (car x))) consult-imenu-config)))) + ;; Fix toplevel items, e.g., emacs-lisp-mode toplevel items are functions + (when-let (toplevel (plist-get config :toplevel)) + (let ((tops (seq-remove (lambda (x) (listp (cdr x))) items)) + (rest (seq-filter (lambda (x) (listp (cdr x))) items))) + (setq items (nconc rest (and tops (list (cons toplevel tops))))))) + ;; Apply our flattening in order to ease searching the imenu. + (consult-imenu--flatten + nil nil items + (mapcar (pcase-lambda (`(,x ,y ,z)) (list y x z)) + (plist-get config :types))))) + +(defun consult-imenu--deduplicate (items) + "Deduplicate imenu ITEMS by appending a counter." + ;; Some imenu backends generate duplicate items (e.g. for overloaded methods in java) + (let ((ht (make-hash-table :test #'equal :size (length items)))) + (dolist (item items) + (if-let (count (gethash (car item) ht)) + (setcar item (format "%s (%s)" (car item) + (puthash (car item) (1+ count) ht))) + (puthash (car item) 0 ht))))) + +(defun consult-imenu--items () + "Return cached imenu candidates, may error." + (unless (equal (car consult-imenu--cache) (buffer-modified-tick)) + (setq consult-imenu--cache (cons (buffer-modified-tick) (consult-imenu--compute)))) + (cdr consult-imenu--cache)) + +(defun consult-imenu--items-safe () + "Return cached imenu candidates, will not error." + (condition-case err + (consult-imenu--items) + (t (message "Cannot create Imenu for buffer %s (%s)" + (buffer-name) (error-message-string err)) + nil))) + +(defun consult-imenu--multi-items (buffers) + "Return all imenu items from BUFFERS." + (consult--with-increased-gc + (let ((reporter (make-progress-reporter "Collecting" 0 (length buffers)))) + (prog1 + (apply #'append + (seq-map-indexed (lambda (buf idx) + (with-current-buffer buf + (prog1 (consult-imenu--items-safe) + (progress-reporter-update + reporter (1+ idx) (buffer-name))))) + buffers)) + (progress-reporter-done reporter))))) + +(defun consult-imenu--jump (item) + "Jump to imenu ITEM via `consult--jump'. +In contrast to the builtin `imenu' jump function, +this function can jump across buffers." + (pcase item + (`(,name ,pos ,fn . ,args) + (push-mark nil t) + (apply fn name pos args)) + (`(,_ . ,pos) + (consult--jump pos)) + (_ (error "Unknown imenu item: %S" item))) + (run-hooks 'imenu-after-jump-hook)) + +(defun consult-imenu--narrow () + "Return narrowing configuration for the current buffer." + (mapcar (lambda (x) (cons (car x) (cadr x))) + (plist-get (cdr (seq-find (lambda (x) (derived-mode-p (car x))) + consult-imenu-config)) + :types))) + +(defun consult-imenu--group () + "Create a imenu group function for the current buffer." + (when-let (narrow (consult-imenu--narrow)) + (lambda (cand transform) + (let ((type (get-text-property 0 'consult--type cand))) + (cond + ((and transform type) + (substring cand (1+ (next-single-property-change 0 'consult--type cand)))) + (transform cand) + (type (alist-get type narrow))))))) + +(defun consult-imenu--select (prompt items) + "Select from imenu ITEMS given PROMPT string." + (consult-imenu--deduplicate items) + (consult-imenu--jump + (consult--read + (or items (user-error "Imenu is empty")) + :state + (let ((preview (consult--jump-preview))) + (lambda (action cand) + ;; Only preview simple menu items which are markers, + ;; in order to avoid any bad side effects. + (funcall preview action (and (markerp (cdr cand)) (cdr cand))))) + :narrow + (when-let (narrow (consult-imenu--narrow)) + (list :predicate + (lambda (cand) + (eq (get-text-property 0 'consult--type (car cand)) consult--narrow)) + :keys narrow)) + :group (consult-imenu--group) + :prompt prompt + :require-match t + :category 'imenu + :lookup #'consult--lookup-cons + :history 'consult-imenu--history + :add-history (thing-at-point 'symbol) + :sort nil))) + +;;;###autoload +(defun consult-imenu () + "Select item from flattened `imenu' using `completing-read' with preview. + +The command supports preview and narrowing. See the variable +`consult-imenu-config', which configures the narrowing. +The symbol at point is added to the future history. + +See also `consult-imenu-multi'." + (interactive) + (consult-imenu--select + "Go to item: " + (consult--slow-operation "Building Imenu..." + (consult-imenu--items)))) + +;;;###autoload +(defun consult-imenu-multi (&optional query) + "Select item from the imenus of all buffers from the same project. + +In order to determine the buffers belonging to the same project, the +`consult-project-function' is used. Only the buffers with the +same major mode as the current buffer are used. See also +`consult-imenu' for more details. In order to search a subset of buffers, +QUERY can be set to a plist according to `consult--buffer-query'." + (interactive "P") + (unless (keywordp (car-safe query)) + (setq query (list :sort 'alpha :mode major-mode + :directory (and (not query) 'project)))) + (let ((buffers (consult--buffer-query-prompt "Go to item" query))) + (consult-imenu--select (car buffers) + (consult-imenu--multi-items (cdr buffers))))) + +(provide 'consult-imenu) +;;; consult-imenu.el ends here blob - /dev/null blob + 8c58caff145bcfeb1c0c42f662ebb45733b44fd6 (mode 644) --- /dev/null +++ elpa/consult-1.5/consult-info.el @@ -0,0 +1,181 @@ +;;; consult-info.el --- Search through the info manuals -*- lexical-binding: t -*- + +;; Copyright (C) 2021-2024 Free Software Foundation, Inc. + +;; This file is 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: + +;; Provides the command `consult-info'. This is an extra package, +;; to allow lazy loading of info.el. The `consult-info' command +;; is autoloaded. + +;;; Code: + +(require 'consult) +(require 'info) + +(defvar consult-info--history nil) + +(defun consult-info--candidates (manuals input) + "Dynamically find lines in MANUALS matching INPUT." + (pcase-let* ((`(,regexps . ,hl) + (funcall consult--regexp-compiler input 'emacs t)) + (re (concat "\\(\^_\n\\(?:.*Node:[ \t]*\\([^,\t\n]+\\)\\)?.*\n\\)\\|" (car regexps))) + (candidates nil) + (cand-idx 0) + (last-node nil) + (full-node nil)) + (pcase-dolist (`(,manual . ,buf) manuals) + (with-current-buffer buf + (setq last-node nil full-node nil) + (widen) + (goto-char (point-min)) + ;; TODO Info has support for subfiles, which is currently not supported + ;; by the `consult-info' search routine. Fortunately most (or all?) + ;; Emacs info files are generated with the --no-split option. See the + ;; comment in doc/emacs/Makefile.in. Given the computing powers these + ;; days split info files are probably also not necessary anymore. + ;; However it could happen that info files installed as part of the + ;; Linux distribution are split. + (while (and (not (eobp)) (re-search-forward re nil t)) + (if (match-end 1) + (progn + (if-let ((node (match-string 2))) + (unless (equal node last-node) + (setq full-node (concat "(" manual ")" node) + last-node node)) + (setq last-node nil full-node nil)) + (goto-char (1+ (pos-eol)))) + (let ((bol (pos-bol)) + (eol (pos-eol))) + (goto-char bol) + (when (and + full-node + ;; Information separator character + (>= (- (point) 2) (point-min)) + (not (eq (char-after (- (point) 2)) ?\^_)) + ;; Non-blank line, only printable characters on the line. + (not (looking-at-p "^\\s-*$")) + (looking-at-p "^[[:print:]]*$") + ;; Matches all regexps + (seq-every-p (lambda (r) + (goto-char bol) + (re-search-forward r eol t)) + (cdr regexps))) + (let ((cand (concat + (funcall hl (buffer-substring-no-properties bol eol)) + (consult--tofu-encode cand-idx)))) + (put-text-property 0 1 'consult--info (list full-node bol buf) cand) + (cl-incf cand-idx) + (push cand candidates))) + (goto-char (1+ eol))))))) + (nreverse candidates))) + +(defun consult-info--position (cand) + "Return position information for CAND." + (when-let ((pos (and cand (get-text-property 0 'consult--info cand))) + (matches (consult--point-placement cand 0)) + (dest (+ (cadr pos) (car matches)))) + `( ,(cdr matches) ,dest . ,pos))) + +(defun consult-info--action (cand) + "Jump to info CAND." + (pcase (consult-info--position cand) + (`( ,_matches ,pos ,node ,_bol ,_buf) + (info node) + (widen) + (goto-char pos) + (Info-select-node) + (run-hooks 'consult-after-jump-hook)))) + +(defun consult-info--state () + "Info manual preview state." + (let ((preview (consult--jump-preview))) + (lambda (action cand) + (pcase action + ('preview + (setq cand (consult-info--position cand)) + (funcall preview 'preview + (pcase cand + (`(,matches ,pos ,_node ,_bol ,buf) + (cons (set-marker (make-marker) pos buf) matches)))) + (let (Info-history Info-history-list Info-history-forward) + (when cand (ignore-errors (Info-select-node))))) + ('return + (consult-info--action cand)))))) + +(defun consult-info--group (cand transform) + "Return title for CAND or TRANSFORM the candidate." + (if transform cand + (car (get-text-property 0 'consult--info cand)))) + +(defun consult-info--prepare-buffers (manuals fun) + "Prepare buffers for MANUALS and call FUN with buffers." + (declare (indent 1)) + (let (buffers) + (unwind-protect + (let ((reporter (make-progress-reporter "Preparing" 0 (length manuals)))) + (consult--with-increased-gc + (seq-do-indexed + (lambda (manual idx) + (push (cons manual (generate-new-buffer (format "*info-preview-%s*" manual))) + buffers) + (with-current-buffer (cdar buffers) + (let (Info-history Info-history-list Info-history-forward) + (Info-mode) + (Info-find-node manual "Top"))) + (progress-reporter-update reporter (1+ idx) manual)) + manuals)) + (progress-reporter-done reporter) + (funcall fun (reverse buffers))) + (dolist (buf buffers) + (kill-buffer (cdr buf)))))) + +;;;###autoload +(defun consult-info (&rest manuals) + "Full text search through info MANUALS." + (interactive + (if Info-current-file + (list (file-name-base Info-current-file)) + (info-initialize) + (completing-read-multiple + "Info Manuals: " + (info--manual-names current-prefix-arg) + nil t))) + (consult-info--prepare-buffers manuals + (lambda (buffers) + (consult--read + (consult--dynamic-collection + (apply-partially #'consult-info--candidates buffers)) + :state (consult-info--state) + :prompt + (format "Info (%s): " + (string-join (if (length> manuals 3) + `(,@(seq-take manuals 3) ,"…") + manuals) + ", ")) + :require-match t + :sort nil + :category 'consult-info + :history '(:input consult-info--history) + :group #'consult-info--group + :initial (consult--async-split-initial "") + :add-history (consult--async-split-thingatpt 'symbol) + :lookup #'consult--lookup-member)))) + +(provide 'consult-info) +;;; consult-info.el ends here blob - /dev/null blob + 3e3418507c285db574b7d8b552de1e7f7e8b11d3 (mode 644) --- /dev/null +++ elpa/consult-1.5/consult-kmacro.el @@ -0,0 +1,90 @@ +;;; consult-kmacro.el --- Provides the command `consult-kmacro' -*- lexical-binding: t -*- + +;; Copyright (C) 2021-2024 Free Software Foundation, Inc. + +;; This file is 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: + +;; Provides the command `consult-kmacro'. This is an extra package, +;; to allow lazy loading of kmacro.el. The `consult-kmacro' command +;; is autoloaded. + +;;; Code: + +(require 'consult) +(require 'kmacro) + +(defvar consult-kmacro--history nil) + +(defun consult-kmacro--candidates () + "Return alist of kmacros and indices." + (thread-last + ;; List of macros + (append (and last-kbd-macro (list (kmacro-ring-head))) kmacro-ring) + ;; Emacs 29 uses OClosures. I like OClosures but it would have been better + ;; if public APIs wouldn't change like that. + (mapcar (lambda (x) + (if (eval-when-compile (> emacs-major-version 28)) + (list (kmacro--keys x) (kmacro--counter x) (kmacro--format x) x) + `(,@x ,x)))) + ;; Filter mouse clicks + (seq-remove (lambda (x) (seq-some #'mouse-event-p (car x)))) + ;; Format macros + (mapcar (pcase-lambda (`(,keys ,counter ,format ,km)) + (propertize + (format-kbd-macro keys 1) + 'consult--candidate km + 'consult-kmacro--annotation + ;; If the counter is 0 and the counter format is its default, + ;; then there is a good chance that the counter isn't actually + ;; being used. This can only be wrong when a user + ;; intentionally starts the counter with a negative value and + ;; then increments it to 0. + (cond + ((not (equal format "%d")) ;; show counter for non-default format + (format " (counter=%d, format=%s) " counter format)) + ((/= counter 0) ;; show counter if non-zero + (format " (counter=%d)" counter)))))) + (delete-dups))) + +;;;###autoload +(defun consult-kmacro (arg) + "Run a chosen keyboard macro. + +With prefix ARG, run the macro that many times. +Macros containing mouse clicks are omitted." + (interactive "p") + (let ((km (consult--read + (or (consult-kmacro--candidates) + (user-error "No keyboard macros defined")) + :prompt "Keyboard macro: " + :category 'consult-kmacro + :require-match t + :sort nil + :history 'consult-kmacro--history + :annotate + (lambda (cand) + (get-text-property 0 'consult-kmacro--annotation cand)) + :lookup #'consult--lookup-candidate))) + ;; Kmacros are lambdas (oclosures) on Emacs 29 + (funcall (if (eval-when-compile (> emacs-major-version 28)) + km + (kmacro-lambda-form km)) + arg))) + +(provide 'consult-kmacro) +;;; consult-kmacro.el ends here blob - /dev/null blob + 905da6d28432dd3fee40cfeeb23c45635cb10c1e (mode 644) --- /dev/null +++ elpa/consult-1.5/consult-org.el @@ -0,0 +1,144 @@ +;;; consult-org.el --- Consult commands for org-mode -*- lexical-binding: t -*- + +;; Copyright (C) 2021-2024 Free Software Foundation, Inc. + +;; This file is 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: + +;; Provides a `completing-read' interface for Org mode navigation. +;; This is an extra package, to allow lazy loading of Org. + +;;; Code: + +(require 'consult) +(require 'org) + +(defvar consult-org--history nil) + +(defun consult-org--narrow () + "Narrowing configuration for `consult-org' commands." + (let ((todo-kws + (seq-filter + (lambda (x) (<= ?a (car x) ?z)) + (mapcar (lambda (s) + (pcase-let ((`(,a ,b) (split-string s "("))) + (cons (downcase (string-to-char (or b a))) a))) + (apply #'append (mapcar #'cdr org-todo-keywords)))))) + (list :predicate + (lambda (cand) + (pcase-let ((`(,level ,todo ,prio . ,_) + (get-text-property 0 'consult-org--heading cand))) + (cond + ((<= ?1 consult--narrow ?9) (<= level (- consult--narrow ?0))) + ((<= ?A consult--narrow ?Z) (eq prio consult--narrow)) + (t (equal todo (alist-get consult--narrow todo-kws)))))) + :keys + (nconc (mapcar (lambda (c) (cons c (format "Level %c" c))) + (number-sequence ?1 ?9)) + (mapcar (lambda (c) (cons c (format "Priority %c" c))) + (number-sequence (max ?A org-highest-priority) + (min ?Z org-lowest-priority))) + todo-kws)))) + +(defun consult-org--headings (prefix match scope &rest skip) + "Return a list of Org heading candidates. + +If PREFIX is non-nil, prefix the candidates with the buffer name. +MATCH, SCOPE and SKIP are as in `org-map-entries'." + (let (buffer (idx 0)) + (apply + #'org-map-entries + (lambda () + ;; Reset the cache when the buffer changes, since `org-get-outline-path' uses the cache + (unless (eq buffer (buffer-name)) + (setq buffer (buffer-name) + org-outline-path-cache nil)) + (pcase-let* ((`(_ ,level ,todo ,prio ,_hl ,tags) (org-heading-components)) + (tags (if org-use-tag-inheritance + (when-let ((tags (org-get-tags))) + (concat ":" (string-join tags ":") ":")) + tags)) + (cand (org-format-outline-path + (org-get-outline-path 'with-self 'use-cache) + most-positive-fixnum))) + (when todo + (put-text-property 0 (length todo) 'face (org-get-todo-face todo) todo)) + (when tags + (put-text-property 0 (length tags) 'face 'org-tag tags)) + (setq cand (concat (and prefix buffer) (and prefix " ") cand (and tags " ") + tags (consult--tofu-encode idx))) + (cl-incf idx) + (add-text-properties 0 1 + `(org-marker ,(point-marker) + consult-org--heading (,level ,todo ,prio . ,buffer)) + cand) + cand)) + match scope skip))) + +(defun consult-org--annotate (cand) + "Annotate CAND for `consult-org-heading'." + (pcase-let ((`(,_level ,todo ,prio . ,_) + (get-text-property 0 'consult-org--heading cand))) + (consult--annotate-align + cand + (concat todo + (and prio (format #(" [#%c]" 1 6 (face org-priority)) prio)))))) + +(defun consult-org--group (cand transform) + "Return title for CAND or TRANSFORM the candidate." + (pcase-let ((`(,_level ,_todo ,_prio . ,buffer) + (get-text-property 0 'consult-org--heading cand))) + (if transform (substring cand (1+ (length buffer))) buffer))) + +;;;###autoload +(defun consult-org-heading (&optional match scope) + "Jump to an Org heading. + +MATCH and SCOPE are as in `org-map-entries' and determine which +entries are offered. By default, all entries of the current +buffer are offered." + (interactive (unless (derived-mode-p #'org-mode) + (user-error "Must be called from an Org buffer"))) + (let ((prefix (not (memq scope '(nil tree region region-start-level file))))) + (consult--read + (consult--slow-operation "Collecting headings..." + (or (consult-org--headings prefix match scope) + (user-error "No headings"))) + :prompt "Go to heading: " + :category 'org-heading + :sort nil + :require-match t + :history '(:input consult-org--history) + :narrow (consult-org--narrow) + :state (consult--jump-state) + :annotate #'consult-org--annotate + :group (and prefix #'consult-org--group) + :lookup (apply-partially #'consult--lookup-prop 'org-marker)))) + +;;;###autoload +(defun consult-org-agenda (&optional match) + "Jump to an Org agenda heading. + +By default, all agenda entries are offered. MATCH is as in +`org-map-entries' and can used to refine this." + (interactive) + (unless org-agenda-files + (user-error "No agenda files")) + (consult-org-heading match 'agenda)) + +(provide 'consult-org) +;;; consult-org.el ends here blob - /dev/null blob + 2fc021cb8e243620881f3b18e278103baf674897 (mode 644) --- /dev/null +++ elpa/consult-1.5/consult-pkg.el @@ -0,0 +1,2 @@ +;; Generated package description from consult.el -*- no-byte-compile: t -*- +(define-package "consult" "1.5" "Consulting completing-read" '((emacs "27.1") (compat "29.1.4.4")) :commit "d8888bb67f881a3c4855c9ce7224de18a7dc3901" :maintainer '("Daniel Mendler" . "mail@daniel-mendler.de") :keywords '("matching" "files" "completion") :url "https://github.com/minad/consult") blob - /dev/null blob + ec01cae0439fdcfb05f8ca5032d64aaa5da65028 (mode 644) --- /dev/null +++ elpa/consult-1.5/consult-register.el @@ -0,0 +1,329 @@ +;;; consult-register.el --- Consult commands for registers -*- lexical-binding: t -*- + +;; Copyright (C) 2021-2024 Free Software Foundation, Inc. + +;; This file is 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: + +;; Provides register-related Consult commands. + +;;; Code: + +(require 'consult) +(require 'kmacro) + +(defcustom consult-register-prefix #("#" 0 1 (face consult-key)) + "Prepend prefix in front of register keys during completion." + :type '(choice (const nil) string) + :group 'consult) + +(defvar consult-register--narrow + '((?n . "Number") + (?s . "String") + (?p . "Point") + (?r . "Rectangle") + (?t . "Frameset") + (?k . "Kmacro") + (?f . "File") + (?b . "Buffer") + (?w . "Window")) + "Register type names. +Each element of the list must have the form (char . name).") + +(cl-defun consult-register--format-value (val) + "Format generic register VAL as string." + (with-output-to-string (register-val-describe val nil))) + +(cl-defgeneric consult-register--describe (val) + "Describe generic register VAL." + (list (consult-register--format-value val))) + +(cl-defmethod consult-register--describe ((val number)) + "Describe numeric register VAL." + (list (consult-register--format-value val) 'consult--type ?n)) + +(cl-defmethod consult-register--describe ((val string)) + "Describe string register VAL." + (list val 'consult--type + (if (eq (car (get-text-property 0 'yank-handler val)) + 'rectangle--insert-for-yank) + ?r ?s))) + +(cl-defmethod consult-register--describe ((val marker)) + "Describe marker register VAL." + (with-current-buffer (marker-buffer val) + (save-excursion + (without-restriction + (goto-char val) + (let* ((line (line-number-at-pos)) + (str (propertize (consult--line-with-mark val) + 'consult-location (cons val line)))) + (list (consult--format-file-line-match (buffer-name) line str) + 'multi-category `(consult-location . ,str) + 'consult--type ?p)))))) + +(defmacro consult-register--describe-kmacro () + "Generate method which describes kmacro register." + `(cl-defmethod consult-register--describe ((val ,(if (< emacs-major-version 30) 'kmacro-register 'kmacro))) + (list (consult-register--format-value val) 'consult--type ?k))) +(consult-register--describe-kmacro) + +(cl-defmethod consult-register--describe ((val (head file))) + "Describe file register VAL." + (list (propertize (abbreviate-file-name (cdr val)) 'face 'consult-file) + 'consult--type ?f 'multi-category `(file . ,(cdr val)))) + +(cl-defmethod consult-register--describe ((val (head buffer))) + "Describe buffer register VAL." + (list (propertize (cdr val) 'face 'consult-buffer) + 'consult--type ?f 'multi-category `(buffer . ,(cdr val)))) + +(cl-defmethod consult-register--describe ((val (head file-query))) + "Describe file-query register VAL." + (list (format "%s at position %d" + (propertize (abbreviate-file-name (cadr val)) + 'face 'consult-file) + (caddr val)) + 'consult--type ?f 'multi-category `(file . ,(cadr val)))) + +(cl-defmethod consult-register--describe ((val cons)) + "Describe rectangle or window-configuration register VAL." + (cond + ((stringp (car val)) + (list (string-join val "\n") 'consult--type ?r)) + ((window-configuration-p (car val)) + (list (consult-register--format-value val) + 'consult--type ?w)) + (t (list (consult-register--format-value val))))) + +(with-eval-after-load 'frameset + (cl-defmethod consult-register--describe ((val frameset-register)) + "Describe frameset register VAL." + (list (consult-register--format-value val) 'consult--type ?t))) + +;;;###autoload +(defun consult-register-window (buffer &optional show-empty) + "Enhanced drop-in replacement for `register-preview'. + +BUFFER is the window buffer. +SHOW-EMPTY must be t if the window should be shown for an empty register list." + (let ((regs (consult-register--alist 'noerror)) + (separator + (and (display-graphic-p) + (propertize #(" \n" 0 1 (display (space :align-to right))) + 'face '(:inherit consult-separator :height 1 :underline t))))) + (when (or show-empty regs) + (with-current-buffer-window buffer + (cons 'display-buffer-at-bottom + '((window-height . fit-window-to-buffer) + (preserve-size . (nil . t)))) + nil + (setq-local cursor-in-non-selected-windows nil + mode-line-format nil + truncate-lines t + window-min-height 1 + window-resize-pixelwise t) + (insert (mapconcat + (lambda (reg) + (concat (funcall register-preview-function reg) separator)) + regs nil)))))) + +;;;###autoload +(defun consult-register-format (reg &optional completion) + "Enhanced preview of register REG. +This function can be used as `register-preview-function'. +If COMPLETION is non-nil format the register for completion." + (pcase-let* ((`(,key . ,val) reg) + (key-str (propertize (single-key-description key) 'face 'consult-key)) + (key-len (max 3 (length key-str))) + (`(,str . ,props) (consult-register--describe val))) + (when (string-search "\n" str) + (let* ((lines (seq-take (seq-remove #'string-blank-p (split-string str "\n")) 3)) + (space (cl-loop for x in lines minimize (string-match-p "[^ ]" x)))) + (setq str (mapconcat (lambda (x) (substring x space)) + lines (concat "\n" (make-string (1+ key-len) ?\s)))))) + (setq str (concat + (and completion consult-register-prefix) + key-str (make-string (- key-len (length key-str)) ?\s) " " + str (and (not completion) "\n"))) + (when completion + (add-text-properties + 0 (length str) + `(consult--candidate ,(car reg) ,@props) + str)) + str)) + +(defun consult-register--alist (&optional noerror filter) + "Return register list, sorted and filtered with FILTER. +Raise an error if the list is empty and NOERROR is nil." + (or (sort (cl-loop for reg in register-alist + ;; Sometimes, registers are made without a `cdr' or with + ;; invalid markers. Such registers don't do anything, and + ;; can be ignored. + if (and (cdr reg) + (or (not (markerp (cdr reg))) (marker-buffer (cdr reg))) + (or (not filter) (funcall filter reg))) + collect reg) + #'car-less-than-car) + (and (not noerror) (user-error "All registers are empty")))) + +(defun consult-register--candidates (&optional filter) + "Return formatted completion candidates, filtered with FILTER." + (mapcar (lambda (reg) (consult-register-format reg 'completion)) + (consult-register--alist nil filter))) + +;;;###autoload +(defun consult-register (&optional arg) + "Load register and either jump to location or insert the stored text. + +This command is useful to search the register contents. For quick access +to registers it is still recommended to use the register functions +`consult-register-load' and `consult-register-store' or the built-in +built-in register access functions. The command supports narrowing, see +`consult-register--narrow'. Marker positions are previewed. See +`jump-to-register' and `insert-register' for the meaning of prefix ARG." + (interactive "P") + (consult-register-load + (consult--read + (consult-register--candidates) + :prompt "Register: " + :category 'multi-category + :state + (let ((preview (consult--jump-preview))) + (lambda (action cand) + ;; Preview only markers + (funcall preview action + (when-let (reg (get-register cand)) + (and (markerp reg) reg))))) + :group (consult--type-group consult-register--narrow) + :narrow (consult--type-narrow consult-register--narrow) + :sort nil + :require-match t + :history t ;; disable history + :lookup #'consult--lookup-candidate) + arg)) + +;;;###autoload +(defun consult-register-load (reg &optional arg) + "Do what I mean with a REG. + +For a window configuration, restore it. For a number or text, insert it. +For a location, jump to it. See `jump-to-register' and `insert-register' +for the meaning of prefix ARG." + (interactive + (list + (and (consult-register--alist) + (register-read-with-preview "Load register: ")) + current-prefix-arg)) + (condition-case err + (jump-to-register reg arg) + (user-error + (unless (string-search "access aborted" (error-message-string err)) + (insert-register reg (not arg)))))) + +(defun consult-register--action (action-list) + "Read register key and execute action from ACTION-LIST. + +This function is derived from `register-read-with-preview'." + (let* ((buffer "*Register Preview*") + (prefix (car action-list)) + (action-list (cdr action-list)) + (action (car (nth 0 action-list))) + (preview + (lambda () + (unless (get-buffer-window buffer) + (register-preview buffer 'show-empty) + (when-let (win (get-buffer-window buffer)) + (with-selected-window win + (let ((inhibit-read-only t)) + (goto-char (point-max)) + (insert + (propertize (concat prefix ": ") 'face 'consult-help) + (mapconcat + (lambda (x) + (concat (propertize (format "M-%c" (car x)) 'face 'consult-key) + " " (propertize (cadr x) 'face 'consult-help))) + action-list " ")) + (fit-window-to-buffer))))))) + (timer (when (numberp register-preview-delay) + (run-at-time register-preview-delay nil preview))) + (help-chars (seq-remove #'get-register (cons help-char help-event-list))) + key reg) + (unwind-protect + (while (not reg) + (while (memq (setq key + (read-key (propertize (caddr (assq action action-list)) + 'face 'minibuffer-prompt))) + help-chars) + (funcall preview)) + (setq key (if (and (eql key ?\e) (characterp last-input-event)) + ;; in terminal Emacs M-letter is read as two keys, ESC and the letter, + ;; use what would have been read in graphical Emacs + (logior #x8000000 last-input-event) + last-input-event)) + (cond + ((or (eq ?\C-g key) + (eq 'escape key) + (eq ?\C-\[ key)) + (keyboard-quit)) + ((and (numberp key) (assq (logxor #x8000000 key) action-list)) + (setq action (logxor #x8000000 key))) + ((characterp key) + (setq reg key)) + (t (user-error "Non-character input")))) + (when (timerp timer) + (cancel-timer timer)) + (let ((w (get-buffer-window buffer))) + (when (window-live-p w) + (delete-window w))) + (when (get-buffer buffer) + (kill-buffer buffer))) + (when reg + (funcall (cadddr (assq action action-list)) reg)))) + +;;;###autoload +(defun consult-register-store (arg) + "Store register dependent on current context, showing an action menu. + +With an active region, store/append/prepend the contents, optionally +deleting the region when a prefix ARG is given. With a numeric prefix +ARG, store or add the number. Otherwise store point, frameset, window or +kmacro." + (interactive "P") + (consult-register--action + (cond + ((use-region-p) + (let ((beg (region-beginning)) + (end (region-end))) + `("Region" + (?c "copy" "Copy region to register: " ,(lambda (r) (copy-to-register r beg end arg t))) + (?a "append" "Append region to register: " ,(lambda (r) (append-to-register r beg end arg))) + (?p "prepend" "Prepend region to register: " ,(lambda (r) (prepend-to-register r beg end arg)))))) + ((numberp arg) + `(,(format "Number %s" arg) + (?s "store" ,(format "Store %s in register: " arg) ,(lambda (r) (number-to-register arg r))) + (?a "add" ,(format "Add %s to register: " arg) ,(lambda (r) (increment-register arg r))))) + (t + `("Store" + (?p "point" "Point to register: " ,#'point-to-register) + (?f "file" "File to register: " ,(lambda (r) (set-register r `(file . ,(buffer-file-name))))) + (?t "frameset" "Frameset to register: " ,#'frameset-to-register) + (?w "window" "Window to register: " ,#'window-configuration-to-register) + ,@(and last-kbd-macro `((?k "kmacro" "Kmacro to register: " ,#'kmacro-to-register)))))))) + +(provide 'consult-register) +;;; consult-register.el ends here blob - /dev/null blob + 7188e3727037bce321a02f8f52ed55a427969031 (mode 644) --- /dev/null +++ elpa/consult-1.5/consult-xref.el @@ -0,0 +1,122 @@ +;;; consult-xref.el --- Xref integration for Consult -*- lexical-binding: t -*- + +;; Copyright (C) 2021-2024 Free Software Foundation, Inc. + +;; This file is 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: + +;; Provides Xref integration for Consult. This is an extra package, to +;; allow lazy loading of xref.el. The `consult-xref' function is +;; autoloaded. + +;;; Code: + +(require 'consult) +(require 'xref) + +(defvar consult-xref--history nil) + +(defvar consult-xref--fetcher nil + "The current xref fetcher. +The fetch is stored globally such that it can be accessed by + Embark for `embark-export'.") + +(defun consult-xref--candidates () + "Return xref candidate list." + (let ((root (consult--project-root))) + (mapcar (lambda (xref) + (let* ((loc (xref-item-location xref)) + (group (if (fboundp 'xref--group-name-for-display) + ;; This function is available in xref 1.3.2 + (xref--group-name-for-display + (xref-location-group loc) root) + (xref-location-group loc))) + (cand (consult--format-file-line-match + group + (or (xref-location-line loc) 0) + (xref-item-summary xref)))) + (add-text-properties + 0 1 `(consult-xref ,xref consult--prefix-group ,group) cand) + cand)) + (funcall consult-xref--fetcher)))) + +(defun consult-xref--preview (display) + "Xref preview with DISPLAY function." + (let ((open (consult--temporary-files)) + (preview (consult--jump-preview))) + (lambda (action cand) + (unless cand + (funcall open)) + (let ((consult--buffer-display display)) + (funcall preview action + (when-let (loc (and cand (eq action 'preview) + (xref-item-location cand))) + (let ((type (type-of loc))) + ;; Only preview file and buffer markers + (pcase type + ('xref-buffer-location + (xref-location-marker loc)) + ((or 'xref-file-location 'xref-etags-location) + (consult--marker-from-line-column + (funcall open + ;; xref-location-group returns the file name + (let ((xref-file-name-display 'abs)) + (xref-location-group loc))) + (xref-location-line loc) + (if (eq type 'xref-file-location) + (xref-file-location-column loc) + 0))))))))))) + +;;;###autoload +(defun consult-xref (fetcher &optional alist) + "Show xrefs with preview in the minibuffer. + +This function can be used for `xref-show-xrefs-function'. +See `xref-show-xrefs-function' for the description of the +FETCHER and ALIST arguments." + (let* ((consult-xref--fetcher fetcher) + (candidates (consult-xref--candidates)) + (display (alist-get 'display-action alist))) + (unless candidates + (user-error "No xref locations")) + (xref-pop-to-location + (if (cdr candidates) + (apply + #'consult--read + candidates + (append + (consult--customize-get #'consult-xref) + (list + :prompt "Go to xref: " + :history 'consult-xref--history + :require-match t + :sort nil + :category 'consult-xref + :group #'consult--prefix-group + :state + ;; do not preview other frame + (when-let (fun (pcase-exhaustive display + ('frame nil) + ('window #'switch-to-buffer-other-window) + ('nil #'switch-to-buffer))) + (consult-xref--preview fun)) + :lookup (apply-partially #'consult--lookup-prop 'consult-xref)))) + (get-text-property 0 'consult-xref (car candidates))) + display))) + +(provide 'consult-xref) +;;; consult-xref.el ends here blob - /dev/null blob + b24db7598a515424c8b57e41107d914c55a93edd (mode 644) --- /dev/null +++ elpa/consult-1.5/consult.el @@ -0,0 +1,5234 @@ +;;; consult.el --- Consulting completing-read -*- lexical-binding: t -*- + +;; Copyright (C) 2021-2024 Free Software Foundation, Inc. + +;; Author: Daniel Mendler and Consult contributors +;; Maintainer: Daniel Mendler +;; Created: 2020 +;; Version: 1.5 +;; Package-Requires: ((emacs "27.1") (compat "29.1.4.4")) +;; Homepage: https://github.com/minad/consult +;; Keywords: matching, files, completion + +;; This file is 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: + +;; Consult implements a set of `consult-' commands, which aim to +;; improve the way you use Emacs. The commands are founded on +;; `completing-read', which selects from a list of candidate strings. +;; Consult provides an enhanced buffer switcher `consult-buffer' and +;; search and navigation commands like `consult-imenu' and +;; `consult-line'. Searching through multiple files is supported by the +;; asynchronous `consult-grep' command. Many Consult commands support +;; previewing candidates. If a candidate is selected in the completion +;; view, the buffer shows the candidate immediately. + +;; The Consult commands are compatible with multiple completion systems +;; based on the Emacs `completing-read' API, including the default +;; completion system, Vertico, Mct and Icomplete. + +;; See the README for an overview of the available Consult commands and +;; the documentation of the configuration and installation of the +;; package. + +;; The full list of contributors can be found in the acknowledgments +;; section of the README. + +;;; Code: + +(eval-when-compile + (require 'cl-lib) + (require 'subr-x)) +(require 'compat) +(require 'bookmark) + +(defgroup consult nil + "Consulting `completing-read'." + :link '(info-link :tag "Info Manual" "(consult)") + :link '(url-link :tag "Homepage" "https://github.com/minad/consult") + :link '(emacs-library-link :tag "Library Source" "consult.el") + :group 'files + :group 'outlines + :group 'minibuffer + :prefix "consult-") + +;;;; Customization + +(defcustom consult-narrow-key nil + "Prefix key for narrowing during completion. + +Good choices for this key are \"<\" and \"C-+\" for example. The +key must be a string accepted by `key-valid-p'." + :type '(choice key (const :tag "None" nil))) + +(defcustom consult-widen-key nil + "Key used for widening during completion. + +If this key is unset, defaults to twice the `consult-narrow-key'. +The key must be a string accepted by `key-valid-p'." + :type '(choice key (const :tag "None" nil))) + +(defcustom consult-project-function + #'consult--default-project-function + "Function which returns project root directory. +The function takes one boolean argument MAY-PROMPT. If +MAY-PROMPT is non-nil, the function may ask the prompt the user +for a project directory. The root directory is used by +`consult-buffer' and `consult-grep'." + :type `(choice + (const :tag "Default project function" ,#'consult--default-project-function) + (function :tag "Custom function") + (const :tag "No project integration" nil))) + +(defcustom consult-async-refresh-delay 0.2 + "Refreshing delay of the completion UI for asynchronous commands. + +The completion UI is only updated every +`consult-async-refresh-delay' seconds. This applies to +asynchronous commands like for example `consult-grep'." + :type '(float :tag "Delay in seconds")) + +(defcustom consult-async-input-throttle 0.5 + "Input throttle for asynchronous commands. + +The asynchronous process is started only every +`consult-async-input-throttle' seconds. This applies to asynchronous +commands, e.g., `consult-grep'." + :type '(float :tag "Delay in seconds")) + +(defcustom consult-async-input-debounce 0.2 + "Input debounce for asynchronous commands. + +The asynchronous process is started only when there has not been new +input for `consult-async-input-debounce' seconds. This applies to +asynchronous commands, e.g., `consult-grep'." + :type '(float :tag "Delay in seconds")) + +(defcustom consult-async-min-input 3 + "Minimum number of characters needed, before asynchronous process is called. + +This applies to asynchronous commands, e.g., `consult-grep'." + :type '(natnum :tag "Number of characters")) + +(defcustom consult-async-split-style 'perl + "Async splitting style, see `consult-async-split-styles-alist'." + :type '(choice (const :tag "No splitting" nil) + (const :tag "Comma" comma) + (const :tag "Semicolon" semicolon) + (const :tag "Perl" perl))) + +(defcustom consult-async-split-styles-alist + `((nil :function ,#'consult--split-nil) + (comma :separator ?, :function ,#'consult--split-separator) + (semicolon :separator ?\; :function ,#'consult--split-separator) + (perl :initial "#" :function ,#'consult--split-perl)) + "Async splitting styles." + :type '(alist :key-type symbol :value-type plist)) + +(defcustom consult-mode-histories + '((eshell-mode eshell-history-ring eshell-history-index eshell-bol) + (comint-mode comint-input-ring comint-input-ring-index comint-bol) + (term-mode term-input-ring term-input-ring-index term-bol)) + "Alist of mode histories (mode history index bol). +The histories can be rings or lists. Index, if provided, is a +variable to set to the index of the selection within the ring or +list. Bol, if provided is a function which jumps to the beginning +of the line after the prompt." + :type '(alist :key-type symbol + :value-type (group :tag "Include Index" + (symbol :tag "List/Ring") + (symbol :tag "Index Variable") + (symbol :tag "Bol Function")))) + +(defcustom consult-themes nil + "List of themes (symbols or regexps) to be presented for selection. +nil shows all `custom-available-themes'." + :type '(repeat (choice symbol regexp))) + +(defcustom consult-after-jump-hook (list #'recenter) + "Function called after jumping to a location. + +Commonly used functions for this hook are `recenter' and +`reposition-window'. You may want to add a function which pulses +the current line, e.g., `pulse-momentary-highlight-one-line' is +supported on Emacs 28 and newer. The hook called during preview +and for the jump after selection." + :type 'hook) + +(defcustom consult-line-start-from-top nil + "Start search from the top if non-nil. +Otherwise start the search at the current line and wrap around." + :type 'boolean) + +(defcustom consult-point-placement 'match-beginning + "Where to leave point when jumping to a match. +This setting affects the command `consult-line' and the `consult-grep' variants." + :type '(choice (const :tag "Beginning of the line" line-beginning) + (const :tag "Beginning of the match" match-beginning) + (const :tag "End of the match" match-end))) + +(defcustom consult-line-numbers-widen t + "Show absolute line numbers when narrowing is active. + +See also `display-line-numbers-widen'." + :type 'boolean) + +(defcustom consult-goto-line-numbers t + "Show line numbers for `consult-goto-line'." + :type 'boolean) + +(defcustom consult-fontify-preserve t + "Preserve fontification for line-based commands." + :type 'boolean) + +(defcustom consult-fontify-max-size 1048576 + "Buffers larger than this byte limit are not fontified. + +This is necessary in order to prevent a large startup time +for navigation commands like `consult-line'." + :type '(natnum :tag "Buffer size in bytes")) + +(defcustom consult-buffer-filter + '("\\` " + "\\`\\*Completions\\*\\'" + "\\`\\*Flymake log\\*\\'" + "\\`\\*Semantic SymRef\\*\\'" + "\\`\\*tramp/.*\\*\\'") + "Filter regexps for `consult-buffer'. + +The default setting is to filter ephemeral buffer names beginning +with a space character, the *Completions* buffer and a few log +buffers. The regular expressions are matched case sensitively." + :type '(repeat regexp)) + +(defcustom consult-buffer-sources + '(consult--source-hidden-buffer + consult--source-modified-buffer + consult--source-buffer + consult--source-recent-file + consult--source-file-register + consult--source-bookmark + consult--source-project-buffer-hidden + consult--source-project-recent-file-hidden) + "Sources used by `consult-buffer'. +See also `consult-project-buffer-sources'. +See `consult--multi' for a description of the source data structure." + :type '(repeat symbol)) + +(defcustom consult-project-buffer-sources + '(consult--source-project-buffer + consult--source-project-recent-file) + "Sources used by `consult-project-buffer'. +See also `consult-buffer-sources'. +See `consult--multi' for a description of the source data structure." + :type '(repeat symbol)) + +(defcustom consult-mode-command-filter + '(;; Filter commands + "-mode\\'" "--" + ;; Filter whole features + simple mwheel time so-long recentf tab-bar tab-line) + "Filter commands for `consult-mode-command'." + :type '(repeat (choice symbol regexp))) + +(defcustom consult-grep-max-columns 300 + "Maximal number of columns of grep output." + :type 'natnum) + +(defconst consult--grep-match-regexp + "\\`\\(?:\\./\\)?\\([^\n\0]+\\)\0\\([0-9]+\\)\\([-:\0]\\)" + "Regexp used to match file and line of grep output.") + +(defcustom consult-grep-args + '("grep" (consult--grep-exclude-args) + "--null --line-buffered --color=never --ignore-case\ + --with-filename --line-number -I -r") + "Command line arguments for grep, see `consult-grep'. +The dynamically computed arguments are appended. +Can be either a string, or a list of strings or expressions." + :type '(choice string (repeat (choice string sexp)))) + +(defcustom consult-git-grep-args + "git --no-pager grep --null --color=never --ignore-case\ + --extended-regexp --line-number -I" + "Command line arguments for git-grep, see `consult-git-grep'. +The dynamically computed arguments are appended. +Can be either a string, or a list of strings or expressions." + :type '(choice string (repeat (choice string sexp)))) + +(defcustom consult-ripgrep-args + "rg --null --line-buffered --color=never --max-columns=1000 --path-separator /\ + --smart-case --no-heading --with-filename --line-number --search-zip" + "Command line arguments for ripgrep, see `consult-ripgrep'. +The dynamically computed arguments are appended. +Can be either a string, or a list of strings or expressions." + :type '(choice string (repeat (choice string sexp)))) + +(defcustom consult-find-args + "find . -not ( -path */.[A-Za-z]* -prune )" + "Command line arguments for find, see `consult-find'. +The dynamically computed arguments are appended. +Can be either a string, or a list of strings or expressions." + :type '(choice string (repeat (choice string sexp)))) + +(defcustom consult-fd-args + '((if (executable-find "fdfind" 'remote) "fdfind" "fd") + "--full-path --color=never") + "Command line arguments for fd, see `consult-fd'. +The dynamically computed arguments are appended. +Can be either a string, or a list of strings or expressions." + :type '(choice string (repeat (choice string sexp)))) + +(defcustom consult-locate-args + "locate --ignore-case" ;; --existing not supported by Debian plocate + "Command line arguments for locate, see `consult-locate'. +The dynamically computed arguments are appended. +Can be either a string, or a list of strings or expressions." + :type '(choice string (repeat (choice string sexp)))) + +(defcustom consult-man-args + "man -k" + "Command line arguments for man, see `consult-man'. +The dynamically computed arguments are appended. +Can be either a string, or a list of strings or expressions." + :type '(choice string (repeat (choice string sexp)))) + +(defcustom consult-preview-key 'any + "Preview trigger keys, can be nil, `any', a single key or a list of keys. +Debouncing can be specified via the `:debounce' attribute. The +individual keys must be strings accepted by `key-valid-p'." + :type '(choice (const :tag "Any key" any) + (list :tag "Debounced" + (const :debounce) + (float :tag "Seconds" 0.1) + (const any)) + (const :tag "No preview" nil) + (key :tag "Key") + (repeat :tag "List of keys" key))) + +(defcustom consult-preview-partial-size 1048576 + "Files larger than this byte limit are previewed partially." + :type '(natnum :tag "File size in bytes")) + +(defcustom consult-preview-partial-chunk 102400 + "Partial preview chunk size in bytes. +If a file is larger than `consult-preview-partial-size' only the +chunk from the beginning of the file is previewed." + :type '(natnum :tag "Chunk size in bytes")) + +(defcustom consult-preview-max-count 10 + "Number of file buffers to keep open temporarily during preview." + :type '(natnum :tag "Number of buffers")) + +(defcustom consult-preview-excluded-files + '("\\`/[^/|:]+:") ;; Do not preview remote files + "List of regexps matched against names of files, which are not previewed." + :type '(repeat regexp)) + +(defcustom consult-preview-allowed-hooks + '(global-font-lock-mode-check-buffers + save-place-find-file-hook) + "List of `find-file' hooks, which should be executed during file preview." + :type '(repeat symbol)) + +(defcustom consult-preview-variables + '((inhibit-message . t) + (enable-dir-local-variables . nil) + (enable-local-variables . :safe) + (non-essential . t) + (delay-mode-hooks . t)) + "Variables which are bound for file preview." + :type '(alist :key-type symbol)) + +(defcustom consult-bookmark-narrow + `((?f "File" bookmark-default-handler) + (?h "Help" help-bookmark-jump Info-bookmark-jump + Man-bookmark-jump woman-bookmark-jump) + (?p "Picture" image-bookmark-jump) + (?d "Docview" doc-view-bookmark-jump) + (?m "Mail" gnus-summary-bookmark-jump) + (?s "Eshell" eshell-bookmark-jump) + (?w "Web" eww-bookmark-jump xwidget-webkit-bookmark-jump-handler) + (?v "VC Directory" vc-dir-bookmark-jump) + (nil "Other")) + "Bookmark narrowing configuration. + +Each element of the list must have the form (char name handlers...)." + :type '(alist :key-type character :value-type (cons string (repeat function)))) + +(defcustom consult-yank-rotate + (if (boundp 'yank-from-kill-ring-rotate) + yank-from-kill-ring-rotate + t) + "Rotate the `kill-ring' in the `consult-yank' commands." + :type 'boolean) + +;;;; Faces + +(defgroup consult-faces nil + "Faces used by Consult." + :group 'consult + :group 'faces) + +(defface consult-preview-line + '((t :inherit consult-preview-insertion :extend t)) + "Face used for line previews.") + +(defface consult-highlight-match + '((t :inherit match)) + "Face used to highlight matches in the completion candidates. +Used for example by `consult-grep'.") + +(defface consult-highlight-mark + '((t :inherit consult-highlight-match)) + "Face used for mark positions in completion candidates. +Used for example by `consult-mark'. The face should be different +than the `cursor' face to avoid confusion.") + +(defface consult-preview-match + '((t :inherit isearch)) + "Face used for match previews, e.g., in `consult-line'.") + +(defface consult-preview-insertion + '((t :inherit region)) + "Face used for previews of text to be inserted. +Used by `consult-completion-in-region', `consult-yank' and `consult-history'.") + +(defface consult-narrow-indicator + '((t :inherit warning)) + "Face used for the narrowing indicator.") + +(defface consult-async-running + '((t :inherit consult-narrow-indicator)) + "Face used if asynchronous process is running.") + +(defface consult-async-finished + '((t :inherit success)) + "Face used if asynchronous process has finished.") + +(defface consult-async-failed + '((t :inherit error)) + "Face used if asynchronous process has failed.") + +(defface consult-async-split + '((t :inherit font-lock-negation-char-face)) + "Face used to highlight punctuation character.") + +(defface consult-help + '((t :inherit shadow)) + "Face used to highlight help, e.g., in `consult-register-store'.") + +(defface consult-key + '((t :inherit font-lock-keyword-face)) + "Face used to highlight keys, e.g., in `consult-register'.") + +(defface consult-line-number + '((t :inherit consult-key)) + "Face used to highlight location line in `consult-global-mark'.") + +(defface consult-file + '((t :inherit font-lock-function-name-face)) + "Face used to highlight files in `consult-buffer'.") + +(defface consult-grep-context + '((t :inherit shadow)) + "Face used to highlight grep context in `consult-grep'.") + +(defface consult-bookmark + '((t :inherit font-lock-constant-face)) + "Face used to highlight bookmarks in `consult-buffer'.") + +(defface consult-buffer + '((t)) + "Face used to highlight buffers in `consult-buffer'.") + +(defface consult-line-number-prefix + '((t :inherit line-number)) + "Face used to highlight line number prefixes.") + +(defface consult-line-number-wrapped + '((t :inherit consult-line-number-prefix :inherit font-lock-warning-face)) + "Face used to highlight line number prefixes after wrap around.") + +(defface consult-separator + '((((class color) (min-colors 88) (background light)) + :foreground "#ccc") + (((class color) (min-colors 88) (background dark)) + :foreground "#333")) + "Face used for thin line separators in `consult-register-window'.") + +;;;; Input history variables + +(defvar consult--path-history nil) +(defvar consult--grep-history nil) +(defvar consult--find-history nil) +(defvar consult--man-history nil) +(defvar consult--line-history nil) +(defvar consult--line-multi-history nil) +(defvar consult--theme-history nil) +(defvar consult--minor-mode-menu-history nil) +(defvar consult--buffer-history nil) + +;;;; Internal variables + +(defvar consult--regexp-compiler + #'consult--default-regexp-compiler + "Regular expression compiler used by `consult-grep' and other commands. +The function must return a list of regular expressions and a highlighter +function.") + +(defvar consult--customize-alist + ;; Disable preview in frames, since `consult--jump-preview' does not properly + ;; clean up. See gh:minad/consult#593. This issue should better be fixed in + ;; `consult--jump-preview'. + `((,#'consult-buffer-other-frame :preview-key nil) + (,#'consult-buffer-other-tab :preview-key nil)) + "Command configuration alist for fine-grained configuration. + +Each element of the list must have the form (command-name plist...). The +options set here will be evaluated and passed to `consult--read', when +called from the corresponding command. Note that the options depend on +the private `consult--read' API and should not be considered as stable +as the public API.") + +(defvar consult--buffer-display #'switch-to-buffer + "Buffer display function.") + +(defvar consult--completion-candidate-hook + (list #'consult--default-completion-minibuffer-candidate + #'consult--default-completion-list-candidate) + "Get candidate from completion system.") + +(defvar consult--completion-refresh-hook nil + "Refresh completion system.") + +(defvar-local consult--preview-function nil + "Minibuffer-local variable which exposes the current preview function. +This function can be called by custom completion systems from +outside the minibuffer.") + +(defvar consult--annotate-align-step 10 + "Round candidate width.") + +(defvar consult--annotate-align-width 0 + "Maximum candidate width used for annotation alignment.") + +(defconst consult--tofu-char #x200000 + "Special character used to encode line prefixes for disambiguation. +We use invalid characters outside the Unicode range.") + +(defconst consult--tofu-range #x100000 + "Special character range.") + +(defvar-local consult--narrow nil + "Current narrowing key.") + +(defvar-local consult--narrow-keys nil + "Narrowing prefixes of the current completion.") + +(defvar-local consult--narrow-predicate nil + "Narrowing predicate of the current completion.") + +(defvar-local consult--narrow-overlay nil + "Narrowing indicator overlay.") + +(defvar consult--gc-threshold (* 64 1024 1024) + "Large GC threshold for temporary increase.") + +(defvar consult--gc-percentage 0.5 + "Large GC percentage for temporary increase.") + +(defvar consult--process-chunk (* 1024 1024) + "Increase process output chunk size.") + +(defvar consult--async-log + " *consult-async*" + "Buffer for async logging output used by `consult--async-process'.") + +(defvar-local consult--focus-lines-overlays nil + "Overlays used by `consult-focus-lines'.") + +(defvar-local consult--org-fold-regions nil + "Stored regions for the org-fold API.") + +;;;; Miscellaneous helper functions + +(defun consult--key-parse (key) + "Parse KEY or signal error if invalid." + (unless (key-valid-p key) + (error "%S is not a valid key definition; see `key-valid-p'" key)) + (key-parse key)) + +(defun consult--in-buffer (fun &optional buffer) + "Ensure that FUN is executed inside BUFFER." + (unless buffer (setq buffer (current-buffer))) + (lambda (&rest args) + (with-current-buffer buffer + (apply fun args)))) + +(defun consult--completion-table-in-buffer (table &optional buffer) + "Ensure that completion TABLE is executed inside BUFFER." + (if (functionp table) + (consult--in-buffer + (lambda (str pred action) + (let ((result (funcall table str pred action))) + (pcase action + ('metadata + (setq result + (mapcar + (lambda (x) + (if (and (string-suffix-p "-function" (symbol-name (car-safe x))) (cdr x)) + (cons (car x) (consult--in-buffer (cdr x))) + x)) + result))) + ((and 'completion--unquote (guard (functionp (cadr result)))) + (cl-callf consult--in-buffer (cadr result) buffer) + (cl-callf consult--in-buffer (cadddr result) buffer))) + result)) + buffer) + table)) + +(defun consult--build-args (arg) + "Return ARG as a flat list of split strings. + +Turn ARG into a list, and for each element either: +- split it if it a string. +- eval it if it is an expression." + (seq-mapcat (lambda (x) + (if (stringp x) + (split-string-and-unquote x) + (ensure-list (eval x 'lexical)))) + (ensure-list arg))) + +(defun consult--command-split (str) + "Return command argument and options list given input STR." + (save-match-data + (let ((opts (when (string-match " +--\\( +\\|\\'\\)" str) + (prog1 (substring str (match-end 0)) + (setq str (substring str 0 (match-beginning 0))))))) + ;; split-string-and-unquote fails if the quotes are invalid. Ignore it. + (cons str (and opts (ignore-errors (split-string-and-unquote opts))))))) + +(defmacro consult--keep! (list form) + "Evaluate FORM for every element of LIST and keep the non-nil results." + (declare (indent 1)) + (cl-with-gensyms (head prev result) + `(let* ((,head (cons nil ,list)) + (,prev ,head)) + (while (cdr ,prev) + (if-let (,result (let ((it (cadr ,prev))) ,form)) + (progn + (pop ,prev) + (setcar ,prev ,result)) + (setcdr ,prev (cddr ,prev)))) + (setq ,list (cdr ,head)) + nil))) + +;; Upstream bug#46326, Consult issue gh:minad/consult#193. +(defmacro consult--minibuffer-with-setup-hook (fun &rest body) + "Variant of `minibuffer-with-setup-hook' using a symbol and `fset'. + +This macro is only needed to prevent memory leaking issues with +the upstream `minibuffer-with-setup-hook' macro. +FUN is the hook function and BODY opens the minibuffer." + (declare (indent 1) (debug t)) + (let ((hook (gensym "hook")) + (append)) + (when (eq (car-safe fun) :append) + (setq append '(t) fun (cadr fun))) + `(let ((,hook (make-symbol "consult--minibuffer-setup-hook"))) + (fset ,hook (lambda () + (remove-hook 'minibuffer-setup-hook ,hook) + (funcall ,fun))) + (unwind-protect + (progn + (add-hook 'minibuffer-setup-hook ,hook ,@append) + ,@body) + (remove-hook 'minibuffer-setup-hook ,hook))))) + +(defun consult--completion-filter (pattern cands category _highlight) + "Filter CANDS with PATTERN. + +CATEGORY is the completion category, used to find the completion style via +`completion-category-defaults' and `completion-category-overrides'. +HIGHLIGHT must be non-nil if the resulting strings should be highlighted." + ;; completion-all-completions returns an improper list + ;; where the last link is not necessarily nil. + (nconc (completion-all-completions pattern cands nil (length pattern) + `(metadata (category . ,category))) + nil)) + +(defun consult--completion-filter-complement (pattern cands category _highlight) + "Filter CANDS with complement of PATTERN. +See `consult--completion-filter' for the arguments CATEGORY and HIGHLIGHT." + (let ((ht (consult--string-hash (consult--completion-filter pattern cands category nil)))) + (seq-remove (lambda (x) (gethash x ht)) cands))) + +(defun consult--completion-filter-dispatch (pattern cands category highlight) + "Filter CANDS with PATTERN with optional complement. +Either using `consult--completion-filter' or +`consult--completion-filter-complement', depending on if the pattern starts +with a bang. See `consult--completion-filter' for the arguments CATEGORY and +HIGHLIGHT." + (cond + ((string-match-p "\\`!? ?\\'" pattern) cands) ;; empty pattern + ((string-prefix-p "! " pattern) (consult--completion-filter-complement + (substring pattern 2) cands category nil)) + (t (consult--completion-filter pattern cands category highlight)))) + +(defmacro consult--each-line (beg end &rest body) + "Iterate over each line. + +The line beginning/ending BEG/END is bound in BODY." + (declare (indent 2)) + (cl-with-gensyms (max) + `(save-excursion + (let ((,beg (point-min)) (,max (point-max)) end) + (while (< ,beg ,max) + (goto-char ,beg) + (setq ,end (pos-eol)) + ,@body + (setq ,beg (1+ ,end))))))) + +(defun consult--display-width (string) + "Compute width of STRING taking display and invisible properties into account." + (let ((pos 0) (width 0) (end (length string))) + (while (< pos end) + (let ((nextd (next-single-property-change pos 'display string end)) + (display (get-text-property pos 'display string))) + (if (stringp display) + (setq width (+ width (string-width display)) + pos nextd) + (while (< pos nextd) + (let ((nexti (next-single-property-change pos 'invisible string nextd))) + (unless (get-text-property pos 'invisible string) + (setq width (+ width (compat-call string-width string pos nexti)))) + (setq pos nexti)))))) + width)) + +(defun consult--string-hash (strings) + "Create hash table from STRINGS." + (let ((ht (make-hash-table :test #'equal :size (length strings)))) + (dolist (str strings) + (puthash str t ht)) + ht)) + +(defmacro consult--local-let (binds &rest body) + "Buffer local let BINDS of dynamic variables in BODY." + (declare (indent 1)) + (let ((buffer (gensym "buffer")) + (local (mapcar (lambda (x) (cons (gensym "local") (car x))) binds))) + `(let ((,buffer (current-buffer)) + ,@(mapcar (lambda (x) `(,(car x) (local-variable-p ',(cdr x)))) local)) + (unwind-protect + (progn + ,@(mapcar (lambda (x) `(make-local-variable ',(car x))) binds) + (let (,@binds) + ,@body)) + (when (buffer-live-p ,buffer) + (with-current-buffer ,buffer + ,@(mapcar (lambda (x) + `(unless ,(car x) + (kill-local-variable ',(cdr x)))) + local))))))) + +(defvar consult--fast-abbreviate-file-name nil) +(defun consult--fast-abbreviate-file-name (name) + "Return abbreviate file NAME. +This function is a pure variant of `abbreviate-file-name', which +does not access the file system. This is important if we require +that the operation is fast, even for remote paths or paths on +network file systems." + (save-match-data + (let (case-fold-search) ;; Assume that file system is case sensitive. + (setq name (directory-abbrev-apply name)) + (if (string-match (with-memoization consult--fast-abbreviate-file-name + (directory-abbrev-make-regexp (expand-file-name "~"))) + name) + (concat "~" (substring name (match-beginning 1))) + name)))) + +(defun consult--left-truncate-file (file) + "Return abbreviated file name of FILE for use in `completing-read' prompt." + (save-match-data + (let ((afile (abbreviate-file-name file))) + (if (string-match "/\\([^/]+\\)/\\([^/]+/?\\)\\'" afile) + (propertize (format "…/%s/%s" (match-string 1 afile) (match-string 2 afile)) + 'help-echo afile) + afile)))) + +(defun consult--directory-prompt (prompt dir) + "Return prompt, paths and default directory. + +PROMPT is the prompt prefix. The directory is appended to the +prompt prefix. For projects only the project name is shown. The +`default-directory' is not shown. Other directories are +abbreviated and only the last two path components are shown. + +If DIR is a string, it is returned as default directory. If DIR +is a list of strings, the list is returned as search paths. If +DIR is nil the `consult-project-function' is tried to retrieve +the default directory. If no project is found the +`default-directory' is returned as is. Otherwise the user is +asked for the directories or files to search via +`completing-read-multiple'." + (let* ((paths nil) + (dir + (pcase dir + ((pred stringp) dir) + ('nil (or (consult--project-root) default-directory)) + (_ + (pcase (if (stringp (car-safe dir)) + dir + ;; Preserve this-command across `completing-read-multiple' call, + ;; such that `consult-customize' continues to work. + (let ((this-command this-command) + (def (abbreviate-file-name default-directory)) + ;; TODO: `minibuffer-completing-file-name' is + ;; mostly deprecated, but still in use. Packages + ;; should instead use the completion metadata. + (minibuffer-completing-file-name t) + (ignore-case read-file-name-completion-ignore-case)) + (consult--minibuffer-with-setup-hook + (lambda () + (setq-local completion-ignore-case ignore-case) + (set-syntax-table minibuffer-local-filename-syntax)) + (completing-read-multiple "Directories or files: " + #'completion-file-name-table + nil t def 'consult--path-history def)))) + ((and `(,p) (guard (file-directory-p p))) p) + (ps (setq paths (mapcar (lambda (p) + (file-relative-name (expand-file-name p))) + ps)) + default-directory))))) + (edir (file-name-as-directory (expand-file-name dir))) + (pdir (let ((default-directory edir)) + ;; Bind default-directory in order to find the project + (consult--project-root)))) + (list + (format "%s (%s): " prompt + (pcase paths + (`(,p) (consult--left-truncate-file p)) + (`(,p . ,_) + (format "%d paths, %s, …" (length paths) (consult--left-truncate-file p))) + ((guard (equal edir pdir)) (concat "Project " (consult--project-name pdir))) + (_ (consult--left-truncate-file edir)))) + (or paths '(".")) + edir))) + +(defun consult--default-project-function (may-prompt) + "Return project root directory. +When no project is found and MAY-PROMPT is non-nil ask the user." + (when-let (proj (project-current may-prompt)) + (cond + ((fboundp 'project-root) (project-root proj)) + ((fboundp 'project-roots) (car (project-roots proj)))))) + +(defun consult--project-root (&optional may-prompt) + "Return project root as absolute path. +When no project is found and MAY-PROMPT is non-nil ask the user." + ;; Preserve this-command across project selection, + ;; such that `consult-customize' continues to work. + (let ((this-command this-command)) + (when-let (root (and consult-project-function + (funcall consult-project-function may-prompt))) + (expand-file-name root)))) + +(defun consult--project-name (dir) + "Return the project name for DIR." + (if (string-match "/\\([^/]+\\)/\\'" dir) + (propertize (match-string 1 dir) 'help-echo (abbreviate-file-name dir)) + dir)) + +(defun consult--format-file-line-match (file line match) + "Format string FILE:LINE:MATCH with faces." + (setq line (number-to-string line) + match (concat file ":" line ":" match) + file (length file)) + (put-text-property 0 file 'face 'consult-file match) + (put-text-property (1+ file) (+ 1 file (length line)) 'face 'consult-line-number match) + match) + +(defun consult--make-overlay (beg end &rest props) + "Make consult overlay between BEG and END with PROPS." + (let ((ov (make-overlay beg end))) + (while props + (overlay-put ov (car props) (cadr props)) + (setq props (cddr props))) + ov)) + +(defun consult--remove-dups (list) + "Remove duplicate strings from LIST." + (delete-dups (copy-sequence list))) + +(defsubst consult--in-range-p (pos) + "Return t if position POS lies in range `point-min' to `point-max'." + (<= (point-min) pos (point-max))) + +(defun consult--completion-window-p () + "Return non-nil if the selected window belongs to the completion UI." + (or (eq (selected-window) (active-minibuffer-window)) + (eq #'completion-list-mode (buffer-local-value 'major-mode (window-buffer))))) + +(defun consult--original-window () + "Return window which was just selected just before the minibuffer was entered. +In contrast to `minibuffer-selected-window' never return nil and +always return an appropriate non-minibuffer window." + (or (minibuffer-selected-window) + (if (window-minibuffer-p (selected-window)) + (next-window) + (selected-window)))) + +(defun consult--forbid-minibuffer () + "Raise an error if executed from the minibuffer." + (when (minibufferp) + (user-error "`%s' called inside the minibuffer" this-command))) + +(defun consult--require-minibuffer () + "Raise an error if executed outside the minibuffer." + (unless (minibufferp) + (user-error "`%s' must be called inside the minibuffer" this-command))) + +(defun consult--fontify-all () + "Ensure that the whole buffer is fontified." + ;; Font-locking is lazy, i.e., if a line has not been looked at yet, the line + ;; is not font-locked. We would observe this if consulting an unfontified + ;; line. Therefore we have to enforce font-locking now, which is slow. In + ;; order to prevent is hang-up we check the buffer size against + ;; `consult-fontify-max-size'. + (when (and consult-fontify-preserve jit-lock-mode + (< (buffer-size) consult-fontify-max-size)) + (jit-lock-fontify-now))) + +(defun consult--fontify-region (start end) + "Ensure that region between START and END is fontified." + (when (and consult-fontify-preserve jit-lock-mode) + (jit-lock-fontify-now start end))) + +(defmacro consult--with-increased-gc (&rest body) + "Temporarily increase the GC limit in BODY to optimize for throughput." + (cl-with-gensyms (overwrite) + `(let* ((,overwrite (> consult--gc-threshold gc-cons-threshold)) + (gc-cons-threshold (if ,overwrite consult--gc-threshold gc-cons-threshold)) + (gc-cons-percentage (if ,overwrite consult--gc-percentage gc-cons-percentage))) + ,@body))) + +(defmacro consult--slow-operation (message &rest body) + "Show delayed MESSAGE if BODY takes too long. +Also temporarily increase the GC limit via `consult--with-increased-gc'." + (declare (indent 1)) + `(let (set-message-function) ;; bug#63253: Broken `with-delayed-message' + (with-delayed-message (1 ,message) + (consult--with-increased-gc + ,@body)))) + +(defun consult--count-lines (pos) + "Move to position POS and return number of lines." + (let ((line 1)) + (while (< (point) pos) + (forward-line) + (when (<= (point) pos) + (cl-incf line))) + (goto-char pos) + line)) + +(defun consult--marker-from-line-column (buffer line column) + "Get marker in BUFFER from LINE and COLUMN." + (when (buffer-live-p buffer) + (with-current-buffer buffer + (save-excursion + (without-restriction + (goto-char (point-min)) + ;; Location data might be invalid by now! + (ignore-errors + (forward-line (1- line)) + (goto-char (min (+ (point) column) (pos-eol)))) + (point-marker)))))) + +(defun consult--line-prefix (&optional curr-line) + "Annotate `consult-location' candidates with line numbers. +CURR-LINE is the current line number." + (setq curr-line (or curr-line -1)) + (let* ((width (length (number-to-string (line-number-at-pos + (point-max) + consult-line-numbers-widen)))) + (before (format #("%%%dd " 0 6 (face consult-line-number-wrapped)) width)) + (after (format #("%%%dd " 0 6 (face consult-line-number-prefix)) width))) + (lambda (cand) + (let ((line (cdr (get-text-property 0 'consult-location cand)))) + (list cand (format (if (< line curr-line) before after) line) ""))))) + +(defsubst consult--location-candidate (cand marker line tofu &rest props) + "Add MARKER and LINE as `consult-location' text property to CAND. +Furthermore add the additional text properties PROPS, and append +TOFU suffix for disambiguation." + (setq cand (concat cand (consult--tofu-encode tofu))) + (add-text-properties 0 1 `(consult-location (,marker . ,line) ,@props) cand) + cand) + +;; There is a similar variable `yank-excluded-properties'. Unfortunately +;; we cannot use it here since it excludes too much (e.g., invisible) +;; and at the same time not enough (e.g., cursor-sensor-functions). +(defconst consult--remove-text-properties + '(category cursor cursor-intangible cursor-sensor-functions field follow-link + fontified front-sticky help-echo insert-behind-hooks insert-in-front-hooks + intangible keymap local-map modification-hooks mouse-face pointer read-only + rear-nonsticky yank-handler) + "List of text properties to remove from buffer strings.") + +(defsubst consult--buffer-substring (beg end &optional fontify) + "Return buffer substring between BEG and END. +If FONTIFY and `consult-fontify-preserve' are non-nil, first ensure that the +region has been fontified." + (if consult-fontify-preserve + (let (str) + (when fontify (consult--fontify-region beg end)) + (setq str (buffer-substring beg end)) + ;; TODO Propose the upstream addition of a function + ;; `preserve-list-of-text-properties', which should be as efficient as + ;; `remove-list-of-text-properties'. + (remove-list-of-text-properties + 0 (- end beg) consult--remove-text-properties str) + str) + (buffer-substring-no-properties beg end))) + +(defun consult--line-with-mark (marker) + "Current line string where the MARKER position is highlighted." + (let* ((beg (pos-bol)) + (end (pos-eol)) + (str (consult--buffer-substring beg end 'fontify))) + (if (>= marker end) + (concat str #(" " 0 1 (face consult-highlight-mark))) + (put-text-property (- marker beg) (- (1+ marker) beg) + 'face 'consult-highlight-mark str) + str))) + +;;;; Tofu cooks + +(defsubst consult--tofu-p (char) + "Return non-nil if CHAR is a tofu." + (<= consult--tofu-char char (+ consult--tofu-char consult--tofu-range -1))) + +(defun consult--tofu-hide (str) + "Hide the tofus in STR." + (let* ((max (length str)) + (end max)) + (while (and (> end 0) (consult--tofu-p (aref str (1- end)))) + (cl-decf end)) + (when (< end max) + (setq str (copy-sequence str)) + (put-text-property end max 'invisible t str)) + str)) + +(defsubst consult--tofu-append (cand id) + "Append tofu-encoded ID to CAND. +The ID must fit within a single character. It must be smaller +than `consult--tofu-range'." + (setq id (char-to-string (+ consult--tofu-char id))) + (add-text-properties 0 1 '(invisible t consult-strip t) id) + (concat cand id)) + +(defsubst consult--tofu-get (cand) + "Extract tofu-encoded ID from CAND. +See `consult--tofu-append'." + (- (aref cand (1- (length cand))) consult--tofu-char)) + +;; We must disambiguate the lines by adding a prefix such that two lines with +;; the same text can be distinguished. In order to avoid matching the line +;; number, such that the user can search for numbers with `consult-line', we +;; encode the line number as characters outside the Unicode range. By doing +;; that, no accidental matching can occur. +(defun consult--tofu-encode (n) + "Return tofu-encoded number N as a string. +Large numbers are encoded as multiple tofu characters." + (let (str tofu) + (while (progn + (setq tofu (char-to-string + (+ consult--tofu-char (% n consult--tofu-range))) + str (if str (concat tofu str) tofu)) + (and (>= n consult--tofu-range) + (setq n (/ n consult--tofu-range))))) + (add-text-properties 0 (length str) '(invisible t consult-strip t) str) + str)) + +;;;; Regexp utilities + +(defun consult--find-highlights (str start &rest ignored-faces) + "Find highlighted regions in STR from position START. +Highlighted regions have a non-nil face property. +IGNORED-FACES are ignored when searching for matches." + (let (highlights + (end (length str)) + (beg start)) + (while (< beg end) + (let ((next (next-single-property-change beg 'face str end)) + (val (get-text-property beg 'face str))) + (when (and val + (not (memq val ignored-faces)) + (not (and (consp val) + (seq-some (lambda (x) (memq x ignored-faces)) val)))) + (push (cons (- beg start) (- next start)) highlights)) + (setq beg next))) + (nreverse highlights))) + +(defun consult--point-placement (str start &rest ignored-faces) + "Compute point placement from STR with START offset. +IGNORED-FACES are ignored when searching for matches. +Return cons of point position and a list of match begin/end pairs." + (let* ((matches (apply #'consult--find-highlights str start ignored-faces)) + (pos (pcase-exhaustive consult-point-placement + ('match-beginning (or (caar matches) 0)) + ('match-end (or (cdar (last matches)) 0)) + ('line-beginning 0)))) + (dolist (match matches) + (cl-decf (car match) pos) + (cl-decf (cdr match) pos)) + (cons pos matches))) + +(defun consult--highlight-regexps (regexps ignore-case str) + "Highlight REGEXPS in STR. +If a regular expression contains capturing groups, only these are highlighted. +If no capturing groups are used highlight the whole match. Case is ignored +if IGNORE-CASE is non-nil." + (dolist (re regexps) + (let ((i 0)) + (while (and (let ((case-fold-search ignore-case)) + (string-match re str i)) + ;; Ensure that regexp search made progress (edge case for .*) + (> (match-end 0) i)) + ;; Unfortunately there is no way to avoid the allocation of the match + ;; data, since the number of capturing groups is unknown. + (let ((m (match-data))) + (setq i (cadr m) m (or (cddr m) m)) + (while m + (when (car m) + (add-face-text-property (car m) (cadr m) + 'consult-highlight-match nil str)) + (setq m (cddr m))))))) + str) + +(defconst consult--convert-regexp-table + (append + ;; For simplicity, treat word beginning/end as word boundaries, + ;; since PCRE does not make this distinction. Usually the + ;; context determines if \b is the beginning or the end. + '(("\\<" . "\\b") ("\\>" . "\\b") + ("\\_<" . "\\b") ("\\_>" . "\\b")) + ;; Treat \` and \' as beginning and end of line. This is more + ;; widely supported and makes sense for line-based commands. + '(("\\`" . "^") ("\\'" . "$")) + ;; Historical: Unescaped *, +, ? are supported at the beginning + (mapcan (lambda (x) + (mapcar (lambda (y) + (cons (concat x y) + (concat (string-remove-prefix "\\" x) "\\" y))) + '("*" "+" "?"))) + '("" "\\(" "\\(?:" "\\|" "^")) + ;; Different escaping + (mapcan (lambda (x) `(,x (,(cdr x) . ,(car x)))) + '(("\\|" . "|") + ("\\(" . "(") ("\\)" . ")") + ("\\{" . "{") ("\\}" . "}")))) + "Regexp conversion table.") + +(defun consult--convert-regexp (regexp type) + "Convert Emacs REGEXP to regexp syntax TYPE." + (if (memq type '(emacs basic)) + regexp + ;; Support for Emacs regular expressions is fairly complete for basic + ;; usage. There are a few unsupported Emacs regexp features: + ;; - \= point matching + ;; - Syntax classes \sx \Sx + ;; - Character classes \cx \Cx + ;; - Explicitly numbered groups (?3:group) + (replace-regexp-in-string + (rx (or "\\\\" "\\^" ;; Pass through + (seq (or "\\(?:" "\\|") (any "*+?")) ;; Historical: \|+ or \(?:* etc + (seq "\\(" (any "*+")) ;; Historical: \(* or \(+ + (seq (or bos "^") (any "*+?")) ;; Historical: + or * at the beginning + (seq (opt "\\") (any "(){|}")) ;; Escape parens/braces/pipe + (seq "\\" (any "'<>`")) ;; Special escapes + (seq "\\_" (any "<>")))) ;; Beginning or end of symbol + (lambda (x) (or (cdr (assoc x consult--convert-regexp-table)) x)) + regexp 'fixedcase 'literal))) + +(defun consult--default-regexp-compiler (input type ignore-case) + "Compile the INPUT string to a list of regular expressions. +The function should return a pair, the list of regular expressions and a +highlight function. The highlight function should take a single +argument, the string to highlight given the INPUT. TYPE is the desired +type of regular expression, which can be `basic', `extended', `emacs' or +`pcre'. If IGNORE-CASE is non-nil return a highlight function which +matches case insensitively." + (setq input (consult--split-escaped input)) + (cons (mapcar (lambda (x) (consult--convert-regexp x type)) input) + (when-let (regexps (seq-filter #'consult--valid-regexp-p input)) + (apply-partially #'consult--highlight-regexps regexps ignore-case)))) + +(defun consult--split-escaped (str) + "Split STR at spaces, which can be escaped with backslash." + (mapcar + (lambda (x) (string-replace "\0" " " x)) + (split-string (replace-regexp-in-string + "\\\\\\\\\\|\\\\ " + (lambda (x) (if (equal x "\\ ") "\0" x)) + str 'fixedcase 'literal) + " +" t))) + +(defun consult--join-regexps (regexps type) + "Join REGEXPS of TYPE." + ;; Add look-ahead wrapper only if there is more than one regular expression + (cond + ((and (eq type 'pcre) (cdr regexps)) + (concat "^" (mapconcat (lambda (x) (format "(?=.*%s)" x)) + regexps ""))) + ((eq type 'basic) + (string-join regexps ".*")) + (t + (when (length> regexps 3) + (message "Too many regexps, %S ignored. Use post-filtering!" + (string-join (seq-drop regexps 3) " ")) + (setq regexps (seq-take regexps 3))) + (consult--join-regexps-permutations regexps (and (eq type 'emacs) "\\"))))) + +(defun consult--join-regexps-permutations (regexps esc) + "Join all permutations of REGEXPS. +ESC is the escaping string for choice and groups." + (pcase regexps + ('nil "") + (`(,r) r) + (_ (mapconcat + (lambda (r) + (concat esc "(" r esc ").*" esc "(" + (consult--join-regexps-permutations (remove r regexps) esc) + esc ")")) + regexps (concat esc "|"))))) + +(defun consult--valid-regexp-p (re) + "Return t if regexp RE is valid." + (condition-case nil + (progn (string-match-p re "") t) + (invalid-regexp nil))) + +(defun consult--regexp-filter (regexps) + "Create filter regexp from REGEXPS." + (if (stringp regexps) + regexps + (mapconcat (lambda (x) (concat "\\(?:" x "\\)")) regexps "\\|"))) + +;;;; Lookup functions + +(defun consult--lookup-member (selected candidates &rest _) + "Lookup SELECTED in CANDIDATES list, return original element." + (car (member selected candidates))) + +(defun consult--lookup-cons (selected candidates &rest _) + "Lookup SELECTED in CANDIDATES alist, return cons." + (assoc selected candidates)) + +(defun consult--lookup-cdr (selected candidates &rest _) + "Lookup SELECTED in CANDIDATES alist, return `cdr' of element." + (cdr (assoc selected candidates))) + +(defun consult--lookup-location (selected candidates &rest _) + "Lookup SELECTED in CANDIDATES list of `consult-location' category. +Return the location marker." + (when-let (found (member selected candidates)) + (setq found (car (consult--get-location (car found)))) + ;; Check that marker is alive + (and (or (not (markerp found)) (marker-buffer found)) found))) + +(defun consult--lookup-prop (prop selected candidates &rest _) + "Lookup SELECTED in CANDIDATES list and return PROP value." + (when-let (found (member selected candidates)) + (get-text-property 0 prop (car found)))) + +(defun consult--lookup-candidate (selected candidates &rest _) + "Lookup SELECTED in CANDIDATES list and return property `consult--candidate'." + (consult--lookup-prop 'consult--candidate selected candidates)) + +;;;; Preview support + +(defun consult--filter-find-file-hook (orig &rest hooks) + "Filter `find-file-hook' by `consult-preview-allowed-hooks'. +This function is an advice for `run-hooks'. +ORIG is the original function, HOOKS the arguments." + (if (memq 'find-file-hook hooks) + (cl-letf* (((default-value 'find-file-hook) + (seq-filter (lambda (x) + (memq x consult-preview-allowed-hooks)) + (default-value 'find-file-hook))) + (find-file-hook (default-value 'find-file-hook))) + (apply orig hooks)) + (apply orig hooks))) + +(defun consult--find-file-temporarily-1 (name) + "Open file NAME, helper function for `consult--find-file-temporarily'." + (when-let (((not (seq-find (lambda (x) (string-match-p x name)) + consult-preview-excluded-files))) + ;; file-attributes may throw permission denied error + (attrs (ignore-errors (file-attributes name))) + (size (file-attribute-size attrs))) + (let* ((partial (>= size consult-preview-partial-size)) + (buffer (if partial + (generate-new-buffer (format "consult-partial-preview-%s" name)) + (find-file-noselect name 'nowarn))) + (success nil)) + (unwind-protect + (with-current-buffer buffer + (if (not partial) + (when (or (eq major-mode 'hexl-mode) + (and (eq major-mode 'fundamental-mode) + (save-excursion (search-forward "\0" nil 'noerror)))) + (error "No preview of binary file `%s'" + (file-name-nondirectory name))) + (with-silent-modifications + (setq buffer-read-only t) + (insert-file-contents name nil 0 consult-preview-partial-chunk) + (goto-char (point-max)) + (insert "\nFile truncated. End of partial preview.\n") + (goto-char (point-min))) + (when (save-excursion (search-forward "\0" nil 'noerror)) + (error "No partial preview of binary file `%s'" + (file-name-nondirectory name))) + ;; Auto detect major mode and hope for the best, given that the + ;; file is only previewed partially. If an error is thrown the + ;; buffer will be killed and preview is aborted. + (set-auto-mode) + (font-lock-mode 1)) + (when (bound-and-true-p so-long-detected-p) + (error "No preview of file `%s' with long lines" + (file-name-nondirectory name))) + (setq success (current-buffer))) + (unless success + (kill-buffer buffer)))))) + +(defun consult--find-file-temporarily (name) + "Open file NAME temporarily for preview." + (let ((vars (delq nil + (mapcar + (pcase-lambda (`(,k . ,v)) + (if (boundp k) + (list k v (default-value k) (symbol-value k)) + (message "consult-preview-variables: The variable `%s' is not bound" k) + nil)) + consult-preview-variables)))) + (condition-case err + (unwind-protect + (progn + (advice-add #'run-hooks :around #'consult--filter-find-file-hook) + (pcase-dolist (`(,k ,v . ,_) vars) + (set-default k v) + (set k v)) + (consult--find-file-temporarily-1 name)) + (advice-remove #'run-hooks #'consult--filter-find-file-hook) + (pcase-dolist (`(,k ,_ ,d ,v) vars) + (set-default k d) + (set k v))) + (error + (message "%s" (error-message-string err)) + nil)))) + +(defun consult--temporary-files () + "Return a function to open files temporarily for preview." + (let ((dir default-directory) + (hook (make-symbol "consult--temporary-files-upgrade-hook")) + (orig-buffers (buffer-list)) + temporary-buffers) + (fset hook + (lambda (_) + ;; Fully initialize previewed files and keep them alive. + (unless (consult--completion-window-p) + (let (live-files) + (pcase-dolist (`(,file . ,buf) temporary-buffers) + (when-let (wins (and (buffer-live-p buf) + (get-buffer-window-list buf))) + (push (cons file (mapcar + (lambda (win) + (cons win (window-state-get win t))) + wins)) + live-files))) + (pcase-dolist (`(,_ . ,buf) temporary-buffers) + (kill-buffer buf)) + (setq temporary-buffers nil) + (pcase-dolist (`(,file . ,wins) live-files) + (when-let (buf (find-file-noselect file)) + (push buf orig-buffers) + (pcase-dolist (`(,win . ,state) wins) + (setf (car (alist-get 'buffer state)) buf) + (window-state-put state win)))))))) + (lambda (&optional name) + (if name + (let ((default-directory dir)) + (setq name (abbreviate-file-name (expand-file-name name))) + (or + ;; Find existing fully initialized buffer (non-previewed). We have + ;; to check for fully initialized buffer before accessing the + ;; previewed buffers, since `embark-act' can open a buffer which is + ;; currently previewed, such that we end up with two buffers for + ;; the same file - one previewed and only partially initialized and + ;; one fully initialized. In this case we prefer the fully + ;; initialized buffer. For directories `get-file-buffer' returns nil, + ;; therefore we have to special case Dired. + (if (and (fboundp 'dired-find-buffer-nocreate) (file-directory-p name)) + (dired-find-buffer-nocreate name) + (get-file-buffer name)) + ;; Find existing previewed buffer. Previewed buffers are not fully + ;; initialized (hooks are delayed) in order to ensure fast preview. + (cdr (assoc name temporary-buffers)) + ;; Finally, if no existing buffer has been found, open the file for + ;; preview. + (when-let (buf (consult--find-file-temporarily name)) + ;; Only add new buffer if not already in the list + (unless (or (rassq buf temporary-buffers) (memq buf orig-buffers)) + (add-hook 'window-selection-change-functions hook) + (push (cons name buf) temporary-buffers) + ;; Disassociate buffer from file by setting `buffer-file-name' + ;; and `dired-directory' to nil and rename the buffer. This + ;; lets us open an already previewed buffer with the Embark + ;; default action C-. RET. + (with-current-buffer buf + (rename-buffer + (format " Preview:%s" + (file-name-nondirectory (directory-file-name name))) + 'unique)) + ;; The buffer disassociation is delayed to avoid breaking modes + ;; like `pdf-view-mode' or `doc-view-mode' which rely on + ;; `buffer-file-name'. Executing (set-visited-file-name nil) + ;; early also prevents the major mode initialization. + (let ((hook (make-symbol "consult--temporary-files-disassociate-hook"))) + (fset hook (lambda () + (when (buffer-live-p buf) + (with-current-buffer buf + (remove-hook 'pre-command-hook hook) + (setq-local buffer-read-only t + dired-directory nil + buffer-file-name nil))))) + (add-hook 'pre-command-hook hook)) + ;; Only keep a few buffers alive + (while (length> temporary-buffers consult-preview-max-count) + (kill-buffer (cdar (last temporary-buffers))) + (setq temporary-buffers (nbutlast temporary-buffers)))) + buf))) + (remove-hook 'window-selection-change-functions hook) + (pcase-dolist (`(,_ . ,buf) temporary-buffers) + (kill-buffer buf)) + (setq temporary-buffers nil))))) + +(defun consult--invisible-open-permanently () + "Open overlays which hide the current line. +See `isearch-open-necessary-overlays' and `isearch-open-overlay-temporary'." + (if (and (derived-mode-p 'org-mode) (fboundp 'org-fold-show-set-visibility)) + ;; New Org 9.6 fold-core API + (let ((inhibit-redisplay t)) ;; HACK: Prevent flicker due to premature redisplay + (org-fold-show-set-visibility 'canonical)) + (dolist (ov (overlays-in (pos-bol) (pos-eol))) + (when-let (fun (overlay-get ov 'isearch-open-invisible)) + (when (invisible-p (overlay-get ov 'invisible)) + (funcall fun ov)))))) + +(defun consult--invisible-open-temporarily () + "Temporarily open overlays which hide the current line. +See `isearch-open-necessary-overlays' and `isearch-open-overlay-temporary'." + (if (and (derived-mode-p 'org-mode) + (fboundp 'org-fold-show-set-visibility) + (fboundp 'org-fold-core-get-regions) + (fboundp 'org-fold-core-region)) + ;; New Org 9.6 fold-core API + ;; TODO The provided Org API `org-fold-show-set-visibility' cannot be used + ;; efficiently. We obtain all regions in the whole buffer in order to + ;; restore them. A better show API would return all the applied + ;; modifications such that we can restore the ones which got modified. + (progn + (unless consult--org-fold-regions + (setq consult--org-fold-regions + (delq nil (org-fold-core-get-regions + :with-markers t :from (point-min) :to (point-max)))) + (when consult--org-fold-regions + (let ((hook (make-symbol "consult--invisible-open-temporarily-cleanup-hook")) + (buffer (current-buffer)) + (depth (recursion-depth))) + (fset hook + (lambda () + (when (= (recursion-depth) depth) + (remove-hook 'minibuffer-exit-hook hook) + (run-at-time + 0 nil + (lambda () + (when (buffer-live-p buffer) + (with-current-buffer buffer + (pcase-dolist (`(,beg ,end ,_) consult--org-fold-regions) + (when (markerp beg) (set-marker beg nil)) + (when (markerp end) (set-marker end nil))) + (kill-local-variable 'consult--org-fold-regions)))))))) + (add-hook 'minibuffer-exit-hook hook)))) + (let ((inhibit-redisplay t)) ;; HACK: Prevent flicker due to premature redisplay + (org-fold-show-set-visibility 'canonical)) + (list (lambda () + (pcase-dolist (`(,beg ,end ,spec) consult--org-fold-regions) + (org-fold-core-region beg end t spec))))) + (let (restore) + (dolist (ov (overlays-in (pos-bol) (pos-eol))) + (let ((inv (overlay-get ov 'invisible))) + (when (and (invisible-p inv) (overlay-get ov 'isearch-open-invisible)) + (push (if-let (fun (overlay-get ov 'isearch-open-invisible-temporary)) + (progn + (funcall fun ov nil) + (lambda () (funcall fun ov t))) + (overlay-put ov 'invisible nil) + (lambda () (overlay-put ov 'invisible inv))) + restore)))) + restore))) + +(defun consult--jump-ensure-buffer (pos) + "Ensure that buffer of marker POS is displayed, return t if successful." + (or (not (markerp pos)) + ;; Switch to buffer if it is not visible + (when-let ((buf (marker-buffer pos))) + (or (and (eq (current-buffer) buf) (eq (window-buffer) buf)) + (consult--buffer-action buf 'norecord) + t)))) + +(defun consult--jump (pos) + "Jump to POS. +First push current position to mark ring, then move to new +position and run `consult-after-jump-hook'." + (when pos + ;; Extract marker from list with with overlay positions, see `consult--line-match' + (when (consp pos) (setq pos (car pos))) + ;; When the marker is in the same buffer, record previous location + ;; such that the user can jump back quickly. + (when (or (not (markerp pos)) (eq (current-buffer) (marker-buffer pos))) + ;; push-mark mutates markers in the mark-ring and the mark-marker. + ;; Therefore we transform the marker to a number to be safe. + ;; We all love side effects! + (setq pos (+ pos 0)) + (push-mark (point) t)) + (when (consult--jump-ensure-buffer pos) + (unless (= (goto-char pos) (point)) ;; Widen if jump failed + (widen) + (goto-char pos)) + (consult--invisible-open-permanently) + (run-hooks 'consult-after-jump-hook))) + nil) + +(defun consult--jump-preview () + "The preview function used if selecting from a list of candidate positions. +The function can be used as the `:state' argument of `consult--read'." + (let (restore) + (lambda (action cand) + (when (eq action 'preview) + (mapc #'funcall restore) + (setq restore nil) + ;; TODO Better buffer preview support + ;; 1. Use consult--buffer-preview instead of consult--jump-ensure-buffer + ;; 2. Remove function consult--jump-ensure-buffer + ;; 3. Remove consult-buffer-other-* from consult-customize-alist + (when-let ((pos (or (car-safe cand) cand)) ;; Candidate can be previewed + ((consult--jump-ensure-buffer pos))) + (let ((saved-min (point-min-marker)) + (saved-max (point-max-marker)) + (saved-pos (point-marker))) + (set-marker-insertion-type saved-max t) ;; Grow when text is inserted + (push (lambda () + (when-let ((buf (marker-buffer saved-pos))) + (with-current-buffer buf + (narrow-to-region saved-min saved-max) + (goto-char saved-pos) + (set-marker saved-pos nil) + (set-marker saved-min nil) + (set-marker saved-max nil)))) + restore)) + (unless (= (goto-char pos) (point)) ;; Widen if jump failed + (widen) + (goto-char pos)) + (setq restore (nconc (consult--invisible-open-temporarily) restore)) + ;; Ensure that cursor is properly previewed (gh:minad/consult#764) + (unless (eq cursor-in-non-selected-windows 'box) + (let ((orig cursor-in-non-selected-windows) + (buf (current-buffer))) + (push + (if (local-variable-p 'cursor-in-non-selected-windows) + (lambda () + (when (buffer-live-p buf) + (with-current-buffer buf + (setq-local cursor-in-non-selected-windows orig)))) + (lambda () + (when (buffer-live-p buf) + (with-current-buffer buf + (kill-local-variable 'cursor-in-non-selected-windows))))) + restore) + (setq-local cursor-in-non-selected-windows 'box))) + ;; Match previews + (let ((overlays + (list (save-excursion + (let ((vbeg (progn (beginning-of-visual-line) (point))) + (vend (progn (end-of-visual-line) (point))) + (end (pos-eol))) + (consult--make-overlay vbeg (if (= vend end) (1+ end) vend) + 'face 'consult-preview-line + 'window (selected-window) + 'priority 1)))))) + (dolist (match (cdr-safe cand)) + (push (consult--make-overlay (+ (point) (car match)) + (+ (point) (cdr match)) + 'face 'consult-preview-match + 'window (selected-window) + 'priority 2) + overlays)) + (push (lambda () (mapc #'delete-overlay overlays)) restore)) + (run-hooks 'consult-after-jump-hook)))))) + +(defun consult--jump-state () + "The state function used if selecting from a list of candidate positions." + (consult--state-with-return (consult--jump-preview) #'consult--jump)) + +(defun consult--get-location (cand) + "Return location from CAND." + (let ((loc (get-text-property 0 'consult-location cand))) + (when (consp (car loc)) + ;; Transform cheap marker to real marker + (setcar loc (set-marker (make-marker) (cdar loc) (caar loc)))) + loc)) + +(defun consult--location-state (candidates) + "Location state function. +The cheap location markers from CANDIDATES are upgraded on window +selection change to full Emacs markers." + (let ((jump (consult--jump-state)) + (hook (make-symbol "consult--location-upgrade-hook"))) + (fset hook + (lambda (_) + (unless (consult--completion-window-p) + (remove-hook 'window-selection-change-functions hook) + (mapc #'consult--get-location + (if (functionp candidates) (funcall candidates) candidates))))) + (lambda (action cand) + (pcase action + ('setup (add-hook 'window-selection-change-functions hook)) + ('exit (remove-hook 'window-selection-change-functions hook))) + (funcall jump action cand)))) + +(defun consult--state-with-return (state return) + "Compose STATE function with RETURN function." + (lambda (action cand) + (funcall state action cand) + (when (and cand (eq action 'return)) + (funcall return cand)))) + +(defmacro consult--define-state (type) + "Define state function for TYPE." + `(defun ,(intern (format "consult--%s-state" type)) () + ,(format "State function for %ss with preview. +The result can be passed as :state argument to `consult--read'." type) + (consult--state-with-return (,(intern (format "consult--%s-preview" type))) + #',(intern (format "consult--%s-action" type))))) + +(defun consult--preview-key-normalize (preview-key) + "Normalize PREVIEW-KEY, return alist of keys and debounce times." + (let ((keys) + (debounce 0)) + (setq preview-key (ensure-list preview-key)) + (while preview-key + (if (eq (car preview-key) :debounce) + (setq debounce (cadr preview-key) + preview-key (cddr preview-key)) + (let ((key (car preview-key))) + (unless (eq key 'any) + (setq key (consult--key-parse key))) + (push (cons key debounce) keys)) + (pop preview-key))) + keys)) + +(defun consult--preview-key-debounce (preview-key cand) + "Return debounce value of PREVIEW-KEY given the current candidate CAND." + (when (and (consp preview-key) (memq :keys preview-key)) + (setq preview-key (funcall (plist-get preview-key :predicate) cand))) + (let ((map (make-sparse-keymap)) + (keys (this-single-command-keys)) + any) + (pcase-dolist (`(,k . ,d) (consult--preview-key-normalize preview-key)) + (if (eq k 'any) + (setq any d) + (define-key map k `(lambda () ,d)))) + (setq keys (lookup-key map keys)) + (if (functionp keys) (funcall keys) any))) + +(defun consult--preview-append-local-pch (fun) + "Append FUN to local `post-command-hook' list." + ;; Symbol indirection because of bug#46407. + (let ((hook (make-symbol "consult--preview-post-command-hook"))) + (fset hook fun) + ;; TODO Emacs 28 has a bug, where the hook--depth-alist is not cleaned up properly + ;; Do not use the broken add-hook here. + ;;(add-hook 'post-command-hook hook 'append 'local) + (setq-local post-command-hook + (append + (remove t post-command-hook) + (list hook) + (and (memq t post-command-hook) '(t)))))) + +(defun consult--with-preview-1 (preview-key state transform candidate save-input fun) + "Add preview support for FUN. +See `consult--with-preview' for the arguments +PREVIEW-KEY, STATE, TRANSFORM, CANDIDATE and SAVE-INPUT." + (let ((mb-input "") mb-narrow selected timer previewed) + (consult--minibuffer-with-setup-hook + (if (and state preview-key) + (lambda () + (let ((hook (make-symbol "consult--preview-minibuffer-exit-hook")) + (depth (recursion-depth))) + (fset hook + (lambda () + (when (= (recursion-depth) depth) + (remove-hook 'minibuffer-exit-hook hook) + (when timer + (cancel-timer timer) + (setq timer nil)) + (with-selected-window (consult--original-window) + ;; STEP 3: Reset preview + (when previewed + (funcall state 'preview nil)) + ;; STEP 4: Notify the preview function of the minibuffer exit + (funcall state 'exit nil))))) + (add-hook 'minibuffer-exit-hook hook)) + ;; STEP 1: Setup the preview function + (with-selected-window (consult--original-window) + (funcall state 'setup nil)) + (setq consult--preview-function + (lambda () + (when-let ((cand (funcall candidate))) + ;; Drop properties to prevent bugs regarding candidate + ;; lookup, which must handle candidates without + ;; properties. Otherwise the arguments passed to the + ;; lookup function are confusing, since during preview + ;; the candidate has properties but for the final lookup + ;; after completion it does not. + (setq cand (substring-no-properties cand)) + (with-selected-window (active-minibuffer-window) + (let ((input (minibuffer-contents-no-properties)) + (narrow consult--narrow) + (win (consult--original-window))) + (with-selected-window win + (when-let ((transformed (funcall transform narrow input cand)) + (debounce (consult--preview-key-debounce preview-key transformed))) + (when timer + (cancel-timer timer) + (setq timer nil)) + ;; The transformed candidate may have text + ;; properties, which change the preview display. + ;; This matters for example for `consult-grep', + ;; where the current candidate and input may + ;; stay equal, but the highlighting of the + ;; candidate changes while the candidates list + ;; is lagging a bit behind and updates + ;; asynchronously. + ;; + ;; In older Consult versions we instead compared + ;; the input without properties, since I worried + ;; that comparing the transformed candidates + ;; could be potentially expensive. However + ;; comparing the transformed candidates is more + ;; correct. The transformed candidate is the + ;; thing which is actually previewed. + (unless (equal-including-properties previewed transformed) + (if (> debounce 0) + (setq timer + (run-at-time + debounce nil + (lambda () + ;; Preview only when a completion + ;; window is selected and when + ;; the preview window is alive. + (when (and (consult--completion-window-p) + (window-live-p win)) + (with-selected-window win + ;; STEP 2: Preview candidate + (funcall state 'preview (setq previewed transformed))))))) + ;; STEP 2: Preview candidate + (funcall state 'preview (setq previewed transformed))))))))))) + (consult--preview-append-local-pch + (lambda () + (setq mb-input (minibuffer-contents-no-properties) + mb-narrow consult--narrow) + (funcall consult--preview-function)))) + (lambda () + (consult--preview-append-local-pch + (lambda () + (setq mb-input (minibuffer-contents-no-properties) + mb-narrow consult--narrow))))) + (unwind-protect + (setq selected (when-let (result (funcall fun)) + (when-let ((save-input) + (list (symbol-value save-input)) + ((equal (car list) result))) + (set save-input (cdr list))) + (funcall transform mb-narrow mb-input result))) + (when save-input + (add-to-history save-input mb-input)) + (when state + ;; STEP 5: The preview function should perform its final action + (funcall state 'return selected)))))) + +(defmacro consult--with-preview (preview-key state transform candidate save-input &rest body) + "Add preview support to BODY. + +STATE is the state function. +TRANSFORM is the transformation function. +CANDIDATE is the function returning the current candidate. +PREVIEW-KEY are the keys which triggers the preview. +SAVE-INPUT can be a history variable symbol to save the input. + +The state function takes two arguments, an action argument and the +selected candidate. The candidate argument can be nil if no candidate is +selected or if the selection was aborted. The function is called in +sequence with the following arguments: + + 1. \\='setup nil After entering the mb (minibuffer-setup-hook). +⎧ 2. \\='preview CAND/nil Preview candidate CAND or reset if CAND is nil. +⎪ \\='preview CAND/nil +⎪ \\='preview CAND/nil +⎪ ... +⎩ 3. \\='preview nil Reset preview. + 4. \\='exit nil Before exiting the mb (minibuffer-exit-hook). + 5. \\='return CAND/nil After leaving the mb, CAND has been selected. + +The state function is always executed with the original window selected, +see `consult--original-window'. The state function is called once in +the beginning of the minibuffer setup with the `setup' argument. This is +useful in order to perform certain setup operations which require that +the minibuffer is initialized. During completion candidates are +previewed. Then the function is called with the `preview' argument and a +candidate CAND or nil if no candidate is selected. Furthermore if nil is +passed for CAND, then the preview must be undone and the original state +must be restored. The call with the `exit' argument happens once at the +end of the completion process, just before exiting the minibuffer. The +minibuffer is still alive at that point. Both `setup' and `exit' are +only useful for setup and cleanup operations. They don't receive a +candidate as argument. After leaving the minibuffer, the selected +candidate or nil is passed to the state function with the action +argument `return'. At this point the state function can perform the +actual action on the candidate. The state function with the `return' +argument is the continuation of `consult--read'. Via `unwind-protect' it +is guaranteed, that if the `setup' action of a state function is +invoked, the state function will also be called with `exit' and +`return'." + (declare (indent 5)) + `(consult--with-preview-1 ,preview-key ,state ,transform ,candidate ,save-input (lambda () ,@body))) + +;;;; Narrowing and grouping + +(defun consult--prefix-group (cand transform) + "Return title for CAND or TRANSFORM the candidate. +The candidate must have a `consult--prefix-group' property." + (if transform + (substring cand (1+ (length (get-text-property 0 'consult--prefix-group cand)))) + (get-text-property 0 'consult--prefix-group cand))) + +(defun consult--type-group (types) + "Return group function for TYPES." + (lambda (cand transform) + (if transform cand + (alist-get (get-text-property 0 'consult--type cand) types)))) + +(defun consult--type-narrow (types) + "Return narrowing configuration from TYPES." + (list :predicate + (lambda (cand) (eq (get-text-property 0 'consult--type cand) consult--narrow)) + :keys types)) + +(defun consult--widen-key () + "Return widening key, if `consult-widen-key' is not set. +The default is twice the `consult-narrow-key'." + (cond + (consult-widen-key + (consult--key-parse consult-widen-key)) + (consult-narrow-key + (let ((key (consult--key-parse consult-narrow-key))) + (vconcat key key))))) + +(defun consult-narrow (key) + "Narrow current completion with KEY. + +This command is used internally by the narrowing system of `consult--read'." + (interactive + (list (unless (equal (this-single-command-keys) (consult--widen-key)) + last-command-event))) + (consult--require-minibuffer) + (setq consult--narrow key) + (when consult--narrow-predicate + (setq minibuffer-completion-predicate (and consult--narrow consult--narrow-predicate))) + (when consult--narrow-overlay + (delete-overlay consult--narrow-overlay)) + (when consult--narrow + (setq consult--narrow-overlay + (consult--make-overlay + (1- (minibuffer-prompt-end)) (minibuffer-prompt-end) + 'before-string + (propertize (format " [%s]" (alist-get consult--narrow + consult--narrow-keys)) + 'face 'consult-narrow-indicator)))) + (run-hooks 'consult--completion-refresh-hook)) + +(defconst consult--narrow-delete + `(menu-item + "" nil :filter + ,(lambda (&optional _) + (when (equal (minibuffer-contents-no-properties) "") + (lambda () + (interactive) + (consult-narrow nil)))))) + +(defconst consult--narrow-space + `(menu-item + "" nil :filter + ,(lambda (&optional _) + (let ((str (minibuffer-contents-no-properties))) + (when-let (pair (or (and (length= str 1) + (assoc (aref str 0) consult--narrow-keys)) + (and (equal str "") + (assoc ?\s consult--narrow-keys)))) + (lambda () + (interactive) + (delete-minibuffer-contents) + (consult-narrow (car pair)))))))) + +(defun consult-narrow-help () + "Print narrowing help as a `minibuffer-message'. + +This command can be bound to a key in `consult-narrow-map', +to make it available for commands with narrowing." + (interactive) + (consult--require-minibuffer) + (let ((minibuffer-message-timeout 1000000)) + (minibuffer-message + (mapconcat (lambda (x) + (concat + (propertize (key-description (list (car x))) 'face 'consult-key) + " " + (propertize (cdr x) 'face 'consult-help))) + consult--narrow-keys + " ")))) + +(defun consult--narrow-setup (settings map) + "Setup narrowing with SETTINGS and keymap MAP." + (if (memq :keys settings) + (setq consult--narrow-predicate (plist-get settings :predicate) + consult--narrow-keys (plist-get settings :keys)) + (setq consult--narrow-predicate nil + consult--narrow-keys settings)) + (when-let ((key consult-narrow-key)) + (setq key (consult--key-parse key)) + (dolist (pair consult--narrow-keys) + (define-key map (vconcat key (vector (car pair))) + (cons (cdr pair) #'consult-narrow)))) + (when-let ((widen (consult--widen-key))) + (define-key map widen (cons "All" #'consult-narrow))) + (when-let ((init (and (memq :keys settings) (plist-get settings :initial)))) + (consult-narrow init))) + +;; Emacs 28: hide in M-X +(put #'consult-narrow-help 'completion-predicate #'ignore) +(put #'consult-narrow 'completion-predicate #'ignore) + +;;;; Splitting completion style + +(defun consult--split-perl (str &optional _plist) + "Split input STR in async input and filtering part. + +The function returns a list with three elements: The async +string, the start position of the completion filter string and a +force flag. If the first character is a punctuation character it +determines the separator. Examples: \"/async/filter\", +\"#async#filter\"." + (if (string-match-p "^[[:punct:]]" str) + (save-match-data + (let ((q (regexp-quote (substring str 0 1)))) + (string-match (concat "^" q "\\([^" q "]*\\)\\(" q "\\)?") str) + `(,(match-string 1 str) + ,(match-end 0) + ;; Force update it two punctuation characters are entered. + ,(match-end 2) + ;; List of highlights + (0 . ,(match-beginning 1)) + ,@(and (match-end 2) `((,(match-beginning 2) . ,(match-end 2))))))) + `(,str ,(length str)))) + +(defun consult--split-nil (str &optional _plist) + "Treat the complete input STR as async input." + `(,str ,(length str))) + +(defun consult--split-separator (str plist) + "Split input STR in async input and filtering part at first separator. +PLIST is the splitter configuration, including the separator." + (let ((sep (regexp-quote (char-to-string (plist-get plist :separator))))) + (save-match-data + (if (string-match (format "^\\([^%s]+\\)\\(%s\\)?" sep sep) str) + `(,(match-string 1 str) + ,(match-end 0) + ;; Force update it space is entered. + ,(match-end 2) + ;; List of highlights + ,@(and (match-end 2) `((,(match-beginning 2) . ,(match-end 2))))) + `(,str ,(length str)))))) + +(defun consult--split-setup (split) + "Setup splitting completion style with splitter function SPLIT." + (let* ((styles completion-styles) + (catdef completion-category-defaults) + (catovr completion-category-overrides) + (try (lambda (str table pred point) + (let ((completion-styles styles) + (completion-category-defaults catdef) + (completion-category-overrides catovr) + (pos (cadr (funcall split str)))) + (pcase (completion-try-completion (substring str pos) table pred + (max 0 (- point pos))) + ('t t) + (`(,newstr . ,newpt) + (cons (concat (substring str 0 pos) newstr) + (+ pos newpt))))))) + (all (lambda (str table pred point) + (let ((completion-styles styles) + (completion-category-defaults catdef) + (completion-category-overrides catovr) + (pos (cadr (funcall split str)))) + (completion-all-completions (substring str pos) table pred + (max 0 (- point pos))))))) + (setq-local completion-styles-alist (cons `(consult--split ,try ,all "") + completion-styles-alist) + completion-styles '(consult--split) + completion-category-defaults nil + completion-category-overrides nil))) + +;;;; Asynchronous filtering functions + +(defun consult--async-p (fun) + "Return t if FUN is an asynchronous completion function." + (and (functionp fun) + (condition-case nil + (progn (funcall fun "" nil 'metadata) nil) + (wrong-number-of-arguments t)))) + +(defmacro consult--with-async (bind &rest body) + "Setup asynchronous completion in BODY. + +BIND is the asynchronous function binding." + (declare (indent 1)) + (let ((async (car bind))) + `(let ((,async ,@(cdr bind)) + (new-chunk (max read-process-output-max consult--process-chunk)) + orig-chunk) + (consult--minibuffer-with-setup-hook + ;; Append such that we overwrite the completion style setting of + ;; `fido-mode'. See `consult--async-split' and + ;; `consult--split-setup'. + (:append + (lambda () + (when (consult--async-p ,async) + (setq orig-chunk read-process-output-max + read-process-output-max new-chunk) + (funcall ,async 'setup) + (let* ((mb (current-buffer)) + (fun (lambda () + (when-let (win (active-minibuffer-window)) + (when (eq (window-buffer win) mb) + (with-current-buffer mb + (let ((inhibit-modification-hooks t)) + ;; Push input string to request refresh. + (funcall ,async (minibuffer-contents-no-properties)))))))) + ;; We use a symbol in order to avoid adding lambdas to + ;; the hook variable. Symbol indirection because of + ;; bug#46407. + (hook (make-symbol "consult--async-after-change-hook"))) + ;; Delay modification hook to ensure that minibuffer is still + ;; alive after the change, such that we don't restart a new + ;; asynchronous search right before exiting the minibuffer. + (fset hook (lambda (&rest _) (run-at-time 0 nil fun))) + (add-hook 'after-change-functions hook nil 'local) + (funcall hook))))) + (let ((,async (if (consult--async-p ,async) ,async (lambda (_) ,async)))) + (unwind-protect + ,(macroexp-progn body) + (funcall ,async 'destroy) + (when (and orig-chunk (eq read-process-output-max new-chunk)) + (setq read-process-output-max orig-chunk)))))))) + +(defun consult--async-sink () + "Create ASYNC sink function. + +An async function must accept a single action argument. For the +\\='setup action it is guaranteed that the call originates from +the minibuffer. For the other actions no assumption about the +context can be made. + +\\='setup Setup the internal closure state. Return nil. +\\='destroy Destroy the internal closure state. Return nil. +\\='flush Flush the list of candidates. Return nil. +\\='refresh Request UI refresh. Return nil. +nil Return the list of candidates. +list Append the list to the already existing candidates list and return it. +string Update with the current user input string. Return nil." + (let (candidates last buffer) + (lambda (action) + (pcase-exhaustive action + ('setup + (setq buffer (current-buffer)) + nil) + ((or (pred stringp) 'destroy) nil) + ('flush (setq candidates nil last nil)) + ('refresh + ;; Refresh the UI when the current minibuffer window belongs + ;; to the current asynchronous completion session. + (when-let (win (active-minibuffer-window)) + (when (eq (window-buffer win) buffer) + (with-selected-window win + (run-hooks 'consult--completion-refresh-hook) + ;; Interaction between asynchronous completion functions and + ;; preview: We have to trigger preview immediately when + ;; candidates arrive (gh:minad/consult#436). + (when (and consult--preview-function candidates) + (funcall consult--preview-function))))) + nil) + ('nil candidates) + ((pred consp) + (setq last (last (if last (setcdr last action) (setq candidates action)))) + candidates))))) + +(defun consult--async-split-style () + "Return the async splitting style function and initial string." + (or (alist-get consult-async-split-style consult-async-split-styles-alist) + (user-error "Splitting style `%s' not found" consult-async-split-style))) + +(defun consult--async-split-initial (initial) + "Return initial string for async command. +INITIAL is the additional initial string." + (concat (plist-get (consult--async-split-style) :initial) initial)) + +(defun consult--async-split-thingatpt (thing) + "Return THING at point with async initial prefix." + (when-let (str (thing-at-point thing)) + (consult--async-split-initial str))) + +(defun consult--async-split (async &optional split) + "Create async function, which splits the input string. +ASYNC is the async sink. +SPLIT is the splitting function." + (unless split + (let* ((style (consult--async-split-style)) + (fn (plist-get style :function))) + (setq split (lambda (str) (funcall fn str style))))) + (lambda (action) + (pcase action + ('setup + (consult--split-setup split) + (funcall async 'setup)) + ((pred stringp) + (pcase-let* ((`(,async-str ,_ ,force . ,highlights) + (funcall split action)) + (async-len (length async-str)) + (input-len (length action)) + (end (minibuffer-prompt-end))) + ;; Highlight punctuation characters + (remove-list-of-text-properties end (+ end input-len) '(face)) + (dolist (hl highlights) + (put-text-property (+ end (car hl)) (+ end (cdr hl)) + 'face 'consult-async-split)) + (funcall async + ;; Pass through if the input is long enough! + (if (or force (>= async-len consult-async-min-input)) + async-str + ;; Pretend that there is no input + "")))) + (_ (funcall async action))))) + +(defun consult--async-indicator (async) + "Create async function with a state indicator overlay. +ASYNC is the async sink." + (let (ov) + (lambda (action &optional state) + (pcase action + ('indicator + (overlay-put ov 'display + (pcase-exhaustive state + ('running #("*" 0 1 (face consult-async-running))) + ('finished #(":" 0 1 (face consult-async-finished))) + ('killed #(";" 0 1 (face consult-async-failed))) + ('failed #("!" 0 1 (face consult-async-failed)))))) + ('setup + (setq ov (make-overlay (- (minibuffer-prompt-end) 2) + (- (minibuffer-prompt-end) 1))) + (funcall async 'setup)) + ('destroy + (delete-overlay ov) + (funcall async 'destroy)) + (_ (funcall async action)))))) + +(defun consult--async-log (formatted &rest args) + "Log FORMATTED ARGS to variable `consult--async-log'." + (with-current-buffer (get-buffer-create consult--async-log) + (goto-char (point-max)) + (insert (apply #'format formatted args)))) + +(defun consult--async-process (async builder &rest props) + "Create process source async function. + +ASYNC is the async function which receives the candidates. +BUILDER is the command line builder function. +PROPS are optional properties passed to `make-process'." + (setq async (consult--async-indicator async)) + (let (proc proc-buf last-args count) + (lambda (action) + (pcase action + ("" ;; If no input is provided kill current process + (when proc + (delete-process proc) + (kill-buffer proc-buf) + (setq proc nil proc-buf nil)) + (setq last-args nil)) + ((pred stringp) + (funcall async action) + (let* ((args (funcall builder action))) + (unless (stringp (car args)) + (setq args (car args))) + (unless (equal args last-args) + (setq last-args args) + (when proc + (delete-process proc) + (kill-buffer proc-buf) + (setq proc nil proc-buf nil)) + (when args + (let* ((flush t) + (rest "") + (proc-filter + (lambda (_ out) + (when flush + (setq flush nil) + (funcall async 'flush)) + (let ((lines (split-string out "[\r\n]+"))) + (if (not (cdr lines)) + (setq rest (concat rest (car lines))) + (setcar lines (concat rest (car lines))) + (let* ((len (length lines)) + (last (nthcdr (- len 2) lines))) + (setq rest (cadr last) + count (+ count len -1)) + (setcdr last nil) + (funcall async lines)))))) + (proc-sentinel + (lambda (_ event) + (when flush + (setq flush nil) + (funcall async 'flush)) + (funcall async 'indicator + (cond + ((string-prefix-p "killed" event) 'killed) + ((string-prefix-p "finished" event) 'finished) + (t 'failed))) + (when (and (string-prefix-p "finished" event) (not (equal rest ""))) + (cl-incf count) + (funcall async (list rest))) + (consult--async-log + "consult--async-process sentinel: event=%s lines=%d\n" + (string-trim event) count) + (when (> (buffer-size proc-buf) 0) + (with-current-buffer (get-buffer-create consult--async-log) + (goto-char (point-max)) + (insert ">>>>> stderr >>>>>\n") + (let ((beg (point))) + (insert-buffer-substring proc-buf) + (save-excursion + (goto-char beg) + (message #("%s" 0 2 (face error)) + (buffer-substring-no-properties (pos-bol) (pos-eol))))) + (insert "<<<<< stderr <<<<<\n"))))) + (process-adaptive-read-buffering nil)) + (funcall async 'indicator 'running) + (consult--async-log "consult--async-process started %S\n" args) + (setq count 0 + proc-buf (generate-new-buffer " *consult-async-stderr*") + proc (apply #'make-process + `(,@props + :connection-type pipe + :name ,(car args) + ;;; XXX tramp bug, the stderr buffer must be empty + :stderr ,proc-buf + :noquery t + :command ,args + :filter ,proc-filter + :sentinel ,proc-sentinel))))))) + nil) + ('destroy + (when proc + (delete-process proc) + (kill-buffer proc-buf) + (setq proc nil proc-buf nil)) + (funcall async 'destroy)) + (_ (funcall async action)))))) + +(defun consult--async-highlight (async builder) + "Return a new ASYNC function with candidate highlighting. +BUILDER is the command line builder function." + (let (highlight) + (lambda (action) + (cond + ((stringp action) + (setq highlight (cdr (funcall builder action))) + (funcall async action)) + ((and (consp action) highlight) + (dolist (str action) + (funcall highlight str)) + (funcall async action)) + (t (funcall async action)))))) + +(defun consult--async-throttle (async &optional throttle debounce) + "Create async function from ASYNC which throttles input. + +The THROTTLE delay defaults to `consult-async-input-throttle'. +The DEBOUNCE delay defaults to `consult-async-input-debounce'." + (setq throttle (or throttle consult-async-input-throttle) + debounce (or debounce consult-async-input-debounce)) + (let* ((input "") (timer (timer-create)) (last 0)) + (lambda (action) + (pcase action + ((pred stringp) + (unless (equal action input) + (cancel-timer timer) + (funcall async "") ;; cancel running process + (setq input action) + (unless (equal action "") + (timer-set-function timer (lambda () + (setq last (float-time)) + (funcall async action))) + (timer-set-time + timer + (timer-relative-time + nil (max debounce (- (+ last throttle) (float-time))))) + (timer-activate timer))) + nil) + ('destroy + (cancel-timer timer) + (funcall async 'destroy)) + (_ (funcall async action)))))) + +(defun consult--async-refresh-immediate (async) + "Create async function from ASYNC, which refreshes the display. + +The refresh happens immediately when candidates are pushed." + (lambda (action) + (pcase action + ((or (pred consp) 'flush) + (prog1 (funcall async action) + (funcall async 'refresh))) + (_ (funcall async action))))) + +(defun consult--async-refresh-timer (async &optional delay) + "Create async function from ASYNC, which refreshes the display. + +The refresh happens after a DELAY, defaulting to `consult-async-refresh-delay'." + (let ((delay (or delay consult-async-refresh-delay)) + (timer (timer-create))) + (timer-set-function timer async '(refresh)) + (lambda (action) + (prog1 (funcall async action) + (pcase action + ((or (pred consp) 'flush) + (unless (memq timer timer-list) + (timer-set-time timer (timer-relative-time nil delay)) + (timer-activate timer))) + ('destroy + (cancel-timer timer))))))) + +(defmacro consult--async-command (builder &rest args) + "Asynchronous command pipeline. +ARGS is a list of `make-process' properties and transforms. +BUILDER is the command line builder function, which takes the +input string and must either return a list of command line +arguments or a pair of the command line argument list and a +highlighting function." + (declare (indent 1)) + `(thread-first + (consult--async-sink) + (consult--async-refresh-timer) + ,@(seq-take-while (lambda (x) (not (keywordp x))) args) + (consult--async-process + ,builder + ,@(seq-drop-while (lambda (x) (not (keywordp x))) args)) + (consult--async-throttle) + (consult--async-split))) + +(defmacro consult--async-transform (async &rest transform) + "Use FUN to TRANSFORM candidates of ASYNC." + (cl-with-gensyms (async-var action-var) + `(let ((,async-var ,async)) + (lambda (,action-var) + (funcall ,async-var (if (consp ,action-var) (,@transform ,action-var) ,action-var)))))) + +(defun consult--async-map (async fun) + "Map candidates of ASYNC by FUN." + (consult--async-transform async mapcar fun)) + +(defun consult--async-filter (async fun) + "Filter candidates of ASYNC by FUN." + (consult--async-transform async seq-filter fun)) + +;;;; Dynamic collections based + +(defun consult--dynamic-compute (async fun &optional debounce) + "Dynamic computation of candidates. +ASYNC is the sink. +FUN computes the candidates given the input. +DEBOUNCE is the time after which an interrupted computation +should be restarted." + (setq debounce (or debounce consult-async-input-debounce)) + (setq async (consult--async-indicator async)) + (let* ((request) (current) (timer) + (cancel (lambda () (when timer (cancel-timer timer) (setq timer nil)))) + (start (lambda (req) (setq request req) (funcall async 'refresh)))) + (lambda (action) + (pcase action + ((and 'nil (guard (not request))) + (funcall async nil)) + ('nil + (funcall cancel) + (let ((state 'killed)) + (unwind-protect + (progn + (funcall async 'indicator 'running) + (redisplay) + ;; Run computation + (let ((response (funcall fun request))) + ;; Flush and update candidate list + (funcall async 'flush) + (setq state 'finished current request) + (funcall async response))) + (funcall async 'indicator state) + ;; If the computation was killed, restart it after some time. + (when (eq state 'killed) + (setq timer (run-at-time debounce nil start request))) + (setq request nil)))) + ((pred stringp) + (funcall cancel) + (if (or (equal action "") (equal action current)) + (funcall async 'indicator 'finished) + (funcall start action))) + ('destroy + (funcall cancel) + (funcall async 'destroy)) + (_ (funcall async action)))))) + +(defun consult--dynamic-collection (fun) + "Dynamic collection with input splitting. +FUN computes the candidates given the input." + (thread-first + (consult--async-sink) + (consult--dynamic-compute fun) + (consult--async-throttle) + (consult--async-split))) + +;;;; Special keymaps + +(defvar-keymap consult-async-map + :doc "Keymap added for commands with asynchronous candidates." + ;; Overwriting some unusable defaults of default minibuffer completion. + " " #'self-insert-command + ;; Remap Emacs 29 history and default completion for now + ;; (gh:minad/consult#613). + " " #'ignore + " " #'consult-history) + +(defvar-keymap consult-narrow-map + :doc "Narrowing keymap which is added to the local minibuffer map. +Note that `consult-narrow-key' and `consult-widen-key' are bound dynamically." + "SPC" consult--narrow-space + "DEL" consult--narrow-delete) + +;;;; Internal API: consult--read + +(defun consult--annotate-align (cand ann) + "Align annotation ANN by computing the maximum CAND width." + (setq consult--annotate-align-width + (max consult--annotate-align-width + (* (ceiling (consult--display-width cand) + consult--annotate-align-step) + consult--annotate-align-step))) + (when ann + (concat + #(" " 0 1 (display (space :align-to (+ left consult--annotate-align-width)))) + ann))) + +(defun consult--add-history (async items) + "Add ITEMS to the minibuffer future history. +ASYNC must be non-nil for async completion functions." + (delete-dups + (append + ;; the defaults are at the beginning of the future history + (ensure-list minibuffer-default) + ;; then our custom items + (remove "" (remq nil (ensure-list items))) + ;; Add all the completions for non-async commands. For async commands this + ;; feature is not useful, since if one selects a completion candidate, the + ;; async search is restarted using that candidate string. This usually does + ;; not yield a desired result since the async input uses a special format, + ;; e.g., `#grep#filter'. + (unless async + (all-completions "" + minibuffer-completion-table + minibuffer-completion-predicate))))) + +(defun consult--setup-keymap (keymap async narrow preview-key) + "Setup minibuffer keymap. + +KEYMAP is a command-specific keymap. +ASYNC must be non-nil for async completion functions. +NARROW are the narrow settings. +PREVIEW-KEY are the preview keys." + (let ((old-map (current-local-map)) + (map (make-sparse-keymap))) + + ;; Add narrow keys + (when narrow + (consult--narrow-setup narrow map)) + + ;; Preview trigger keys + (when (and (consp preview-key) (memq :keys preview-key)) + (setq preview-key (plist-get preview-key :keys))) + (setq preview-key (mapcar #'car (consult--preview-key-normalize preview-key))) + (when preview-key + (dolist (key preview-key) + (unless (or (eq key 'any) (lookup-key old-map key)) + (define-key map key #'ignore)))) + + ;; Put the keymap together + (use-local-map + (make-composed-keymap + (delq nil (list keymap + (and async consult-async-map) + (and narrow consult-narrow-map) + map)) + old-map)))) + +(defun consult--tofu-hide-in-minibuffer (&rest _) + "Hide the tofus in the minibuffer." + (let* ((min (minibuffer-prompt-end)) + (max (point-max)) + (pos max)) + (while (and (> pos min) (consult--tofu-p (char-before pos))) + (cl-decf pos)) + (when (< pos max) + (add-text-properties pos max '(invisible t rear-nonsticky t cursor-intangible t))))) + +(defun consult--read-annotate (fun cand) + "Annotate CAND with annotation function FUN." + (pcase (funcall fun cand) + (`(,_ ,_ ,suffix) suffix) + (ann ann))) + +(defun consult--read-affixate (fun cands) + "Affixate CANDS with annotation function FUN." + (mapcar (lambda (cand) + (let ((ann (funcall fun cand))) + (if (consp ann) + ann + (setq ann (or ann "")) + (list cand "" + ;; The default completion UI adds the + ;; `completions-annotations' face if no other faces are + ;; present. + (if (text-property-not-all 0 (length ann) 'face nil ann) + ann + (propertize ann 'face 'completions-annotations)))))) + cands)) + +(cl-defun consult--read-1 (table &key + prompt predicate require-match history default + keymap category initial narrow add-history annotate + state preview-key sort lookup group inherit-input-method) + "See `consult--read' for the documentation of the arguments." + (consult--minibuffer-with-setup-hook + (:append (lambda () + (add-hook 'after-change-functions #'consult--tofu-hide-in-minibuffer nil 'local) + (consult--setup-keymap keymap (consult--async-p table) narrow preview-key) + (setq-local minibuffer-default-add-function + (apply-partially #'consult--add-history (consult--async-p table) add-history)))) + (consult--with-async (async table) + (consult--with-preview + preview-key state + (lambda (narrow input cand) + (funcall lookup cand (funcall async nil) input narrow)) + (apply-partially #'run-hook-with-args-until-success + 'consult--completion-candidate-hook) + (pcase-exhaustive history + (`(:input ,var) var) + ((pred symbolp))) + ;; Do not unnecessarily let-bind the lambdas to avoid over-capturing in + ;; the interpreter. This will make closures and the lambda string + ;; representation larger, which makes debugging much worse. Fortunately + ;; the over-capturing problem does not affect the bytecode interpreter + ;; which does a proper scope analysis. + (let* ((metadata `(metadata + ,@(when category `((category . ,category))) + ,@(when group `((group-function . ,group))) + ,@(when annotate + `((affixation-function + . ,(apply-partially #'consult--read-affixate annotate)) + (annotation-function + . ,(apply-partially #'consult--read-annotate annotate)))) + ,@(unless sort '((cycle-sort-function . identity) + (display-sort-function . identity))))) + (consult--annotate-align-width 0) + (selected + (completing-read + prompt + (lambda (str pred action) + (let ((result (complete-with-action action (funcall async nil) str pred))) + (if (eq action 'metadata) + (if (and (eq (car result) 'metadata) (cdr result)) + ;; Merge metadata + `(metadata ,@(cdr metadata) ,@(cdr result)) + metadata) + result))) + predicate require-match initial + (if (symbolp history) history (cadr history)) + default + inherit-input-method))) + ;; Repair the null completion semantics. `completing-read' may return + ;; an empty string even if REQUIRE-MATCH is non-nil. One can always + ;; opt-in to null completion by passing the empty string for DEFAULT. + (when (and (eq require-match t) (not default) (equal selected "")) + (user-error "No selection")) + selected))))) + +(cl-defun consult--read (table &rest options &key + prompt predicate require-match history default + keymap category initial narrow add-history annotate + state preview-key sort lookup group inherit-input-method) + "Enhanced completing read function to select from TABLE. + +The function is a thin wrapper around `completing-read'. Keyword +arguments are used instead of positional arguments for code +clarity. On top of `completing-read' it additionally supports +computing the candidate list asynchronously, candidate preview +and narrowing. You should use `completing-read' instead of +`consult--read' if you don't use asynchronous candidate +computation or candidate preview. + +Keyword OPTIONS: + +PROMPT is the string which is shown as prompt in the minibuffer. +PREDICATE is a filter function called for each candidate, returns +nil or t. +REQUIRE-MATCH equals t means that an exact match is required. +HISTORY is the symbol of the history variable. +DEFAULT is the default selected value. +ADD-HISTORY is a list of items to add to the history. +CATEGORY is the completion category symbol. +SORT should be set to nil if the candidates are already sorted. +This will disable sorting in the completion UI. +LOOKUP is a lookup function passed the selected candidate string, +the list of candidates, the current input string and the current +narrowing value. +ANNOTATE is a function passed a candidate string. The function +should either return an annotation string or a list of three +strings (candidate prefix postfix). +INITIAL is the initial input string. +STATE is the state function, see `consult--with-preview'. +GROUP is a completion metadata `group-function' as documented in +the Elisp manual. +PREVIEW-KEY are the preview keys. Can be nil, `any', a single +key or a list of keys. +NARROW is an alist of narrowing prefix strings and description. +KEYMAP is a command-specific keymap. +INHERIT-INPUT-METHOD, if non-nil the minibuffer inherits the +input method." + ;; supported types + (cl-assert (or (functionp table) ;; dynamic table or asynchronous function + (obarrayp table) ;; obarray + (hash-table-p table) ;; hash table + (not table) ;; empty list + (stringp (car table)) ;; string list + (and (consp (car table)) (stringp (caar table))) ;; string alist + (and (consp (car table)) (symbolp (caar table))))) ;; symbol alist + (ignore prompt predicate require-match history default + keymap category initial narrow add-history annotate + state preview-key sort lookup group inherit-input-method) + (apply #'consult--read-1 table + (append + (consult--customize-get) + options + (list :prompt "Select: " + :preview-key consult-preview-key + :sort t + :lookup (lambda (selected &rest _) selected))))) + +;;;; Internal API: consult--prompt + +(cl-defun consult--prompt-1 (&key prompt history add-history initial default + keymap state preview-key transform inherit-input-method) + "See `consult--prompt' for documentation." + (consult--minibuffer-with-setup-hook + (:append (lambda () + (consult--setup-keymap keymap nil nil preview-key) + (setq-local minibuffer-default-add-function + (apply-partially #'consult--add-history nil add-history)))) + (consult--with-preview + preview-key state + (lambda (_narrow inp _cand) (funcall transform inp)) + (lambda () "") + history + (read-from-minibuffer prompt initial nil nil history default inherit-input-method)))) + +(cl-defun consult--prompt (&rest options &key prompt history add-history initial default + keymap state preview-key transform inherit-input-method) + "Read from minibuffer. + +Keyword OPTIONS: + +PROMPT is the string to prompt with. +TRANSFORM is a function which is applied to the current input string. +HISTORY is the symbol of the history variable. +INITIAL is initial input. +DEFAULT is the default selected value. +ADD-HISTORY is a list of items to add to the history. +STATE is the state function, see `consult--with-preview'. +PREVIEW-KEY are the preview keys (nil, `any', a single key or a list of keys). +KEYMAP is a command-specific keymap." + (ignore prompt history add-history initial default + keymap state preview-key transform inherit-input-method) + (apply #'consult--prompt-1 + (append + (consult--customize-get) + options + (list :prompt "Input: " + :preview-key consult-preview-key + :transform #'identity)))) + +;;;; Internal API: consult--multi + +(defsubst consult--multi-source (sources cand) + "Lookup source for CAND in SOURCES list." + (aref sources (consult--tofu-get cand))) + +(defun consult--multi-predicate (sources cand) + "Predicate function called for each candidate CAND given SOURCES." + (let* ((src (consult--multi-source sources cand)) + (narrow (plist-get src :narrow)) + (type (or (car-safe narrow) narrow -1))) + (or (eq consult--narrow type) + (not (or consult--narrow (plist-get src :hidden)))))) + +(defun consult--multi-narrow (sources) + "Return narrow list from SOURCES." + (thread-last sources + (mapcar (lambda (src) + (when-let (narrow (plist-get src :narrow)) + (if (consp narrow) + narrow + (when-let (name (plist-get src :name)) + (cons narrow name)))))) + (delq nil) + (delete-dups))) + +(defun consult--multi-annotate (sources cand) + "Annotate candidate CAND from multi SOURCES." + (consult--annotate-align + cand + (let ((src (consult--multi-source sources cand))) + (if-let ((fun (plist-get src :annotate))) + (funcall fun (cdr (get-text-property 0 'multi-category cand))) + (plist-get src :name))))) + +(defun consult--multi-group (sources cand transform) + "Return title of candidate CAND or TRANSFORM the candidate given SOURCES." + (if transform cand + (plist-get (consult--multi-source sources cand) :name))) + +(defun consult--multi-preview-key (sources) + "Return preview keys from SOURCES." + (list :predicate + (lambda (cand) + (if (plist-member (cdr cand) :preview-key) + (plist-get (cdr cand) :preview-key) + consult-preview-key)) + :keys + (delete-dups + (seq-mapcat (lambda (src) + (let ((key (if (plist-member src :preview-key) + (plist-get src :preview-key) + consult-preview-key))) + (ensure-list key))) + sources)))) + +(defun consult--multi-lookup (sources selected candidates _input narrow &rest _) + "Lookup SELECTED in CANDIDATES given SOURCES, with potential NARROW." + (if (or (string-blank-p selected) + (not (consult--tofu-p (aref selected (1- (length selected)))))) + ;; Non-existing candidate without Tofu or default submitted (empty string) + (let* ((src (cond + (narrow (seq-find (lambda (src) + (let ((n (plist-get src :narrow))) + (eq (or (car-safe n) n -1) narrow))) + sources)) + ((seq-find (lambda (src) (plist-get src :default)) sources)) + ((seq-find (lambda (src) (not (plist-get src :hidden))) sources)) + ((aref sources 0)))) + (idx (seq-position sources src)) + (def (and (string-blank-p selected) ;; default candidate + (seq-find (lambda (cand) (eq idx (consult--tofu-get cand))) candidates)))) + (if def + (cons (cdr (get-text-property 0 'multi-category def)) src) + `(,selected :match nil ,@src))) + (if-let (found (member selected candidates)) + ;; Existing candidate submitted + (cons (cdr (get-text-property 0 'multi-category (car found))) + (consult--multi-source sources selected)) + ;; Non-existing Tofu'ed candidate submitted, e.g., via Embark + `(,(substring selected 0 -1) :match nil ,@(consult--multi-source sources selected))))) + +(defun consult--multi-candidates (sources) + "Return `consult--multi' candidates from SOURCES." + (let ((idx 0) candidates) + (seq-doseq (src sources) + (let* ((face (and (plist-member src :face) `(face ,(plist-get src :face)))) + (cat (plist-get src :category)) + (items (plist-get src :items)) + (items (if (functionp items) (funcall items) items))) + (dolist (item items) + (let* ((str (or (car-safe item) item)) + (cand (consult--tofu-append str idx))) + ;; Preserve existing `multi-category' datum of the candidate. + (if (and (eq str item) (get-text-property 0 'multi-category str)) + (when face (add-text-properties 0 (length str) face cand)) + ;; Attach `multi-category' datum and face. + (add-text-properties + 0 (length str) + `(multi-category (,cat . ,(or (cdr-safe item) item)) ,@face) cand)) + (push cand candidates)))) + (cl-incf idx)) + (nreverse candidates))) + +(defun consult--multi-enabled-sources (sources) + "Return vector of enabled SOURCES." + (vconcat + (seq-filter (lambda (src) + (if-let (pred (plist-get src :enabled)) + (funcall pred) + t)) + (mapcar (lambda (src) + (if (symbolp src) (symbol-value src) src)) + sources)))) + +(defun consult--multi-state (sources) + "State function given SOURCES." + (when-let (states (delq nil (mapcar (lambda (src) + (when-let (fun (plist-get src :state)) + (cons src (funcall fun)))) + sources))) + (let (last-fun) + (pcase-lambda (action `(,cand . ,src)) + (pcase action + ('setup + (pcase-dolist (`(,_ . ,fun) states) + (funcall fun 'setup nil))) + ('exit + (pcase-dolist (`(,_ . ,fun) states) + (funcall fun 'exit nil))) + ('preview + (let ((selected-fun (cdr (assq src states)))) + ;; If the candidate source changed during preview communicate to + ;; the last source, that none of its candidates is previewed anymore. + (when (and last-fun (not (eq last-fun selected-fun))) + (funcall last-fun 'preview nil)) + (setq last-fun selected-fun) + (when selected-fun + (funcall selected-fun 'preview cand)))) + ('return + (let ((selected-fun (cdr (assq src states)))) + ;; Finish all the sources, except the selected one. + (pcase-dolist (`(,_ . ,fun) states) + (unless (eq fun selected-fun) + (funcall fun 'return nil))) + ;; Finish the source with the selected candidate + (when selected-fun + (funcall selected-fun 'return cand))))))))) + +(defun consult--multi (sources &rest options) + "Select from candidates taken from a list of SOURCES. + +OPTIONS is the plist of options passed to `consult--read'. The following +options are supported: :require-match, :history, :keymap, :initial, +:add-history, :sort and :inherit-input-method. The other options of +`consult--read' are used by the implementation of `consult--multi' and +should not be overwritten, except in in special scenarios. + +The function returns the selected candidate in the form (cons candidate +source-plist). The plist has the key :match with a value nil if the +candidate does not exist, t if the candidate exists and `new' if the +candidate has been created. The sources of the source list can either be +symbols of source variables or source values. Source values must be +plists with fields from the following list. + +Required source fields: +* :category - Completion category symbol. +* :items - List of strings to select from or function returning + list of strings. Note that the strings can use text properties + to carry metadata, which is then available to the :annotate, + :action and :state functions. + +Optional source fields: +* :name - Name of the source as a string, used for narrowing, + group titles and annotations. +* :narrow - Narrowing character or (character . string) pair. +* :enabled - Function which must return t if the source is enabled. +* :hidden - When t candidates of this source are hidden by default. +* :face - Face used for highlighting the candidates. +* :annotate - Annotation function called for each candidate, returns string. +* :history - Name of history variable to add selected candidate. +* :default - Must be t if the first item of the source is the default value. +* :action - Function called with the selected candidate. +* :new - Function called with new candidate name, only if :require-match is nil. +* :state - State constructor for the source, must return the + state function. The state function is informed about state + changes of the UI and can be used to implement preview. +* Other custom source fields can be added depending on the use + case. Note that the source is returned by `consult--multi' + together with the selected candidate." + (let* ((sources (consult--multi-enabled-sources sources)) + (candidates (consult--with-increased-gc + (consult--multi-candidates sources))) + (selected + (apply #'consult--read + candidates + (append + options + (list + :category 'multi-category + :predicate (apply-partially #'consult--multi-predicate sources) + :annotate (apply-partially #'consult--multi-annotate sources) + :group (apply-partially #'consult--multi-group sources) + :lookup (apply-partially #'consult--multi-lookup sources) + :preview-key (consult--multi-preview-key sources) + :narrow (consult--multi-narrow sources) + :state (consult--multi-state sources)))))) + (when-let (history (plist-get (cdr selected) :history)) + (add-to-history history (car selected))) + (if (plist-member (cdr selected) :match) + (when-let (fun (plist-get (cdr selected) :new)) + (funcall fun (car selected)) + (plist-put (cdr selected) :match 'new)) + (when-let (fun (plist-get (cdr selected) :action)) + (funcall fun (car selected))) + (setq selected `(,(car selected) :match t ,@(cdr selected)))) + selected)) + +;;;; Customization macro + +(defun consult--customize-put (cmds prop form) + "Set property PROP to FORM of commands CMDS." + (dolist (cmd cmds) + (cond + ((and (boundp cmd) (consp (symbol-value cmd))) + (setf (plist-get (symbol-value cmd) prop) (eval form 'lexical))) + ((functionp cmd) + (setf (plist-get (alist-get cmd consult--customize-alist) prop) form)) + (t (user-error "%s is neither a Command command nor a source" cmd)))) + nil) + +(defmacro consult-customize (&rest args) + "Set properties of commands or sources. +ARGS is a list of commands or sources followed by the list of +keyword-value pairs. For `consult-customize' to succeed, the +customized sources and commands must exist. When a command is +invoked, the value of `this-command' is used to lookup the +corresponding customization options." + (let (setter) + (while args + (let ((cmds (seq-take-while (lambda (x) (not (keywordp x))) args))) + (setq args (seq-drop-while (lambda (x) (not (keywordp x))) args)) + (while (keywordp (car args)) + (push `(consult--customize-put ',cmds ,(car args) ',(cadr args)) setter) + (setq args (cddr args))))) + (macroexp-progn setter))) + +(defun consult--customize-get (&optional cmd) + "Get configuration from `consult--customize-alist' for CMD." + (mapcar (lambda (x) (eval x 'lexical)) + (alist-get (or cmd this-command) consult--customize-alist))) + +;;;; Commands + +;;;;; Command: consult-completion-in-region + +(defun consult--insertion-preview (start end) + "State function for previewing a candidate in a specific region. +The candidates are previewed in the region from START to END. This function is +used as the `:state' argument for `consult--read' in the `consult-yank' family +of functions and in `consult-completion-in-region'." + (unless (or (minibufferp) + ;; Disable preview if anything odd is going on with the markers. + ;; Otherwise we get "Marker points into wrong buffer errors". See + ;; gh:minad/consult#375, where Org mode source blocks are + ;; completed in a different buffer than the original buffer. This + ;; completion is probably also problematic in my Corfu completion + ;; package. + (not (eq (window-buffer) (current-buffer))) + (and (markerp start) (not (eq (marker-buffer start) (current-buffer)))) + (and (markerp end) (not (eq (marker-buffer end) (current-buffer))))) + (let (ov) + (lambda (action cand) + (cond + ((and (not cand) ov) + (delete-overlay ov) + (setq ov nil)) + ((and (eq action 'preview) cand) + (unless ov + (setq ov (consult--make-overlay start end + 'invisible t + 'window (selected-window)))) + ;; Use `add-face-text-property' on a copy of "cand in order to merge face properties + (setq cand (copy-sequence cand)) + (add-face-text-property 0 (length cand) 'consult-preview-insertion t cand) + ;; Use the `before-string' property since the overlay might be empty. + (overlay-put ov 'before-string cand))))))) + +;;;###autoload +(defun consult-completion-in-region (start end collection &optional predicate) + "Use minibuffer completion as the UI for `completion-at-point'. + +The function is called with 4 arguments: START END COLLECTION +PREDICATE. The arguments and expected return value are as +specified for `completion-in-region'. Use this function as a +value for `completion-in-region-function'." + (barf-if-buffer-read-only) + (let* ((initial (buffer-substring-no-properties start end)) + (metadata (completion-metadata initial collection predicate)) + ;; TODO: `minibuffer-completing-file-name' is mostly deprecated, but + ;; still in use. Packages should instead use the completion metadata. + (minibuffer-completing-file-name + (eq 'file (completion-metadata-get metadata 'category))) + (threshold (completion--cycle-threshold metadata)) + (all (completion-all-completions initial collection predicate (length initial))) + ;; Wrap all annotation functions to ensure that they are executed + ;; in the original buffer. + (exit-fun (plist-get completion-extra-properties :exit-function)) + (ann-fun (plist-get completion-extra-properties :annotation-function)) + (aff-fun (plist-get completion-extra-properties :affixation-function)) + (docsig-fun (plist-get completion-extra-properties :company-docsig)) + (completion-extra-properties + `(,@(and ann-fun (list :annotation-function (consult--in-buffer ann-fun))) + ,@(and aff-fun (list :affixation-function (consult--in-buffer aff-fun))) + ;; Provide `:annotation-function' if `:company-docsig' is specified. + ,@(and docsig-fun (not ann-fun) (not aff-fun) + (list :annotation-function + (consult--in-buffer + (lambda (cand) + (concat (propertize " " 'display '(space :align-to center)) + (funcall docsig-fun cand))))))))) + ;; error if `threshold' is t or the improper list `all' is too short + (if (and threshold + (or (not (consp (ignore-errors (nthcdr threshold all)))) + (and completion-cycling completion-all-sorted-completions))) + (completion--in-region start end collection predicate) + (let* ((limit (car (completion-boundaries initial collection predicate ""))) + (this-command #'consult-completion-in-region) + (completion + (cond + ((atom all) nil) + ((and (consp all) (atom (cdr all))) + (concat (substring initial 0 limit) (car all))) + (t + (consult--local-let ((enable-recursive-minibuffers t)) + ;; Evaluate completion table in the original buffer. + ;; This is a reasonable thing to do and required by + ;; some completion tables in particular by lsp-mode. + ;; See gh:minad/vertico#61. + (consult--read (consult--completion-table-in-buffer collection) + :prompt "Completion: " + :state (consult--insertion-preview start end) + :predicate predicate + :initial initial)))))) + (if completion + (progn + ;; bug#55205: completion--replace removes properties! + (completion--replace start end (setq completion (concat completion))) + (when exit-fun + (funcall exit-fun completion + ;; If completion is finished and cannot be further + ;; completed, return `finished'. Otherwise return + ;; `exact'. + (if (eq (try-completion completion collection predicate) t) + 'finished 'exact))) + t) + (message "No completion") + nil))))) + +;;;;; Command: consult-outline + +(defun consult--outline-candidates () + "Return alist of outline headings and positions." + (consult--forbid-minibuffer) + (let* ((line (line-number-at-pos (point-min) consult-line-numbers-widen)) + (heading-regexp (concat "^\\(?:" + ;; default definition from outline.el + (or (bound-and-true-p outline-regexp) "[*\^L]+") + "\\)")) + (heading-alist (bound-and-true-p outline-heading-alist)) + (level-fun (or (bound-and-true-p outline-level) + (lambda () ;; as in the default from outline.el + (or (cdr (assoc (match-string 0) heading-alist)) + (- (match-end 0) (match-beginning 0)))))) + (buffer (current-buffer)) + candidates) + (save-excursion + (goto-char (point-min)) + (while (save-excursion + (if-let (fun (bound-and-true-p outline-search-function)) + (funcall fun) + (re-search-forward heading-regexp nil t))) + (cl-incf line (consult--count-lines (match-beginning 0))) + (push (consult--location-candidate + (consult--buffer-substring (pos-bol) (pos-eol) 'fontify) + (cons buffer (point)) (1- line) (1- line) + 'consult--outline-level (funcall level-fun)) + candidates) + (goto-char (1+ (pos-eol))))) + (unless candidates + (user-error "No headings")) + (nreverse candidates))) + +;;;###autoload +(defun consult-outline (&optional level) + "Jump to an outline heading, obtained by matching against `outline-regexp'. + +This command supports narrowing to a heading level and candidate +preview. The initial narrowing LEVEL can be given as prefix +argument. The symbol at point is added to the future history." + (interactive + (list (and current-prefix-arg (prefix-numeric-value current-prefix-arg)))) + (let* ((candidates (consult--slow-operation + "Collecting headings..." + (consult--outline-candidates))) + (min-level (- (cl-loop for cand in candidates minimize + (get-text-property 0 'consult--outline-level cand)) + ?1)) + (narrow-pred (lambda (cand) + (<= (get-text-property 0 'consult--outline-level cand) + (+ consult--narrow min-level)))) + (narrow-keys (mapcar (lambda (c) (cons c (format "Level %c" c))) + (number-sequence ?1 ?9))) + (narrow-init (and level (max ?1 (min ?9 (+ level ?0)))))) + (consult--read + candidates + :prompt "Go to heading: " + :annotate (consult--line-prefix) + :category 'consult-location + :sort nil + :require-match t + :lookup #'consult--line-match + :narrow `(:predicate ,narrow-pred :keys ,narrow-keys :initial ,narrow-init) + :history '(:input consult--line-history) + :add-history (thing-at-point 'symbol) + :state (consult--location-state candidates)))) + +;;;;; Command: consult-mark + +(defun consult--mark-candidates (markers) + "Return list of candidates strings for MARKERS." + (consult--forbid-minibuffer) + (let ((candidates) + (current-buf (current-buffer))) + (save-excursion + (dolist (marker markers) + (when-let ((pos (marker-position marker)) + (buf (marker-buffer marker))) + (when (and (eq buf current-buf) + (consult--in-range-p pos)) + (goto-char pos) + ;; `line-number-at-pos' is a very slow function, which should be + ;; replaced everywhere. However in this case the slow + ;; line-number-at-pos does not hurt much, since the mark ring is + ;; usually small since it is limited by `mark-ring-max'. + (push (consult--location-candidate + (consult--line-with-mark marker) marker + (line-number-at-pos pos consult-line-numbers-widen) + marker) + candidates))))) + (unless candidates + (user-error "No marks")) + (nreverse (delete-dups candidates)))) + +;;;###autoload +(defun consult-mark (&optional markers) + "Jump to a marker in MARKERS list (defaults to buffer-local `mark-ring'). + +The command supports preview of the currently selected marker position. +The symbol at point is added to the future history." + (interactive) + (consult--read + (consult--mark-candidates + (or markers (cons (mark-marker) mark-ring))) + :prompt "Go to mark: " + :annotate (consult--line-prefix) + :category 'consult-location + :sort nil + :require-match t + :lookup #'consult--lookup-location + :history '(:input consult--line-history) + :add-history (thing-at-point 'symbol) + :state (consult--jump-state))) + +;;;;; Command: consult-global-mark + +(defun consult--global-mark-candidates (markers) + "Return list of candidates strings for MARKERS." + (consult--forbid-minibuffer) + (let ((candidates)) + (save-excursion + (dolist (marker markers) + (when-let ((pos (marker-position marker)) + (buf (marker-buffer marker))) + (unless (minibufferp buf) + (with-current-buffer buf + (when (consult--in-range-p pos) + (goto-char pos) + ;; `line-number-at-pos' is slow, see comment in `consult--mark-candidates'. + (let* ((line (line-number-at-pos pos consult-line-numbers-widen)) + (prefix (consult--format-file-line-match (buffer-name buf) line "")) + (cand (concat prefix (consult--line-with-mark marker) (consult--tofu-encode marker)))) + (put-text-property 0 (length prefix) 'consult-strip t cand) + (put-text-property 0 (length cand) 'consult-location (cons marker line) cand) + (push cand candidates)))))))) + (unless candidates + (user-error "No global marks")) + (nreverse (delete-dups candidates)))) + +;;;###autoload +(defun consult-global-mark (&optional markers) + "Jump to a marker in MARKERS list (defaults to `global-mark-ring'). + +The command supports preview of the currently selected marker position. +The symbol at point is added to the future history." + (interactive) + (consult--read + (consult--global-mark-candidates + (or markers global-mark-ring)) + :prompt "Go to global mark: " + ;; Despite `consult-global-mark' formatting the candidates in grep-like + ;; style, we are not using the `consult-grep' category, since the candidates + ;; have location markers attached. + :category 'consult-location + :sort nil + :require-match t + :lookup #'consult--lookup-location + :history '(:input consult--line-history) + :add-history (thing-at-point 'symbol) + :state (consult--jump-state))) + +;;;;; Command: consult-line + +(defun consult--line-candidates (top curr-line) + "Return list of line candidates. +Start from top if TOP non-nil. +CURR-LINE is the current line number." + (consult--forbid-minibuffer) + (consult--fontify-all) + (let* ((buffer (current-buffer)) + (line (line-number-at-pos (point-min) consult-line-numbers-widen)) + default-cand candidates) + (consult--each-line beg end + (unless (looking-at-p "^\\s-*$") + (push (consult--location-candidate + (consult--buffer-substring beg end) + (cons buffer beg) line line) + candidates) + (when (and (not default-cand) (>= line curr-line)) + (setq default-cand candidates))) + (cl-incf line)) + (unless candidates + (user-error "No lines")) + (nreverse + (if (or top (not default-cand)) + candidates + (let ((before (cdr default-cand))) + (setcdr default-cand nil) + (nconc before candidates)))))) + +(defun consult--line-point-placement (selected candidates highlighted &rest ignored-faces) + "Find point position on matching line. +SELECTED is the currently selected candidate. +CANDIDATES is the list of candidates. +HIGHLIGHTED is the highlighted string to determine the match position. +IGNORED-FACES are ignored when determining the match position." + (when-let (pos (consult--lookup-location selected candidates)) + (if highlighted + (let* ((matches (apply #'consult--point-placement highlighted 0 ignored-faces)) + (dest (+ pos (car matches)))) + ;; Only create a new marker when jumping across buffers (for example + ;; `consult-line-multi'). Avoid creating unnecessary markers, when + ;; scrolling through candidates, since creating markers is not free. + (when (and (markerp pos) (not (eq (marker-buffer pos) (current-buffer)))) + (setq dest (move-marker (make-marker) dest (marker-buffer pos)))) + (cons dest (cdr matches))) + pos))) + +(defun consult--line-match (selected candidates input &rest _) + "Lookup position of match. +SELECTED is the currently selected candidate. +CANDIDATES is the list of candidates. +INPUT is the input string entered by the user." + (consult--line-point-placement selected candidates + (and (not (string-blank-p input)) + (car (consult--completion-filter + input + (list (substring-no-properties selected)) + 'consult-location 'highlight))) + 'completions-first-difference)) + +;;;###autoload +(defun consult-line (&optional initial start) + "Search for a matching line. + +Depending on the setting `consult-point-placement' the command +jumps to the beginning or the end of the first match on the line +or the line beginning. The default candidate is the non-empty +line next to point. This command obeys narrowing. Optional +INITIAL input can be provided. The search starting point is +changed if the START prefix argument is set. The symbol at point +and the last `isearch-string' is added to the future history." + (interactive (list nil (not (not current-prefix-arg)))) + (let* ((curr-line (line-number-at-pos (point) consult-line-numbers-widen)) + (top (not (eq start consult-line-start-from-top))) + (candidates (consult--slow-operation "Collecting lines..." + (consult--line-candidates top curr-line)))) + (consult--read + candidates + :prompt (if top "Go to line from top: " "Go to line: ") + :annotate (consult--line-prefix curr-line) + :category 'consult-location + :sort nil + :require-match t + ;; Always add last `isearch-string' to future history + :add-history (list (thing-at-point 'symbol) isearch-string) + :history '(:input consult--line-history) + :lookup #'consult--line-match + :default (car candidates) + ;; Add `isearch-string' as initial input if starting from Isearch + :initial (or initial + (and isearch-mode + (prog1 isearch-string (isearch-done)))) + :state (consult--location-state candidates)))) + +;;;;; Command: consult-line-multi + +(defun consult--line-multi-match (selected candidates &rest _) + "Lookup position of match. +SELECTED is the currently selected candidate. +CANDIDATES is the list of candidates." + (consult--line-point-placement selected candidates + (car (member selected candidates)))) + +(defun consult--line-multi-group (cand transform) + "Group function used by `consult-line-multi'. +If TRANSFORM non-nil, return transformed CAND, otherwise return title." + (if transform cand + (let* ((marker (car (get-text-property 0 'consult-location cand))) + (buf (if (consp marker) + (car marker) ;; Handle cheap marker + (marker-buffer marker)))) + (if buf (buffer-name buf) "Dead buffer")))) + +(defun consult--line-multi-candidates (buffers input) + "Collect matching candidates from multiple buffers. +INPUT is the user input which should be matched. +BUFFERS is the list of buffers." + (pcase-let ((`(,regexps . ,hl) + (funcall consult--regexp-compiler + input 'emacs completion-ignore-case)) + (candidates nil) + (cand-idx 0)) + (save-match-data + (dolist (buf buffers (nreverse candidates)) + (with-current-buffer buf + (save-excursion + (let ((line (line-number-at-pos (point-min) consult-line-numbers-widen))) + (goto-char (point-min)) + (while (and (not (eobp)) + (save-excursion (re-search-forward (car regexps) nil t))) + (cl-incf line (consult--count-lines (match-beginning 0))) + (let ((bol (pos-bol)) + (eol (pos-eol))) + (goto-char bol) + (when (and (not (looking-at-p "^\\s-*$")) + (seq-every-p (lambda (r) + (goto-char bol) + (re-search-forward r eol t)) + (cdr regexps))) + (push (consult--location-candidate + (funcall hl (buffer-substring-no-properties bol eol)) + (cons buf bol) (1- line) cand-idx) + candidates) + (cl-incf cand-idx)) + (goto-char (1+ eol))))))))))) + +;;;###autoload +(defun consult-line-multi (query &optional initial) + "Search for a matching line in multiple buffers. + +By default search across all project buffers. If the prefix +argument QUERY is non-nil, all buffers are searched. Optional +INITIAL input can be provided. The symbol at point and the last +`isearch-string' is added to the future history. In order to +search a subset of buffers, QUERY can be set to a plist according +to `consult--buffer-query'." + (interactive "P") + (unless (keywordp (car-safe query)) + (setq query (list :sort 'alpha-current :directory (and (not query) 'project)))) + (pcase-let* ((`(,prompt . ,buffers) (consult--buffer-query-prompt "Go to line" query)) + (collection (consult--dynamic-collection + (apply-partially #'consult--line-multi-candidates + buffers)))) + (consult--read + collection + :prompt prompt + :annotate (consult--line-prefix) + :category 'consult-location + :sort nil + :require-match t + ;; Always add last Isearch string to future history + :add-history (mapcar #'consult--async-split-initial + (delq nil (list (thing-at-point 'symbol) + isearch-string))) + :history '(:input consult--line-multi-history) + :lookup #'consult--line-multi-match + ;; Add `isearch-string' as initial input if starting from Isearch + :initial (consult--async-split-initial + (or initial + (and isearch-mode + (prog1 isearch-string (isearch-done))))) + :state (consult--location-state (lambda () (funcall collection nil))) + :group #'consult--line-multi-group))) + +;;;;; Command: consult-keep-lines + +(defun consult--keep-lines-state (filter) + "State function for `consult-keep-lines' with FILTER function." + (let ((font-lock-orig font-lock-mode) + (whitespace-orig (bound-and-true-p whitespace-mode)) + (hl-line-orig (bound-and-true-p hl-line-mode)) + (point-orig (point)) + lines content-orig replace last-input) + (if (use-region-p) + (save-restriction + ;; Use the same behavior as `keep-lines'. + (let ((rbeg (region-beginning)) + (rend (save-excursion + (goto-char (region-end)) + (unless (or (bolp) (eobp)) + (forward-line 0)) + (point)))) + (consult--fontify-region rbeg rend) + (narrow-to-region rbeg rend) + (consult--each-line beg end + (push (consult--buffer-substring beg end) lines)) + (setq content-orig (buffer-string) + replace (lambda (content &optional pos) + (delete-region rbeg rend) + (insert-before-markers content) + (goto-char (or pos rbeg)) + (setq rend (+ rbeg (length content))) + (add-face-text-property rbeg rend 'region t))))) + (consult--fontify-all) + (setq content-orig (buffer-string) + replace (lambda (content &optional pos) + (delete-region (point-min) (point-max)) + (insert content) + (goto-char (or pos (point-min))))) + (consult--each-line beg end + (push (consult--buffer-substring beg end) lines))) + (setq lines (nreverse lines)) + (lambda (action input) + ;; Restoring content and point position + (when (and (eq action 'return) last-input) + ;; No undo recording, modification hooks, buffer modified-status + (with-silent-modifications (funcall replace content-orig point-orig))) + ;; Committing or new input provided -> Update + (when (and input ;; Input has been provided + (or + ;; Committing, but not with empty input + (and (eq action 'return) (not (string-match-p "\\`!? ?\\'" input))) + ;; Input has changed + (not (equal input last-input)))) + (let ((filtered-content + (if (string-match-p "\\`!? ?\\'" input) + ;; Special case the empty input for performance. + ;; Otherwise it could happen that the minibuffer is empty, + ;; but the buffer has not been updated. + content-orig + (if (eq action 'return) + (apply #'concat (mapcan (lambda (x) (list x "\n")) + (funcall filter input lines))) + (while-no-input + ;; Heavy computation is interruptible if *not* committing! + ;; Allocate new string candidates since the matching function mutates! + (apply #'concat (mapcan (lambda (x) (list x "\n")) + (funcall filter input (mapcar #'copy-sequence lines))))))))) + (when (stringp filtered-content) + (when font-lock-mode (font-lock-mode -1)) + (when (bound-and-true-p whitespace-mode) (whitespace-mode -1)) + (when (bound-and-true-p hl-line-mode) (hl-line-mode -1)) + (if (eq action 'return) + (atomic-change-group + ;; Disable modification hooks for performance + (let ((inhibit-modification-hooks t)) + (funcall replace filtered-content))) + ;; No undo recording, modification hooks, buffer modified-status + (with-silent-modifications + (funcall replace filtered-content) + (setq last-input input)))))) + ;; Restore modes + (when (eq action 'return) + (when hl-line-orig (hl-line-mode 1)) + (when whitespace-orig (whitespace-mode 1)) + (when font-lock-orig (font-lock-mode 1)))))) + +;;;###autoload +(defun consult-keep-lines (filter &optional initial) + "Select a subset of the lines in the current buffer with live preview. + +The selected lines are kept and the other lines are deleted. When called +interactively, the lines selected are those that match the minibuffer input. In +order to match the inverse of the input, prefix the input with `! '. When +called from Elisp, the filtering is performed by a FILTER function. This +command obeys narrowing. + +FILTER is the filter function. +INITIAL is the initial input." + (interactive + (list (lambda (pattern cands) + ;; Use consult-location completion category when filtering lines + (consult--completion-filter-dispatch + pattern cands 'consult-location 'highlight)))) + (consult--forbid-minibuffer) + (let ((ro buffer-read-only)) + (unwind-protect + (consult--minibuffer-with-setup-hook + (lambda () + (when ro + (minibuffer-message + (substitute-command-keys + " [Unlocked read-only buffer. \\[minibuffer-keyboard-quit] to quit.]")))) + (setq buffer-read-only nil) + (consult--with-increased-gc + (consult--prompt + :prompt "Keep lines: " + :initial initial + :history 'consult--line-history + :state (consult--keep-lines-state filter)))) + (setq buffer-read-only ro)))) + +;;;;; Command: consult-focus-lines + +(defun consult--focus-lines-state (filter) + "State function for `consult-focus-lines' with FILTER function." + (let (lines overlays last-input pt-orig pt-min pt-max) + (save-excursion + (save-restriction + (if (not (use-region-p)) + (consult--fontify-all) + (consult--fontify-region (region-beginning) (region-end)) + (narrow-to-region + (region-beginning) + ;; Behave the same as `keep-lines'. + ;; Move to the next line. + (save-excursion + (goto-char (region-end)) + (unless (or (bolp) (eobp)) + (forward-line 0)) + (point)))) + (setq pt-orig (point) pt-min (point-min) pt-max (point-max)) + (let ((i 0)) + (consult--each-line beg end + ;; Use "\n" for empty lines, since we need a non-empty string to + ;; attach the text property to. + (let ((line (if (eq beg end) (char-to-string ?\n) + (buffer-substring-no-properties beg end)))) + (put-text-property 0 1 'consult--focus-line (cons (cl-incf i) beg) line) + (push line lines))) + (setq lines (nreverse lines))))) + (lambda (action input) + ;; New input provided -> Update + (when (and input (not (equal input last-input))) + (let (new-overlays) + (pcase (while-no-input + (unless (string-match-p "\\`!? ?\\'" input) ;; Empty input. + (let* ((inhibit-quit (eq action 'return)) ;; Non interruptible, when quitting! + (not (string-prefix-p "! " input)) + (stripped (string-remove-prefix "! " input)) + (matches (funcall filter stripped lines)) + (old-ind 0) + (block-beg pt-min) + (block-end pt-min)) + (while old-ind + (let ((match (pop matches)) (ind nil) (beg pt-max) (end pt-max) prop) + (when match + (setq prop (get-text-property 0 'consult--focus-line match) + ind (car prop) + beg (cdr prop) + ;; Check for empty lines, see above. + end (+ 1 beg (if (equal match "\n") 0 (length match))))) + (unless (eq ind (1+ old-ind)) + (let ((a (if not block-beg block-end)) + (b (if not block-end beg))) + (when (/= a b) + (push (consult--make-overlay a b 'invisible t) new-overlays))) + (setq block-beg beg)) + (setq block-end end old-ind ind))))) + 'commit) + ('commit + (mapc #'delete-overlay overlays) + (setq last-input input overlays new-overlays)) + (_ (mapc #'delete-overlay new-overlays))))) + (when (eq action 'return) + (cond + ((not input) + (mapc #'delete-overlay overlays) + (goto-char pt-orig)) + ((equal input "") + (consult-focus-lines nil 'show) + (goto-char pt-orig)) + (t + ;; Successfully terminated -> Remember invisible overlays + (setq consult--focus-lines-overlays + (nconc consult--focus-lines-overlays overlays)) + ;; move point past invisible + (goto-char (if-let (ov (and (invisible-p pt-orig) + (seq-find (lambda (ov) (overlay-get ov 'invisible)) + (overlays-at pt-orig)))) + (overlay-end ov) + pt-orig)))))))) + +;;;###autoload +(defun consult-focus-lines (filter &optional show initial) + "Hide or show lines using overlays. + +The selected lines are shown and the other lines hidden. When called +interactively, the lines selected are those that match the minibuffer input. In +order to match the inverse of the input, prefix the input with `! '. With +optional prefix argument SHOW reveal the hidden lines. Alternatively the +command can be restarted to reveal the lines. When called from Elisp, the +filtering is performed by a FILTER function. This command obeys narrowing. + +FILTER is the filter function. +INITIAL is the initial input." + (interactive + (list (lambda (pattern cands) + ;; Use consult-location completion category when filtering lines + (consult--completion-filter-dispatch + pattern cands 'consult-location nil)) + current-prefix-arg)) + (if show + (progn + (mapc #'delete-overlay consult--focus-lines-overlays) + (setq consult--focus-lines-overlays nil) + (message "All lines revealed")) + (consult--forbid-minibuffer) + (consult--with-increased-gc + (consult--prompt + :prompt + (if consult--focus-lines-overlays + "Focus on lines (RET to reveal): " + "Focus on lines: ") + :initial initial + :history 'consult--line-history + :state (consult--focus-lines-state filter))))) + +;;;;; Command: consult-goto-line + +(defun consult--goto-line-position (str msg) + "Transform input STR to line number. +Print an error message with MSG function." + (save-match-data + (if (and str (string-match "\\`\\([[:digit:]]+\\):?\\([[:digit:]]*\\)\\'" str)) + (let ((line (string-to-number (match-string 1 str))) + (col (string-to-number (match-string 2 str)))) + (save-excursion + (save-restriction + (when consult-line-numbers-widen + (widen)) + (goto-char (point-min)) + (forward-line (1- line)) + (goto-char (min (+ (point) col) (pos-eol))) + (point)))) + (when (and str (not (equal str ""))) + (funcall msg "Please enter a number.")) + nil))) + +;;;###autoload +(defun consult-goto-line (&optional arg) + "Read line number and jump to the line with preview. + +Enter either a line number to jump to the first column of the +given line or line:column in order to jump to a specific column. +Jump directly if a line number is given as prefix ARG. The +command respects narrowing and the settings +`consult-goto-line-numbers' and `consult-line-numbers-widen'." + (interactive "P") + (if arg + (call-interactively #'goto-line) + (consult--forbid-minibuffer) + (consult--local-let ((display-line-numbers consult-goto-line-numbers) + (display-line-numbers-widen consult-line-numbers-widen)) + (while (if-let (pos (consult--goto-line-position + (consult--prompt + :prompt "Go to line: " + :history 'goto-line-history + :state + (let ((preview (consult--jump-preview))) + (lambda (action str) + (funcall preview action + (consult--goto-line-position str #'ignore))))) + #'minibuffer-message)) + (consult--jump pos) + t))))) + +;;;;; Command: consult-recent-file + +(defun consult--file-preview () + "Create preview function for files." + (let ((open (consult--temporary-files)) + (preview (consult--buffer-preview))) + (lambda (action cand) + (unless cand + (funcall open)) + (funcall preview action + (and cand + (eq action 'preview) + (funcall open cand)))))) + +(defun consult--file-action (file) + "Open FILE via `consult--buffer-action'." + (consult--buffer-action (find-file-noselect file))) + +(consult--define-state file) + +;;;###autoload +(defun consult-recent-file () + "Find recent file using `completing-read'." + (interactive) + (find-file + (consult--read + (or + (mapcar #'consult--fast-abbreviate-file-name (bound-and-true-p recentf-list)) + (user-error "No recent files, `recentf-mode' is %s" + (if recentf-mode "enabled" "disabled"))) + :prompt "Find recent file: " + :sort nil + :require-match t + :category 'file + :state (consult--file-preview) + :history 'file-name-history))) + +;;;;; Command: consult-mode-command + +(defun consult--mode-name (mode) + "Return name part of MODE." + (replace-regexp-in-string + "global-\\(.*\\)-mode" "\\1" + (replace-regexp-in-string + "\\(-global\\)?-mode\\'" "" + (if (eq mode 'c-mode) + "cc" + (symbol-name mode)) + 'fixedcase) + 'fixedcase)) + +(defun consult--mode-command-candidates (modes) + "Extract commands from MODES. + +The list of features is searched for files belonging to the modes. +From these files, the commands are extracted." + (let* ((case-fold-search) + (buffer (current-buffer)) + (command-filter (consult--regexp-filter (seq-filter #'stringp consult-mode-command-filter))) + (feature-filter (seq-filter #'symbolp consult-mode-command-filter)) + (minor-hash (consult--string-hash minor-mode-list)) + (minor-local-modes (seq-filter (lambda (m) + (and (gethash m minor-hash) + (local-variable-if-set-p m))) + modes)) + (minor-global-modes (seq-filter (lambda (m) + (and (gethash m minor-hash) + (not (local-variable-if-set-p m)))) + modes)) + (major-modes (seq-remove (lambda (m) + (gethash m minor-hash)) + modes)) + (major-paths-hash (consult--string-hash (mapcar #'symbol-file major-modes))) + (minor-local-paths-hash (consult--string-hash (mapcar #'symbol-file minor-local-modes))) + (minor-global-paths-hash (consult--string-hash (mapcar #'symbol-file minor-global-modes))) + (major-name-regexp (regexp-opt (mapcar #'consult--mode-name major-modes))) + (minor-local-name-regexp (regexp-opt (mapcar #'consult--mode-name minor-local-modes))) + (minor-global-name-regexp (regexp-opt (mapcar #'consult--mode-name minor-global-modes))) + (commands)) + (dolist (feature load-history commands) + (when-let (name (alist-get 'provide feature)) + (let* ((path (car feature)) + (file (file-name-nondirectory path)) + (key (cond + ((memq name feature-filter) nil) + ((or (gethash path major-paths-hash) + (string-match-p major-name-regexp file)) + ?m) + ((or (gethash path minor-local-paths-hash) + (string-match-p minor-local-name-regexp file)) + ?l) + ((or (gethash path minor-global-paths-hash) + (string-match-p minor-global-name-regexp file)) + ?g)))) + (when key + (dolist (cmd (cdr feature)) + (let ((sym (cdr-safe cmd))) + (when (and (consp cmd) + (eq (car cmd) 'defun) + (commandp sym) + (not (get sym 'byte-obsolete-info)) + ;; Emacs 28 has a `read-extended-command-predicate' + (if (bound-and-true-p read-extended-command-predicate) + (funcall read-extended-command-predicate sym buffer) + t)) + (let ((name (symbol-name sym))) + (unless (string-match-p command-filter name) + (push (propertize name + 'consult--candidate sym + 'consult--type key) + commands)))))))))))) + +;;;###autoload +(defun consult-mode-command (&rest modes) + "Run a command from any of the given MODES. + +If no MODES are specified, use currently active major and minor modes." + (interactive) + (unless modes + (setq modes (cons major-mode + (seq-filter (lambda (m) + (and (boundp m) (symbol-value m))) + minor-mode-list)))) + (let ((narrow `((?m . ,(format "Major: %s" major-mode)) + (?l . "Local Minor") + (?g . "Global Minor")))) + (command-execute + (consult--read + (consult--mode-command-candidates modes) + :prompt "Mode command: " + :predicate + (lambda (cand) + (let ((key (get-text-property 0 'consult--type cand))) + (if consult--narrow + (= key consult--narrow) + (/= key ?g)))) + :lookup #'consult--lookup-candidate + :group (consult--type-group narrow) + :narrow narrow + :require-match t + :history 'extended-command-history + :category 'command)))) + +;;;;; Command: consult-yank + +(defun consult--read-from-kill-ring () + "Open kill ring menu and return selected string." + ;; `current-kill' updates `kill-ring' with interprogram paste, see + ;; gh:minad/consult#443. + (current-kill 0) + ;; Do not specify a :lookup function in order to preserve completion-styles + ;; highlighting of the current candidate. We have to perform a final lookup to + ;; obtain the original candidate which may be propertized with yank-specific + ;; properties, like 'yank-handler. + (consult--lookup-member + (consult--read + (consult--remove-dups + (or (if consult-yank-rotate + (append kill-ring-yank-pointer + (butlast kill-ring (length kill-ring-yank-pointer))) + kill-ring) + (user-error "Kill ring is empty"))) + :prompt "Yank from kill-ring: " + :history t ;; disable history + :sort nil + :category 'kill-ring + :require-match t + :state + (consult--insertion-preview + (point) + ;; If previous command is yank, hide previously yanked string + (or (and (eq last-command 'yank) (mark t)) (point)))) + kill-ring)) + +;; Adapted from the Emacs `yank-from-kill-ring' function. +;;;###autoload +(defun consult-yank-from-kill-ring (string &optional arg) + "Select STRING from the kill ring and insert it. +With prefix ARG, put point at beginning, and mark at end, like `yank' does. + +This command behaves like `yank-from-kill-ring' in Emacs 28, which also offers +a `completing-read' interface to the `kill-ring'. Additionally the Consult +version supports preview of the selected string." + (interactive (list (consult--read-from-kill-ring) current-prefix-arg)) + (when string + (setq yank-window-start (window-start)) + (push-mark) + (insert-for-yank string) + (setq this-command 'yank) + (when consult-yank-rotate + (if-let (pos (seq-position kill-ring string)) + (setq kill-ring-yank-pointer (nthcdr pos kill-ring)) + (kill-new string))) + (when (consp arg) + ;; Swap point and mark like in `yank'. + (goto-char (prog1 (mark t) + (set-marker (mark-marker) (point) (current-buffer))))))) + +(put 'consult-yank-replace 'delete-selection 'yank) +(put 'consult-yank-pop 'delete-selection 'yank) +(put 'consult-yank-from-kill-ring 'delete-selection 'yank) + +;;;###autoload +(defun consult-yank-pop (&optional arg) + "If there is a recent yank act like `yank-pop'. + +Otherwise select string from the kill ring and insert it. +See `yank-pop' for the meaning of ARG. + +This command behaves like `yank-pop' in Emacs 28, which also offers a +`completing-read' interface to the `kill-ring'. Additionally the Consult +version supports preview of the selected string." + (interactive "*p") + (if (eq last-command 'yank) + (yank-pop (or arg 1)) + (call-interactively #'consult-yank-from-kill-ring))) + +;; Adapted from the Emacs yank-pop function. +;;;###autoload +(defun consult-yank-replace (string) + "Select STRING from the kill ring. + +If there was no recent yank, insert the string. +Otherwise replace the just-yanked string with the selected string. + +There exists no equivalent of this command in Emacs 28." + (interactive (list (consult--read-from-kill-ring))) + (when string + (if (not (eq last-command 'yank)) + (consult-yank-from-kill-ring string) + (let ((inhibit-read-only t) + (pt (point)) + (mk (mark t))) + (setq this-command 'yank) + (funcall (or yank-undo-function 'delete-region) (min pt mk) (max pt mk)) + (setq yank-undo-function nil) + (set-marker (mark-marker) pt (current-buffer)) + (insert-for-yank string) + (set-window-start (selected-window) yank-window-start t) + (if (< pt mk) + (goto-char (prog1 (mark t) + (set-marker (mark-marker) (point) (current-buffer))))))))) + +;;;;; Command: consult-bookmark + +(defun consult--bookmark-preview () + "Create preview function for bookmarks." + (let ((preview (consult--jump-preview)) + (open (consult--temporary-files))) + (lambda (action cand) + (unless cand + (funcall open)) + (funcall + preview action + ;; Only preview bookmarks with the default handler. + (when-let ((bm (and cand (eq action 'preview) (assoc cand bookmark-alist))) + (handler (or (bookmark-get-handler bm) #'bookmark-default-handler)) + ((eq handler #'bookmark-default-handler)) + (file (bookmark-get-filename bm)) + (pos (bookmark-get-position bm)) + (buf (funcall open file))) + (set-marker (make-marker) pos buf)))))) + +(defun consult--bookmark-action (bm) + "Open BM via `consult--buffer-action'." + (bookmark-jump bm consult--buffer-display)) + +(consult--define-state bookmark) + +(defun consult--bookmark-candidates () + "Return bookmark candidates." + (bookmark-maybe-load-default-file) + (let ((narrow (cl-loop for (y _ . xs) in consult-bookmark-narrow nconc + (cl-loop for x in xs collect (cons x y))))) + (cl-loop for bm in bookmark-alist collect + (propertize (car bm) + 'consult--type + (alist-get + (or (bookmark-get-handler bm) #'bookmark-default-handler) + narrow))))) + +;;;###autoload +(defun consult-bookmark (name) + "If bookmark NAME exists, open it, otherwise create a new bookmark with NAME. + +The command supports preview of file bookmarks and narrowing. See the +variable `consult-bookmark-narrow' for the narrowing configuration." + (interactive + (list + (let ((narrow (cl-loop for (x y . _) in consult-bookmark-narrow collect (cons x y)))) + (consult--read + (consult--bookmark-candidates) + :prompt "Bookmark: " + :state (consult--bookmark-preview) + :category 'bookmark + :history 'bookmark-history + ;; Add default names to future history. + ;; Ignore errors such that `consult-bookmark' can be used in + ;; buffers which are not backed by a file. + :add-history (ignore-errors (bookmark-prop-get (bookmark-make-record) 'defaults)) + :group (consult--type-group narrow) + :narrow (consult--type-narrow narrow))))) + (bookmark-maybe-load-default-file) + (if (assoc name bookmark-alist) + (bookmark-jump name) + (bookmark-set name))) + +;;;;; Command: consult-complex-command + +;;;###autoload +(defun consult-complex-command () + "Select and evaluate command from the command history. + +This command can act as a drop-in replacement for `repeat-complex-command'." + (interactive) + (let* ((history (or (delete-dups (mapcar #'prin1-to-string command-history)) + (user-error "There are no previous complex commands"))) + (cmd (read (consult--read + history + :prompt "Command: " + :default (car history) + :sort nil + :history t ;; disable history + :category 'expression)))) + ;; Taken from `repeat-complex-command' + (add-to-history 'command-history cmd) + (apply #'funcall-interactively + (car cmd) + (mapcar (lambda (e) (eval e t)) (cdr cmd))))) + +;;;;; Command: consult-history + +(declare-function ring-elements "ring") + +(defun consult--current-history () + "Return the history and index variable relevant to the current buffer. +If the minibuffer is active, the minibuffer history is returned, +otherwise the history corresponding to the mode. There is a +special case for `repeat-complex-command', for which the command +history is used." + (cond + ;; In the minibuffer we use the current minibuffer history, + ;; which can be configured by setting `minibuffer-history-variable'. + ((minibufferp) + (when (eq minibuffer-history-variable t) + (user-error "Minibuffer history is disabled for `%s'" this-command)) + (list (mapcar #'consult--tofu-hide + (if (eq minibuffer-history-variable 'command-history) + ;; If pressing "C-x M-:", i.e., `repeat-complex-command', + ;; we are instead querying the `command-history' and get a + ;; full s-expression. Alternatively you might want to use + ;; `consult-complex-command', which can also be bound to + ;; "C-x M-:"! + (mapcar #'prin1-to-string command-history) + (symbol-value minibuffer-history-variable))))) + ;; Otherwise we use a mode-specific history, see `consult-mode-histories'. + (t (let ((found (seq-find (lambda (h) + (and (derived-mode-p (car h)) + (boundp (if (consp (cdr h)) (cadr h) (cdr h))))) + consult-mode-histories))) + (unless found + (user-error "No history configured for `%s', see `consult-mode-histories'" + major-mode)) + (cons (symbol-value (cadr found)) (cddr found)))))) + +;;;###autoload +(defun consult-history (&optional history index bol) + "Insert string from HISTORY of current buffer. +In order to select from a specific HISTORY, pass the history +variable as argument. INDEX is the name of the index variable to +update, if any. BOL is the function which jumps to the beginning +of the prompt. See also `cape-history' from the Cape package." + (interactive) + (pcase-let* ((`(,history ,index ,bol) (if history + (list history index bol) + (consult--current-history))) + (history (if (ring-p history) (ring-elements history) history)) + (`(,beg . ,end) + (if (minibufferp) + (cons (minibuffer-prompt-end) (point-max)) + (if bol + (save-excursion + (funcall bol) + (cons (point) (pos-eol))) + (cons (point) (point))))) + (str (consult--local-let ((enable-recursive-minibuffers t)) + (consult--read + (or (consult--remove-dups history) + (user-error "History is empty")) + :prompt "History: " + :history t ;; disable history + :category ;; Report category depending on history variable + (and (minibufferp) + (pcase minibuffer-history-variable + ('extended-command-history 'command) + ('buffer-name-history 'buffer) + ('face-name-history 'face) + ('read-envvar-name-history 'environment-variable) + ('bookmark-history 'bookmark) + ('file-name-history 'file))) + :sort nil + :initial (buffer-substring-no-properties beg end) + :state (consult--insertion-preview beg end))))) + (delete-region beg end) + (when index + (set index (seq-position history str))) + (insert (substring-no-properties str)))) + +;;;;; Command: consult-isearch-history + +(defun consult-isearch-forward (&optional reverse) + "Continue Isearch forward optionally in REVERSE." + (interactive) + (consult--require-minibuffer) + (setq isearch-new-forward (not reverse) isearch-new-nonincremental nil) + (funcall (or (command-remapping #'exit-minibuffer) #'exit-minibuffer))) + +(defun consult-isearch-backward (&optional reverse) + "Continue Isearch backward optionally in REVERSE." + (interactive) + (consult-isearch-forward (not reverse))) + +;; Emacs 28: hide in M-X +(put #'consult-isearch-backward 'completion-predicate #'ignore) +(put #'consult-isearch-forward 'completion-predicate #'ignore) + +(defvar-keymap consult-isearch-history-map + :doc "Additional keymap used by `consult-isearch-history'." + " " #'consult-isearch-forward + " " #'consult-isearch-backward) + +(defun consult--isearch-history-candidates () + "Return Isearch history candidates." + ;; Do not throw an error on empty history, in order to allow starting a + ;; search. We do not :require-match here. + (let ((history (if (eq t search-default-mode) + (append regexp-search-ring search-ring) + (append search-ring regexp-search-ring)))) + (delete-dups + (mapcar + (lambda (cand) + ;; The search type can be distinguished via text properties. + (let* ((props (plist-member (text-properties-at 0 cand) + 'isearch-regexp-function)) + (type (pcase (cadr props) + ((and 'nil (guard (not props))) ?r) + ('nil ?l) + ('word-search-regexp ?w) + ('isearch-symbol-regexp ?s) + ('char-fold-to-regexp ?c) + (_ ?u)))) + ;; Disambiguate history items. The same string could + ;; occur with different search types. + (consult--tofu-append cand type))) + history)))) + +(defconst consult--isearch-history-narrow + '((?c . "Char") + (?u . "Custom") + (?l . "Literal") + (?r . "Regexp") + (?s . "Symbol") + (?w . "Word"))) + +;;;###autoload +(defun consult-isearch-history () + "Read a search string with completion from the Isearch history. + +This replaces the current search string if Isearch is active, and +starts a new Isearch session otherwise." + (interactive) + (consult--forbid-minibuffer) + (let* ((isearch-message-function #'ignore) + (cursor-in-echo-area t) ;; Avoid cursor flickering + (candidates (consult--isearch-history-candidates))) + (unless isearch-mode (isearch-mode t)) + (with-isearch-suspended + (setq isearch-new-string + (consult--read + candidates + :prompt "I-search: " + :category 'consult-isearch-history + :history t ;; disable history + :sort nil + :initial isearch-string + :keymap consult-isearch-history-map + :annotate + (lambda (cand) + (consult--annotate-align + cand + (alist-get (consult--tofu-get cand) consult--isearch-history-narrow))) + :group + (lambda (cand transform) + (if transform + cand + (alist-get (consult--tofu-get cand) consult--isearch-history-narrow))) + :lookup + (lambda (selected candidates &rest _) + (if-let (found (member selected candidates)) + (substring (car found) 0 -1) + selected)) + :state + (lambda (action cand) + (when (and (eq action 'preview) cand) + (setq isearch-string cand) + (isearch-update-from-string-properties cand) + (isearch-update))) + :narrow + (list :predicate + (lambda (cand) (= (consult--tofu-get cand) consult--narrow)) + :keys consult--isearch-history-narrow)) + isearch-new-message + (mapconcat 'isearch-text-char-description isearch-new-string ""))) + ;; Setting `isearch-regexp' etc only works outside of `with-isearch-suspended'. + (unless (plist-member (text-properties-at 0 isearch-string) 'isearch-regexp-function) + (setq isearch-regexp t + isearch-regexp-function nil)))) + +;;;;; Command: consult-minor-mode-menu + +(defun consult--minor-mode-candidates () + "Return list of minor-mode candidate strings." + (mapcar + (pcase-lambda (`(,name . ,sym)) + (propertize + name + 'consult--candidate sym + 'consult--minor-mode-narrow + (logior + (ash (if (local-variable-if-set-p sym) ?l ?g) 8) + (if (and (boundp sym) (symbol-value sym)) ?i ?o)) + 'consult--minor-mode-group + (concat + (if (local-variable-if-set-p sym) "Local " "Global ") + (if (and (boundp sym) (symbol-value sym)) "On" "Off")))) + (nconc + ;; according to describe-minor-mode-completion-table-for-symbol + ;; the minor-mode-list contains *all* minor modes + (mapcar (lambda (sym) (cons (symbol-name sym) sym)) minor-mode-list) + ;; take the lighters from minor-mode-alist + (delq nil + (mapcar (pcase-lambda (`(,sym ,lighter)) + (when (and lighter (not (equal "" lighter))) + (let (message-log-max) + (setq lighter (string-trim (format-mode-line lighter))) + (unless (string-blank-p lighter) + (cons lighter sym))))) + minor-mode-alist))))) + +(defconst consult--minor-mode-menu-narrow + '((?l . "Local") + (?g . "Global") + (?i . "On") + (?o . "Off"))) + +;;;###autoload +(defun consult-minor-mode-menu () + "Enable or disable minor mode. + +This is an alternative to `minor-mode-menu-from-indicator'." + (interactive) + (call-interactively + (consult--read + (consult--minor-mode-candidates) + :prompt "Minor mode: " + :require-match t + :category 'minor-mode + :group + (lambda (cand transform) + (if transform cand (get-text-property 0 'consult--minor-mode-group cand))) + :narrow + (list :predicate + (lambda (cand) + (let ((narrow (get-text-property 0 'consult--minor-mode-narrow cand))) + (or (= (logand narrow 255) consult--narrow) + (= (ash narrow -8) consult--narrow)))) + :keys + consult--minor-mode-menu-narrow) + :lookup #'consult--lookup-candidate + :history 'consult--minor-mode-menu-history))) + +;;;;; Command: consult-theme + +;;;###autoload +(defun consult-theme (theme) + "Disable current themes and enable THEME from `consult-themes'. + +The command supports previewing the currently selected theme." + (interactive + (list + (let* ((regexp (consult--regexp-filter + (mapcar (lambda (x) (if (stringp x) x (format "\\`%s\\'" x))) + consult-themes))) + (avail-themes (seq-filter + (lambda (x) (string-match-p regexp (symbol-name x))) + (cons 'default (custom-available-themes)))) + (saved-theme (car custom-enabled-themes))) + (consult--read + (mapcar #'symbol-name avail-themes) + :prompt "Theme: " + :require-match t + :category 'theme + :history 'consult--theme-history + :lookup (lambda (selected &rest _) + (setq selected (and selected (intern-soft selected))) + (or (and selected (car (memq selected avail-themes))) + saved-theme)) + :state (lambda (action theme) + (pcase action + ('return (consult-theme (or theme saved-theme))) + ((and 'preview (guard theme)) (consult-theme theme)))) + :default (symbol-name (or saved-theme 'default)))))) + (when (eq theme 'default) (setq theme nil)) + (unless (eq theme (car custom-enabled-themes)) + (mapc #'disable-theme custom-enabled-themes) + (when theme + (if (custom-theme-p theme) + (enable-theme theme) + (load-theme theme :no-confirm))))) + +;;;;; Command: consult-buffer + +(defun consult--buffer-sort-alpha (buffers) + "Sort BUFFERS alphabetically, put starred buffers at the end." + (sort buffers + (lambda (x y) + (setq x (buffer-name x) y (buffer-name y)) + (let ((a (and (length> x 0) (eq (aref x 0) ?*))) + (b (and (length> y 0) (eq (aref y 0) ?*)))) + (if (eq a b) + (string< x y) + (not a)))))) + +(defun consult--buffer-sort-alpha-current (buffers) + "Sort BUFFERS alphabetically, put current at the beginning." + (let ((buffers (consult--buffer-sort-alpha buffers)) + (current (current-buffer))) + (if (memq current buffers) + (cons current (delq current buffers)) + buffers))) + +(defun consult--buffer-sort-visibility (buffers) + "Sort BUFFERS by visibility." + (let ((hidden) + (current (current-buffer))) + (consult--keep! buffers + (unless (eq it current) + (if (get-buffer-window it 'visible) + it + (push it hidden) + nil))) + (nconc (nreverse hidden) buffers (list current)))) + +(defun consult--normalize-directory (dir) + "Normalize directory DIR. +DIR can be project, nil or a path." + (cond + ((eq dir 'project) (consult--project-root)) + (dir (expand-file-name dir)))) + +(defun consult--buffer-query-prompt (prompt query) + "Return a list of buffers and create an appropriate prompt string. +Return a pair of a prompt string and a list of buffers. PROMPT +is the prefix of the prompt string. QUERY specifies the buffers +to search and is passed to `consult--buffer-query'." + (let* ((dir (plist-get query :directory)) + (ndir (consult--normalize-directory dir)) + (buffers (apply #'consult--buffer-query :directory ndir query)) + (count (length buffers))) + (cons (format "%s (%d buffer%s%s): " prompt count + (if (= count 1) "" "s") + (cond + ((and ndir (eq dir 'project)) + (format ", Project %s" (consult--project-name ndir))) + (ndir (concat ", " (consult--left-truncate-file ndir))) + (t ""))) + buffers))) + +(cl-defun consult--buffer-query (&key sort directory mode as predicate (filter t) + include (exclude consult-buffer-filter)) + "Query for a list of matching buffers. +The function supports filtering by various criteria which are +used throughout Consult. In particular it is the backbone of +most `consult-buffer-sources'. +DIRECTORY can either be the symbol project or a file name. +SORT can be visibility, alpha or nil. +FILTER can be either t, nil or invert. +EXCLUDE is a list of regexps. +INCLUDE is a list of regexps. +MODE can be a mode or a list of modes to restrict the returned buffers. +PREDICATE is a predicate function. +AS is a conversion function." + (let ((root (consult--normalize-directory directory)) + (buffers (buffer-list))) + (when sort + (setq buffers (funcall (intern (format "consult--buffer-sort-%s" sort)) buffers))) + (when (or filter mode as root) + (let ((exclude-re (consult--regexp-filter exclude)) + (include-re (consult--regexp-filter include)) + (case-fold-search)) + (consult--keep! buffers + (and + (or (not mode) + (let ((mm (buffer-local-value 'major-mode it))) + (if (consp mode) + (seq-some (lambda (m) (provided-mode-derived-p mm m)) mode) + (provided-mode-derived-p mm mode)))) + (pcase-exhaustive filter + ('nil t) + ((or 't 'invert) + (eq (eq filter t) + (and + (or (not exclude) + (not (string-match-p exclude-re (buffer-name it)))) + (or (not include) + (not (not (string-match-p include-re (buffer-name it))))))))) + (or (not root) + (when-let (dir (buffer-local-value 'default-directory it)) + (string-prefix-p root + (if (and (/= 0 (length dir)) (eq (aref dir 0) ?/)) + dir + (expand-file-name dir))))) + (or (not predicate) (funcall predicate it)) + (if as (funcall as it) it))))) + buffers)) + +(defun consult--buffer-file-hash () + "Return hash table of all buffer file names." + (consult--string-hash (consult--buffer-query :as #'buffer-file-name))) + +(defun consult--buffer-pair (buffer) + "Return a pair of name of BUFFER and BUFFER." + (cons (buffer-name buffer) buffer)) + +(defun consult--buffer-preview () + "Buffer preview function." + (let ((orig-buf (window-buffer (consult--original-window))) + (orig-prev (copy-sequence (window-prev-buffers))) + (orig-next (copy-sequence (window-next-buffers))) + other-win) + (lambda (action cand) + (pcase action + ('exit + (set-window-prev-buffers other-win orig-prev) + (set-window-next-buffers other-win orig-next)) + ('preview + (when (and (eq consult--buffer-display #'switch-to-buffer-other-window) + (not other-win)) + (switch-to-buffer-other-window orig-buf 'norecord) + (setq other-win (selected-window))) + (let ((win (or other-win (selected-window))) + (buf (or (and cand (get-buffer cand)) orig-buf))) + (when (and (window-live-p win) (buffer-live-p buf)) + (with-selected-window win + (unless (or orig-prev orig-next) + (setq orig-prev (copy-sequence (window-prev-buffers)) + orig-next (copy-sequence (window-next-buffers)))) + (switch-to-buffer buf 'norecord))))))))) + +(defun consult--buffer-action (buffer &optional norecord) + "Switch to BUFFER via `consult--buffer-display' function. +If NORECORD is non-nil, do not record the buffer switch in the buffer list." + (funcall consult--buffer-display buffer norecord)) + +(consult--define-state buffer) + +(defvar consult--source-bookmark + `(:name "Bookmark" + :narrow ?m + :category bookmark + :face consult-bookmark + :history bookmark-history + :items ,#'bookmark-all-names + :state ,#'consult--bookmark-state) + "Bookmark candidate source for `consult-buffer'.") + +(defvar consult--source-project-buffer + `(:name "Project Buffer" + :narrow ?b + :category buffer + :face consult-buffer + :history buffer-name-history + :state ,#'consult--buffer-state + :enabled ,(lambda () consult-project-function) + :items + ,(lambda () + (when-let (root (consult--project-root)) + (consult--buffer-query :sort 'visibility + :directory root + :as #'consult--buffer-pair)))) + "Project buffer candidate source for `consult-buffer'.") + +(defvar consult--source-project-recent-file + `(:name "Project File" + :narrow ?f + :category file + :face consult-file + :history file-name-history + :state ,#'consult--file-state + :new + ,(lambda (file) + (consult--file-action + (expand-file-name file (consult--project-root)))) + :enabled + ,(lambda () + (and consult-project-function + recentf-mode)) + :items + ,(lambda () + (when-let (root (consult--project-root)) + (let ((len (length root)) + (ht (consult--buffer-file-hash)) + items) + (dolist (file (bound-and-true-p recentf-list) (nreverse items)) + ;; Emacs 29 abbreviates file paths by default, see + ;; `recentf-filename-handlers'. I recommend to set + ;; `recentf-filename-handlers' to nil to avoid any slow down. + (unless (eq (aref file 0) ?/) + (let (file-name-handler-alist) ;; No Tramp slowdown please. + (setq file (expand-file-name file)))) + (when (and (not (gethash file ht)) (string-prefix-p root file)) + (let ((part (substring file len))) + (when (equal part "") (setq part "./")) + (put-text-property 0 1 'multi-category `(file . ,file) part) + (push part items)))))))) + "Project file candidate source for `consult-buffer'.") + +(defvar consult--source-project-buffer-hidden + `(:hidden t :narrow (?p . "Project") ,@consult--source-project-buffer) + "Like `consult--source-project-buffer' but hidden by default.") + +(defvar consult--source-project-recent-file-hidden + `(:hidden t :narrow (?p . "Project") ,@consult--source-project-recent-file) + "Like `consult--source-project-recent-file' but hidden by default.") + +(defvar consult--source-hidden-buffer + `(:name "Hidden Buffer" + :narrow ?\s + :hidden t + :category buffer + :face consult-buffer + :history buffer-name-history + :action ,#'consult--buffer-action + :items + ,(lambda () (consult--buffer-query :sort 'visibility + :filter 'invert + :as #'consult--buffer-pair))) + "Hidden buffer candidate source for `consult-buffer'.") + +(defvar consult--source-modified-buffer + `(:name "Modified Buffer" + :narrow ?* + :hidden t + :category buffer + :face consult-buffer + :history buffer-name-history + :state ,#'consult--buffer-state + :items + ,(lambda () (consult--buffer-query :sort 'visibility + :as #'consult--buffer-pair + :predicate + (lambda (buf) + (and (buffer-modified-p buf) + (buffer-file-name buf)))))) + "Modified buffer candidate source for `consult-buffer'.") + +(defvar consult--source-buffer + `(:name "Buffer" + :narrow ?b + :category buffer + :face consult-buffer + :history buffer-name-history + :state ,#'consult--buffer-state + :default t + :items + ,(lambda () (consult--buffer-query :sort 'visibility + :as #'consult--buffer-pair))) + "Buffer candidate source for `consult-buffer'.") + +(defun consult--file-register-p (reg) + "Return non-nil if REG is a file register." + (memq (car-safe (cdr reg)) '(file-query file))) + +(autoload 'consult-register--candidates "consult-register") +(defvar consult--source-file-register + `(:name "File Register" + :narrow (?r . "Register") + :category file + :state ,#'consult--file-state + :enabled ,(lambda () (seq-some #'consult--file-register-p register-alist)) + :items ,(lambda () (consult-register--candidates #'consult--file-register-p))) + "File register source.") + +(defvar consult--source-recent-file + `(:name "File" + :narrow ?f + :category file + :face consult-file + :history file-name-history + :state ,#'consult--file-state + :new ,#'consult--file-action + :enabled ,(lambda () recentf-mode) + :items + ,(lambda () + (let ((ht (consult--buffer-file-hash)) + items) + (dolist (file (bound-and-true-p recentf-list) (nreverse items)) + ;; Emacs 29 abbreviates file paths by default, see + ;; `recentf-filename-handlers'. I recommend to set + ;; `recentf-filename-handlers' to nil to avoid any slow down. + (unless (eq (aref file 0) ?/) + (let (file-name-handler-alist) ;; No Tramp slowdown please. + (setq file (expand-file-name file)))) + (unless (gethash file ht) + (push (consult--fast-abbreviate-file-name file) items)))))) + "Recent file candidate source for `consult-buffer'.") + +;;;###autoload +(defun consult-buffer (&optional sources) + "Enhanced `switch-to-buffer' command with support for virtual buffers. + +The command supports recent files, bookmarks, views and project files as +virtual buffers. Buffers are previewed. Narrowing to buffers (b), files (f), +bookmarks (m) and project files (p) is supported via the corresponding +keys. In order to determine the project-specific files and buffers, the +`consult-project-function' is used. The virtual buffer SOURCES +default to `consult-buffer-sources'. See `consult--multi' for the +configuration of the virtual buffer sources." + (interactive) + (let ((selected (consult--multi (or sources consult-buffer-sources) + :require-match + (confirm-nonexistent-file-or-buffer) + :prompt "Switch to: " + :history 'consult--buffer-history + :sort nil))) + ;; For non-matching candidates, fall back to buffer creation. + (unless (plist-get (cdr selected) :match) + (consult--buffer-action (car selected))))) + +(defmacro consult--with-project (&rest body) + "Ensure that BODY is executed with a project root." + ;; We have to work quite hard here to ensure that the project root is + ;; only overridden at the current recursion level. When entering a + ;; recursive minibuffer session, we should be able to still switch the + ;; project. But who does that? Working on the first level on project A + ;; and on the second level on project B and on the third level on project C? + ;; You mustn't be afraid to dream a little bigger, darling. + `(let ((consult-project-function + (let ((root (or (consult--project-root t) (user-error "No project found"))) + (depth (recursion-depth)) + (orig consult-project-function)) + (lambda (may-prompt) + (if (= depth (recursion-depth)) + root + (funcall orig may-prompt)))))) + ,@body)) + +;;;###autoload +(defun consult-project-buffer () + "Enhanced `project-switch-to-buffer' command with support for virtual buffers. +The command may prompt you for a project directory if it is invoked from +outside a project. See `consult-buffer' for more details." + (interactive) + (consult--with-project + (consult-buffer consult-project-buffer-sources))) + +;;;###autoload +(defun consult-buffer-other-window () + "Variant of `consult-buffer', switching to a buffer in another window." + (interactive) + (let ((consult--buffer-display #'switch-to-buffer-other-window)) + (consult-buffer))) + +;;;###autoload +(defun consult-buffer-other-frame () + "Variant of `consult-buffer', switching to a buffer in another frame." + (interactive) + (let ((consult--buffer-display #'switch-to-buffer-other-frame)) + (consult-buffer))) + +;;;###autoload +(defun consult-buffer-other-tab () + "Variant of `consult-buffer', switching to a buffer in another tab." + (interactive) + (let ((consult--buffer-display #'switch-to-buffer-other-tab)) + (consult-buffer))) + +;;;;; Command: consult-grep + +(defun consult--grep-format (async builder) + "Return ASYNC function highlighting grep match results. +BUILDER is the command line builder function." + (let (highlight) + (lambda (action) + (cond + ((stringp action) + (setq highlight (cdr (funcall builder action))) + (funcall async action)) + ((consp action) + (let ((file "") (file-len 0) result) + (save-match-data + (dolist (str action) + (when (and (string-match consult--grep-match-regexp str) + ;; Filter out empty context lines + (or (/= (aref str (match-beginning 3)) ?-) + (/= (match-end 0) (length str)))) + ;; We share the file name across candidates to reduce + ;; the amount of allocated memory. + (unless (and (= file-len (- (match-end 1) (match-beginning 1))) + (eq t (compare-strings + file 0 file-len + str (match-beginning 1) (match-end 1) nil))) + (setq file (match-string 1 str) + file-len (length file))) + (let* ((line (match-string 2 str)) + (ctx (= (aref str (match-beginning 3)) ?-)) + (sep (if ctx "-" ":")) + (content (substring str (match-end 0))) + (line-len (length line))) + (when (length> content consult-grep-max-columns) + (setq content (substring content 0 consult-grep-max-columns))) + (when highlight + (funcall highlight content)) + (setq str (concat file sep line sep content)) + ;; Store file name in order to avoid allocations in `consult--prefix-group' + (add-text-properties 0 file-len `(face consult-file consult--prefix-group ,file) str) + (put-text-property (1+ file-len) (+ 1 file-len line-len) 'face 'consult-line-number str) + (when ctx + (add-face-text-property (+ 2 file-len line-len) (length str) 'consult-grep-context 'append str)) + (push str result))))) + (funcall async (nreverse result)))) + (t (funcall async action)))))) + +(defun consult--grep-position (cand &optional find-file) + "Return the grep position marker for CAND. +FIND-FILE is the file open function, defaulting to `find-file-noselect'." + (when cand + (let* ((file-end (next-single-property-change 0 'face cand)) + (line-end (next-single-property-change (1+ file-end) 'face cand)) + (matches (consult--point-placement cand (1+ line-end) 'consult-grep-context)) + (file (substring-no-properties cand 0 file-end)) + (line (string-to-number (substring-no-properties cand (+ 1 file-end) line-end)))) + (when-let (pos (consult--marker-from-line-column + (funcall (or find-file #'find-file-noselect) file) + line (or (car matches) 0))) + (cons pos (cdr matches)))))) + +(defun consult--grep-state () + "Grep state function." + (let ((open (consult--temporary-files)) + (jump (consult--jump-state))) + (lambda (action cand) + (unless cand + (funcall open)) + (funcall jump action (consult--grep-position + cand + (and (not (eq action 'return)) open)))))) + +(defun consult--grep-exclude-args () + "Produce grep exclude arguments. +Take the variables `grep-find-ignored-directories' and +`grep-find-ignored-files' into account." + (unless (boundp 'grep-find-ignored-files) (require 'grep)) + (nconc (mapcar (lambda (s) (concat "--exclude=" s)) + (bound-and-true-p grep-find-ignored-files)) + (mapcar (lambda (s) (concat "--exclude-dir=" s)) + (bound-and-true-p grep-find-ignored-directories)))) + +(defun consult--grep (prompt make-builder dir initial) + "Run asynchronous grep. + +MAKE-BUILDER is the function that returns the command line +builder function. DIR is a directory or a list of file or +directories. PROMPT is the prompt string. INITIAL is initial +input." + (pcase-let* ((`(,prompt ,paths ,dir) (consult--directory-prompt prompt dir)) + (default-directory dir) + (builder (funcall make-builder paths))) + (consult--read + (consult--async-command builder + (consult--grep-format builder) + :file-handler t) ;; allow tramp + :prompt prompt + :lookup #'consult--lookup-member + :state (consult--grep-state) + :initial (consult--async-split-initial initial) + :add-history (consult--async-split-thingatpt 'symbol) + :require-match t + :category 'consult-grep + :group #'consult--prefix-group + :history '(:input consult--grep-history) + :sort nil))) + +(defun consult--grep-lookahead-p (&rest cmd) + "Return t if grep CMD supports look-ahead." + (eq 0 (process-file-shell-command + (concat "echo xaxbx | " + (mapconcat #'shell-quote-argument `(,@cmd "^(?=.*b)(?=.*a)") " "))))) + +(defun consult--grep-make-builder (paths) + "Build grep command line and grep across PATHS." + (let* ((cmd (consult--build-args consult-grep-args)) + (type (if (consult--grep-lookahead-p (car cmd) "-P") 'pcre 'extended))) + (lambda (input) + (pcase-let* ((`(,arg . ,opts) (consult--command-split input)) + (flags (append cmd opts)) + (ignore-case (or (member "-i" flags) (member "--ignore-case" flags)))) + (if (or (member "-F" flags) (member "--fixed-strings" flags)) + (cons (append cmd (list "-e" arg) opts paths) + (apply-partially #'consult--highlight-regexps + (list (regexp-quote arg)) ignore-case)) + (pcase-let ((`(,re . ,hl) (funcall consult--regexp-compiler arg type ignore-case))) + (when re + (cons (append cmd + (list (if (eq type 'pcre) "-P" "-E") ;; perl or extended + "-e" (consult--join-regexps re type)) + opts paths) + hl)))))))) + +;;;###autoload +(defun consult-grep (&optional dir initial) + "Search with `grep' for files in DIR where the content matches a regexp. + +The initial input is given by the INITIAL argument. DIR can be +nil, a directory string or a list of file/directory paths. If +`consult-grep' is called interactively with a prefix argument, +the user can specify the directories or files to search in. +Multiple directories must be separated by comma in the +minibuffer, since they are read via `completing-read-multiple'. +By default the project directory is used if +`consult-project-function' is defined and returns non-nil. +Otherwise the `default-directory' is searched. + +The input string is split, the first part of the string (grep +input) is passed to the asynchronous grep process and the second +part of the string is passed to the completion-style filtering. + +The input string is split at a punctuation character, which is +given as the first character of the input string. The format is +similar to Perl-style regular expressions, e.g., /regexp/. +Furthermore command line options can be passed to grep, specified +behind --. The overall prompt input has the form +`#async-input -- grep-opts#filter-string'. + +Note that the grep input string is transformed from Emacs regular +expressions to Posix regular expressions. Always enter Emacs +regular expressions at the prompt. `consult-grep' behaves like +builtin Emacs search commands, e.g., Isearch, which take Emacs +regular expressions. Furthermore the asynchronous input split +into words, each word must match separately and in any order. +See `consult--regexp-compiler' for the inner workings. In order +to disable transformations of the grep input, adjust +`consult--regexp-compiler' accordingly. + +Here we give a few example inputs: + +#alpha beta : Search for alpha and beta in any order. +#alpha.*beta : Search for alpha before beta. +#\\(alpha\\|beta\\) : Search for alpha or beta (Note Emacs syntax!) +#word -- -C3 : Search for word, include 3 lines as context +#first#second : Search for first, quick filter for second. + +The symbol at point is added to the future history." + (interactive "P") + (consult--grep "Grep" #'consult--grep-make-builder dir initial)) + +;;;;; Command: consult-git-grep + +(defun consult--git-grep-make-builder (paths) + "Create grep command line builder given PATHS." + (let ((cmd (consult--build-args consult-git-grep-args))) + (lambda (input) + (pcase-let* ((`(,arg . ,opts) (consult--command-split input)) + (flags (append cmd opts)) + (ignore-case (or (member "-i" flags) (member "--ignore-case" flags)))) + (if (or (member "-F" flags) (member "--fixed-strings" flags)) + (cons (append cmd (list "-e" arg) opts paths) + (apply-partially #'consult--highlight-regexps + (list (regexp-quote arg)) ignore-case)) + (pcase-let ((`(,re . ,hl) (funcall consult--regexp-compiler arg 'extended ignore-case))) + (when re + (cons (append cmd + (cdr (mapcan (lambda (x) (list "--and" "-e" x)) re)) + opts paths) + hl)))))))) + +;;;###autoload +(defun consult-git-grep (&optional dir initial) + "Search with `git grep' for files in DIR with INITIAL input. +See `consult-grep' for details." + (interactive "P") + (consult--grep "Git-grep" #'consult--git-grep-make-builder dir initial)) + +;;;;; Command: consult-ripgrep + +(defun consult--ripgrep-make-builder (paths) + "Create ripgrep command line builder given PATHS." + (let* ((cmd (consult--build-args consult-ripgrep-args)) + (type (if (consult--grep-lookahead-p (car cmd) "-P") 'pcre 'extended))) + (lambda (input) + (pcase-let* ((`(,arg . ,opts) (consult--command-split input)) + (flags (append cmd opts)) + (ignore-case + (and (not (or (member "-s" flags) (member "--case-sensitive" flags))) + (or (member "-i" flags) (member "--ignore-case" flags) + (and (or (member "-S" flags) (member "--smart-case" flags)) + (let (case-fold-search) + ;; Case insensitive if there are no uppercase letters + (not (string-match-p "[[:upper:]]" arg)))))))) + (if (or (member "-F" flags) (member "--fixed-strings" flags)) + (cons (append cmd (list "-e" arg) opts paths) + (apply-partially #'consult--highlight-regexps + (list (regexp-quote arg)) ignore-case)) + (pcase-let ((`(,re . ,hl) (funcall consult--regexp-compiler arg type ignore-case))) + (when re + (cons (append cmd (and (eq type 'pcre) '("-P")) + (list "-e" (consult--join-regexps re type)) + opts paths) + hl)))))))) + +;;;###autoload +(defun consult-ripgrep (&optional dir initial) + "Search with `rg' for files in DIR with INITIAL input. +See `consult-grep' for details." + (interactive "P") + (consult--grep "Ripgrep" #'consult--ripgrep-make-builder dir initial)) + +;;;;; Command: consult-find + +(defun consult--find (prompt builder initial) + "Run find command in current directory. + +The function returns the selected file. +The filename at point is added to the future history. + +BUILDER is the command line builder function. +PROMPT is the prompt. +INITIAL is initial input." + (consult--read + (consult--async-command builder + (consult--async-map (lambda (x) (string-remove-prefix "./" x))) + (consult--async-highlight builder) + :file-handler t) ;; allow tramp + :prompt prompt + :sort nil + :require-match t + :initial (consult--async-split-initial initial) + :add-history (consult--async-split-thingatpt 'filename) + :category 'file + :history '(:input consult--find-history))) + +(defun consult--find-make-builder (paths) + "Build find command line, finding across PATHS." + (let* ((cmd (seq-mapcat (lambda (x) + (if (equal x ".") paths (list x))) + (consult--build-args consult-find-args))) + (type (if (eq 0 (process-file-shell-command + (concat (car cmd) " -regextype emacs -version"))) + 'emacs 'basic))) + (lambda (input) + (pcase-let* ((`(,arg . ,opts) (consult--command-split input)) + ;; ignore-case=t since -iregex is used below + (`(,re . ,hl) (funcall consult--regexp-compiler arg type t))) + (when re + (cons (append cmd + (cdr (mapcan + (lambda (x) + `("-and" "-iregex" + ,(format ".*%s.*" + ;; Replace non-capturing groups with capturing groups. + ;; GNU find does not support non-capturing groups. + (replace-regexp-in-string + "\\\\(\\?:" "\\(" x 'fixedcase 'literal)))) + re)) + opts) + hl)))))) + +;;;###autoload +(defun consult-find (&optional dir initial) + "Search for files with `find' in DIR. +The file names must match the input regexp. INITIAL is the +initial minibuffer input. See `consult-grep' for details +regarding the asynchronous search and the arguments." + (interactive "P") + (pcase-let* ((`(,prompt ,paths ,dir) (consult--directory-prompt "Find" dir)) + (default-directory dir) + (builder (consult--find-make-builder paths))) + (find-file (consult--find prompt builder initial)))) + +;;;;; Command: consult-fd + +(defun consult--fd-make-builder (paths) + "Build find command line, finding across PATHS." + (let ((cmd (consult--build-args consult-fd-args))) + (lambda (input) + (pcase-let* ((`(,arg . ,opts) (consult--command-split input)) + (flags (append cmd opts)) + (ignore-case + (and (not (or (member "-s" flags) (member "--case-sensitive" flags))) + (or (member "-i" flags) (member "--ignore-case" flags) + (let (case-fold-search) + ;; Case insensitive if there are no uppercase letters + (not (string-match-p "[[:upper:]]" arg))))))) + (if (or (member "-F" flags) (member "--fixed-strings" flags)) + (cons (append cmd (list arg) opts paths) + (apply-partially #'consult--highlight-regexps + (list (regexp-quote arg)) ignore-case)) + (pcase-let ((`(,re . ,hl) (funcall consult--regexp-compiler arg 'pcre ignore-case))) + (when re + (cons (append cmd + (cdr (mapcan (lambda (x) `("--and" ,x)) re)) + opts paths) + hl)))))))) + +;;;###autoload +(defun consult-fd (&optional dir initial) + "Search for files with `fd' in DIR. +The file names must match the input regexp. INITIAL is the +initial minibuffer input. See `consult-grep' for details +regarding the asynchronous search and the arguments." + (interactive "P") + (pcase-let* ((`(,prompt ,paths ,dir) (consult--directory-prompt "Fd" dir)) + (default-directory dir) + (builder (consult--fd-make-builder paths))) + (find-file (consult--find prompt builder initial)))) + +;;;;; Command: consult-locate + +(defun consult--locate-builder (input) + "Build command line from INPUT." + (pcase-let ((`(,arg . ,opts) (consult--command-split input))) + (unless (string-blank-p arg) + (cons (append (consult--build-args consult-locate-args) + (consult--split-escaped arg) opts) + (cdr (consult--default-regexp-compiler input 'basic t)))))) + +;;;###autoload +(defun consult-locate (&optional initial) + "Search with `locate' for files which match input given INITIAL input. + +The input is treated literally such that locate can take advantage of +the locate database index. Regular expressions would often force a slow +linear search through the entire database. The locate process is started +asynchronously, similar to `consult-grep'. See `consult-grep' for more +details regarding the asynchronous search." + (interactive) + (find-file (consult--find "Locate: " #'consult--locate-builder initial))) + +;;;;; Command: consult-man + +(defun consult--man-builder (input) + "Build command line from INPUT." + (pcase-let* ((`(,arg . ,opts) (consult--command-split input)) + (`(,re . ,hl) (funcall consult--regexp-compiler arg 'extended t))) + (when re + (cons (append (consult--build-args consult-man-args) + (list (consult--join-regexps re 'extended)) + opts) + hl)))) + +(defun consult--man-format (lines) + "Format man candidates from LINES." + (let ((candidates)) + (save-match-data + (dolist (str lines) + (when (string-match "\\`\\(.*?\\([^ ]+\\) *(\\([^,)]+\\)[^)]*).*?\\) +- +\\(.*\\)\\'" str) + (let* ((names (match-string 1 str)) + (name (match-string 2 str)) + (section (match-string 3 str)) + (desc (match-string 4 str)) + (cand (format "%s - %s" names desc))) + (add-text-properties 0 (length names) + (list 'face 'consult-file + 'consult-man (concat section " " name)) + cand) + (push cand candidates))))) + (nreverse candidates))) + +;;;###autoload +(defun consult-man (&optional initial) + "Search for man page given INITIAL input. + +The input string is not preprocessed and passed literally to the +underlying man commands. The man process is started asynchronously, +similar to `consult-grep'. See `consult-grep' for more details regarding +the asynchronous search." + (interactive) + (man (consult--read + (consult--async-command #'consult--man-builder + (consult--async-transform consult--man-format) + (consult--async-highlight #'consult--man-builder)) + :prompt "Manual entry: " + :require-match t + :category 'consult-man + :lookup (apply-partially #'consult--lookup-prop 'consult-man) + :initial (consult--async-split-initial initial) + :add-history (consult--async-split-thingatpt 'symbol) + :history '(:input consult--man-history)))) + +;;;; Preview at point in completions buffers + +(define-minor-mode consult-preview-at-point-mode + "Preview minor mode for *Completions* buffers. +When moving around in the *Completions* buffer, the candidate at point is +automatically previewed." + :group 'consult + (if consult-preview-at-point-mode + (add-hook 'post-command-hook #'consult-preview-at-point nil 'local) + (remove-hook 'post-command-hook #'consult-preview-at-point 'local))) + +(defun consult-preview-at-point () + "Preview candidate at point in *Completions* buffer." + (interactive) + (when-let ((win (active-minibuffer-window)) + (buf (window-buffer win)) + (fun (buffer-local-value 'consult--preview-function buf))) + (funcall fun))) + +;;;; Integration with completion systems + +;;;;; Integration: Default *Completions* + +(defun consult--default-completion-minibuffer-candidate () + "Return current minibuffer candidate from default completion system or Icomplete." + (when (and (minibufferp) + (eq completing-read-function #'completing-read-default)) + (let ((content (minibuffer-contents-no-properties))) + ;; When the current minibuffer content matches a candidate, return it! + (if (test-completion content + minibuffer-completion-table + minibuffer-completion-predicate) + content + ;; Return the full first candidate of the sorted completion list. + (when-let ((completions (completion-all-sorted-completions))) + (concat + (substring content 0 (or (cdr (last completions)) 0)) + (car completions))))))) + +(defun consult--default-completion-list-candidate () + "Return current candidate at point from completions buffer." + (let (beg end) + (when (and + (derived-mode-p 'completion-list-mode) + ;; Logic taken from `choose-completion'. + ;; TODO Upstream a `completion-list-get-candidate' function. + (cond + ((and (not (eobp)) (get-text-property (point) 'mouse-face)) + (setq end (point) beg (1+ (point)))) + ((and (not (bobp)) (get-text-property (1- (point)) 'mouse-face)) + (setq end (1- (point)) beg (point))))) + (setq beg (previous-single-property-change beg 'mouse-face) + end (or (next-single-property-change end 'mouse-face) (point-max))) + (or (get-text-property beg 'completion--string) + (buffer-substring-no-properties beg end))))) + +;;;;; Integration: Vertico + +(defvar vertico--input) +(declare-function vertico--exhibit "ext:vertico") +(declare-function vertico--candidate "ext:vertico") +(declare-function vertico--filter-completions "ext:vertico") + +(defun consult--vertico-candidate () + "Return current candidate for Consult preview." + (and vertico--input (vertico--candidate 'highlight))) + +(defun consult--vertico-refresh () + "Refresh completion UI." + (when vertico--input + (setq vertico--input t) + (vertico--exhibit))) + +(defun consult--vertico-filter-adv (orig pattern cands category highlight) + "Advice for ORIG `consult--completion-filter' function. +See `consult--completion-filter' for arguments PATTERN, CANDS, CATEGORY +and HIGHLIGHT." + (if (and (not highlight) (bound-and-true-p vertico-mode)) + ;; Optimize `consult--completion-filter' using the deferred highlighting + ;; from Vertico. The advice is not necessary - it is a pure optimization. + (nconc (car (vertico--filter-completions pattern cands nil (length pattern) + `(metadata (category . ,category)))) + nil) + (funcall orig pattern cands category highlight))) + +(with-eval-after-load 'vertico + (advice-add #'consult--completion-filter :around #'consult--vertico-filter-adv) + (add-hook 'consult--completion-candidate-hook #'consult--vertico-candidate) + (add-hook 'consult--completion-refresh-hook #'consult--vertico-refresh) + (define-key consult-async-map [remap vertico-insert] 'vertico-next-group)) + +;;;;; Integration: Mct + +(with-eval-after-load 'mct (add-hook 'consult--completion-refresh-hook + 'mct--live-completions-refresh)) + +;;;;; Integration: Icomplete + +(defvar icomplete-mode) +(declare-function icomplete-exhibit "icomplete") + +(defun consult--icomplete-refresh () + "Refresh icomplete view." + (when icomplete-mode + (let ((top (car completion-all-sorted-completions))) + (completion--flush-all-sorted-completions) + ;; force flushing, otherwise narrowing is broken! + (setq completion-all-sorted-completions nil) + (when top + (let* ((completions (completion-all-sorted-completions)) + (last (last completions)) + (before)) ;; completions before top + ;; warning: completions is an improper list + (while (consp completions) + (if (equal (car completions) top) + (progn + (setcdr last (append (nreverse before) (cdr last))) + (setq completion-all-sorted-completions completions + completions nil)) + (push (car completions) before) + (setq completions (cdr completions))))))) + (icomplete-exhibit))) + +(with-eval-after-load 'icomplete + (add-hook 'consult--completion-refresh-hook #'consult--icomplete-refresh)) + +(provide 'consult) +;;; consult.el ends here blob - /dev/null blob + c007175cd15c22c3ab8409360172611cfd8d5f30 (mode 644) Binary files /dev/null and elpa/consult-1.5/consult.info differ blob - /dev/null blob + 8f854d786568c1933d455c28482e23663ccb4bec (mode 644) --- /dev/null +++ elpa/consult-1.5/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 +* Consult: (consult). Useful commands built on completing-read. blob - 5ad87555042b19e460d053a050218b19f4d7a2de (mode 644) blob + /dev/null --- elpa/consult-1.4.signed +++ /dev/null @@ -1,2 +0,0 @@ -Good signature from 066DAFCB81E42C40 GNU ELPA Signing Agent (2019) (trust undefined) created at 2024-03-08T23:05:03+0100 using RSA -Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) (trust undefined) created at 2024-03-08T23:05:03+0100 using EDDSA \ No newline at end of file blob - /dev/null blob + 71f0f4851ae3492275b8be424596025b29adcbe5 (mode 644) --- /dev/null +++ elpa/consult-1.5.signed @@ -0,0 +1 @@ +Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) (trust undefined) created at 2024-04-19T23:05:03+0200 using EDDSA \ No newline at end of file blob - 1f7c4b790168032cdf15e155f3d3f3419b908ce0 (mode 644) blob + /dev/null --- elpa/denote-2.3.3/.dir-locals.el +++ /dev/null @@ -1,4 +0,0 @@ -;;; Directory Local Variables -;;; For more information see (info "(emacs) Directory Variables") - -((emacs-lisp-mode . ((indent-tabs-mode . nil)))) blob - 619d1542450db444eb03d4af7ce133cd953fdd1b (mode 644) blob + /dev/null --- elpa/denote-2.3.3/CHANGELOG.org +++ /dev/null @@ -1,4558 +0,0 @@ -#+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 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 - 5b040d3ee212f49e6d097d3d83df82e81933b798 (mode 644) blob + /dev/null --- elpa/denote-2.3.3/README-elpa +++ /dev/null @@ -1,5813 +0,0 @@ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - 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 2.3.0, -released on 2024-03-24. 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 3.0.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. Overview -3. 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-buffer-after-creation' option -..... 6. The `denote-date-prompt-use-org-read-date' option -.. 2. Create a note from the current Org subtree -.. 3. Create note using Org capture -.. 4. Create note with specific prompts using Org capture -.. 5. Create a note with the region’s contents -.. 6. Open an existing note or create it if missing -.. 7. Maintain separate directory silos for notes -..... 1. Use custom commands to select a silo -..... 2. The `denote-silo-extras.el' -.. 8. Exclude certain directories from all operations -.. 9. Exclude certain keywords from being inferred -.. 10. Use Denote commands from the menu bar or context menu -4. Renaming files -.. 1. Rename a single file -..... 1. The `denote-rename-no-confirm' 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 keywords interactively -.. 8. Rename a file by adding or removing a signature interactively -.. 9. Faces used by rename commands -5. The file-naming scheme -.. 1. Sluggification of file name components -.. 2. User-defined sluggification of file name components -.. 3. Features of the file-naming scheme for searching or filtering -6. Front matter -.. 1. Change the front matter format -.. 2. Regenerate front matter -7. Linking notes -.. 1. Adding a single link -.. 2. The `denote-org-store-link-to-heading' user option -.. 3. Insert link to an Org file with a further pointer to a heading -.. 4. Insert links matching a regexp -.. 5. Insert link to file with signature -.. 6. Insert links from marked files in Dired -.. 7. Link to an existing note or create a new one -.. 8. The backlinks’ buffer -.. 9. Writing metanotes -.. 10. Visiting linked files via the minibuffer -.. 11. Convert `denote:' links to `file:' links -.. 12. Miscellaneous information about links -..... 1. Aliases for the linking commands -..... 2. The `denote-link-description-function' to format links -8. Choose which commands to prompt for -9. Fontification in Dired -10. Automatically rename Denote buffers -.. 1. The `denote-rename-buffer-format' option -11. Use Org dynamic blocks -.. 1. Org dynamic blocks to insert links or backlinks -.. 2. Org dynamic block to insert file contents -12. Sort files by component -13. Keep a journal or diary -.. 1. Journaling with a timer -14. Minibuffer histories -15. Extending Denote -.. 1. Create a new note in any directory -.. 2. Narrow the list of files in Dired -.. 3. Use `dired-virtual-mode' for arbitrary file listings -.. 4. Use Embark to collect minibuffer candidates -.. 5. Search file contents -.. 6. Bookmark the directory with the notes -.. 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. Treat your notes as a project -.. 12. Use the tree-based file prompt for select commands -.. 13. Rename files with Denote in the Image Dired thumbnails buffer -.. 14. Rename files with Denote using `dired-preview' -.. 15. Avoid duplicate identifiers when exporting Denote notes -..... 1. Export Denote notes with Org Mode -..... 2. Export Denote notes with Markdown -16. Installation -.. 1. GNU ELPA package -.. 2. Manual installation -17. Sample configuration -18. For developers or advanced users -19. Troubleshoot Denote in a pristine environment -20. Contributing -.. 1. Wishlist of what we can do to extend Denote -21. Publications about Denote -22. Alternatives to Denote -.. 1. Alternative implementations and further reading -23. 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. Speed up backlinks’ buffer creation? -.. 10. Why do I get “Search failed with status 1” when I search for backlinks? -.. 11. Why do I get a double `#+title' in Doom Emacs? -24. Acknowledgements -25. GNU Free Documentation License -26. Indices -.. 1. Function index -.. 2. Variable index -.. 3. Concept index - - -1 COPYING -═════════ - - Copyright (C) 2022-2024 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 -══════════ - - 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 5 - -[Renaming files] See section 4 - -[Points of entry] See section 3 - -[Writing metanotes] See section 7.9 - -[Keep a journal or diary] See section 13 - - -3 Points of entry -═════════════════ - - There are five 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. - - -3.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. - - 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). - - 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 5 - -[Front matter] See section 6 - -[The denote-prompts option] See section 3.1.1 - -3.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 to - establish a sequential relationship between files (e.g. 1, 1a, 1b, - 1b1, 1b2, …). Signatures have no strictly defined function and are - up to the user to apply as they see fit. One use-case is to - implement Niklas Luhmann’s Zettelkasten system for a sequence of - notes (Folgezettel). Signatures are not included in a file’s front - matter. They are reserved solely for creating a sequence in a file - listing, at least for the time being. To insert a link that - includes the signature, use the command `denote-link-with-signature' - ([Insert link to file with signature]). - - 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 3.1 - -[Convenience commands for note creation] See section 3.1.4 - -[Renaming files] See section 4 - -[The `denote-history-completion-in-prompts' option] See section 3.1.2 - -[The denote-date-prompt-use-org-read-date option] See section 3.1.6 - -[The denote-templates option] See section 3.1.3 - -[Insert link to file with signature] See section 7.5 - -[The file-naming scheme] See section 5 - - -3.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.] - - -3.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 . 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. Below we show some - concrete examples. - - 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. - - 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 3.1.1 - -[Convenience commands for note creation] See section 3.1.4 - - -3.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 3.1 - -[The denote-prompts option] See section 3.1.1 - -[The denote-date-prompt-use-org-read-date option] See section 3.1.6 - -[The denote-templates option] See section 3.1.3 - -◊ 3.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 equivalent to calling `denote' when `denote-prompts' is - │ set to '(subdirectory title keywords)." - │ (declare (interactive-only t)) - │ (interactive) - │ (let ((denote-prompts '(subdirectory title keywords))) - │ (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. - - Now let’s say we want to have a command that (i) asks for a template - and (ii) for a subdirectory ([The denote-templates option]). 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 denote-subdirectory-with-template () - │ "Create note while also prompting for a template and subdirectory. - │ - │ This is equivalent to calling `denote' when `denote-prompts' is - │ set to '(template subdirectory title keywords)." - │ (declare (interactive-only t)) - │ (interactive) - │ (let ((denote-prompts '(template subdirectory title keywords))) - │ (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]). - - - [Convenience commands for note creation] See section 3.1.4 - - [The denote-templates option] See section 3.1.3 - - [The denote-prompts option] See section 3.1.1 - - [Choose which commands to prompt for] See section 8 - - -3.1.5 The `denote-save-buffer-after-creation' option -╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - - The user option `denote-save-buffer-after-creation' controls whether - commands that creeate 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]). - - If `denote-save-buffer-after-creation' is set to a non-nil value, such - buffers are saved automatically. - - -[Points of entry] See section 3 - -[The `denote-save-buffer-after-creation' option] See section 3.1.5 - - -3.1.6 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 3.1.1 - - -3.2 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-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. - - 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-extras-extract-org-subtree' prompts for - keywords. Else the new note has no keywords ([Add or remove keywords - interactively]). - - 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'. - - -[Add or remove keywords interactively] See section 4.7 - - -3.3 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))) - └──── - - [ In the future, we might develop Denote in ways which do not require - such manual intervention. More user feedback is required to - identify the relevant workflows. ] - - 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 - ([Standard note creation]). - - 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'. - - -[Standard note creation] See section 3.1 - - -3.4 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 a title and keywords. 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)) - └──── - - -[Create note using Org capture] See section 3.3 - -[The file-naming scheme] See section 5 - -[Points of entry] See section 3 - - -3.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. - - -3.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 3.1 - -[Points of entry] See section 3 - -[Link to a note or create it if missing] See section 7.7 - - -3.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. They will not read the global value of - `denote-directory'. The global value of `denote-directory' is read - everywhere else except the silos. - - [Use custom commands to select a silo]. - - 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. - - -[Use custom commands to select a silo] See section 3.7.1 - -3.7.1 Use custom commands to select a silo -╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - - [ As part of version 2.1.0, the contents of this section are formally - provided in the file `denote-silo-extras.el'. We keep this here for - existing users. Otherwise consult the new entry in the manual ([The - `denote-silo-extras.el']). ] - - We implement silos as directory-local values of the user option - `denote-directory'. This means that all Denote commands read from the - local value if they are invoked from that context. For example, if - `~/Videos/recordings' is a silo and `~/Documents/notes' is the - default/global value of `denote-directory' all Denote commands will - read the video’s path when called from there (e.g. by using Emacs’ - `dired'); any other context reads the global value. - - [Maintain separate directory silos for notes]. - - There are cases where the user (i) wants to maintain multiple silos - and (ii) prefers an interactive way to switch between them without - going through Dired. Since this is specific to the user’s workflow, - it is easier to have some custom code for it. The following should be - added to the user’s Denote configuration: - - ┌──── - │ (defvar my-denote-silo-directories - │ `("/home/prot/Videos/recordings" - │ "/home/prot/Documents/books" - │ ;; You don't actually need to include the `denote-directory' here - │ ;; if you use the regular commands in their global context. I am - │ ;; including it for completeness. - │ ,denote-directory) - │ "List of file paths pointing to my Denote silos. - │ This is a list of strings.") - │ - │ (defvar my-denote-commands-for-silos - │ '(denote - │ denote-date - │ denote-subdirectory - │ denote-template - │ denote-type) - │ "List of Denote commands to call after selecting a silo. - │ This is a list of symbols that specify the note-creating - │ interactive functions that Denote provides.") - │ - │ (defun my-denote-pick-silo-then-command (silo command) - │ "Select SILO and run Denote COMMAND in it. - │ SILO is a file path from `my-denote-silo-directories', while - │ COMMAND is one among `my-denote-commands-for-silos'." - │ (interactive - │ (list (completing-read "Select a silo: " my-denote-silo-directories nil t) - │ (intern (completing-read - │ "Run command in silo: " - │ my-denote-commands-for-silos nil t)))) - │ (let ((denote-directory silo)) - │ (call-interactively command))) - └──── - - With this in place, `M-x my-denote-pick-silo-then-command' will use - minibuffer completion to select a silo among the predefined options - and then ask for the command to run in that context. - - Note that `let' binding `denote-directory' can be used in custom - commands and other wrapper functions to override the global default - value of `denote-directory' to select silos. - - To see another example of a wrapper function that `let' binds - `denote-directory', see: - - [Extending Denote: Split an Org subtree into its own note]. - - -[The `denote-silo-extras.el'] See section 3.7.2 - -[Maintain separate directory silos for notes] See section 3.7 - -[Extending Denote: Split an Org subtree into its own note] See section -3.2 - - -3.7.2 The `denote-silo-extras.el' -╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - - The `denote-silo-extras.el' provides optional convenience functions - for working with silos ([Maintain separate directory silos for - notes]). Start by loading the relevant library: - - ┌──── - │ (require 'denote-silo-extras) - └──── - - The user option `denote-silo-extras-directories' specifies a list of - directories that the user has set up as `denote-directory' silos. - - The command `denote-silo-extras-create-note' prompts for a directory - among `denote-silo-extras-directories' and runs the `denote' command - from there. - - Similar to the above, the command `denote-silo-extras-open-or-create' - prompts for a directory among `denote-silo-extras-directories' and - runs the `denote-open-or-create' command from there. - - The command `denote-silo-extras-select-silo-then-command' prompts with - minibuffer completion for a directory among - `denote-silo-extras-directories'. Once the user selects a silo, a - second prompt asks for a Denote note-creation command to call from - inside that silo ([Points of entry]). - - -[Maintain separate directory silos for notes] See section 3.7 - -[Points of entry] See section 3 - - -3.8 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 3.7 - -[For developers or advanced users] See section 18 - - -3.9 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'. - - -3.10 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) - └──── - - -4 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. - - 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]. - - -[The file-naming scheme] See section 5 - -[Linking notes] See section 7 - -[Automatically rename Denote buffers] See section 10 - -4.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'. If a file name component is present, - but there is no entry for it in `denote-prompts', keep it 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, it asks for confirmation, showing the difference - between old and new file names. It does not ask for confirmation if - the user option `denote-rename-no-confirm' is set to a non-nil value - ([The `denote-rename-no-confirm' option]). - - If `FILE' has front matter for `TITLE' and `KEYWORDS', - `denote-rename-file' asks to rewrite their values in order to reflect - the new input, unless `denote-rename-no-confirm' is non-nil. When the - `denote-rename-no-confirm' is nil (the default), the underlying buffer - is not saved, giving the user the change to invoking - `diff-buffer-with-file' to double-check the effect. 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'), the `denote-rename-file' adds front - matter to the top of it and leaves the buffer unsaved for further - inspection. It actually saves the buffer if `denote-rename-no-confirm' - is non-nil ([Front matter]). - - 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 4.3 - -[The `denote-prompts' option] See section 3.1.1 - -[The `denote-rename-no-confirm' option] See section 4.1.1 - -[Front matter] See section 6 - -4.1.1 The `denote-rename-no-confirm' option -╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - - The user option `denote-rename-no-confirm' makes all commands that - rename files not prompt for confirmation and save buffers outright - ([Renaming files]). - - 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. - - 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. It also does not save the affected file’s - buffer to let the user inspect and confirm the changes (such as by - invoking the command `diff-buffer-with-file'). - - With this user option bound to a non-nil value, buffers are saved as - well. The assumption is that the user who opts in to this feature is - familiar with the `denote-rename-file' (or related) operation and - knows it is reliable. - - Specialised 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 4 - -[Rename multiple files interactively] See section 4.3 - - -4.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 - └──── - - - 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. - - If called interactively with a prefix argument (`C-u' by default) or - from Lisp with a non-nil `NO-CONFIRM' argument, this “yes or no” - prompt is skipped and the renaming is done outright. - - If called interactively with a double prefix argument (`C-u C-u' by - default) or from Lisp with a non-nil `SAVE-BUFFER' argument, the - buffer is saved after the front matter is updated and the file is - renamed. - - If the user option `denote-rename-no-confirm' is non-nil, it is - interpreted the same way as a combination of `NO-CONFIRM' and - `SAVE-BUFFER' ([The `denote-rename-no-confirm' 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. - - -[Rename a single file] See section 4.1 - -[Front matter] See section 6 - -[The `denote-rename-no-confirm' option] See section 4.1.1 - - -4.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. 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 4.1 - -[Rename by writing only keywords] See section 4.4 - -[Rename multiple files based on their front matter] See section 4.5 - - -4.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). ] - - -4.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 4.2 - -[Rename multiple files interactively] See section 4.3 - - -4.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 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. - - -4.7 Rename a file by adding or removing keywords interactively -────────────────────────────────────────────────────────────── - - The commands `denote-keywords-add' and `denote-keywords-remove' - streamline the process of interactively updating a file’s keywords in - the front matter and renaming it accordingly. - - The `denote-keywords-add' 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]). - - Similarly, the `denote-keywords-remove' removes one or more keywords - from the list of existing keywords and then renames the file - accordingly. - - Both commands accept an optional prefix argument to automatically save - the buffer. Similarly, they both interpret a non-nil value for the - user option `denote-rename-no-confirm' the same as the prefix argument - ([The `denote-rename-no-confirm' option]). - - Furthermore, both commands call the `denote-after-rename-file-hook' as - a final step after carrying out their task. - - Aliases for these commands are: `denote-rename-add-keywords' and - `denote-rename-remove-keywords'. - - -[Standard note creation] See section 3.1 - -[Rename a single file based on its front matter] See section 4.2 - -[The `denote-rename-no-confirm' option] See section 4.1.1 - - -4.8 Rename a file by adding or removing a signature interactively -───────────────────────────────────────────────────────────────── - - The commands `denote-rename-add-signature' and - `denote-rename-remove-signature' streamline the process of - interactively adding or removing a signature from a given file ([The - file-naming scheme]). - - 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. - - Both commands ask for confirmation before carrying out their action. - They do so unless the user option `denote-rename-no-confirm' is set to - a non-nil value ([The `denote-rename-no-confirm' option]). They also - both take care to reload any Dired buffers and run the - `denote-after-rename-file-hook' as a final step. - - -[The file-naming scheme] See section 5 - -[The `denote-rename-no-confirm' option] See section 4.1.1 - - -4.9 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' - - -5 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 a string of alphanumeric characters in the - `SIGNATURE' field. Signatures have no clearly defined purpose and are - up to the user to define. One use-case is to use them to establish - sequential relations between files (e.g. 1, 1a, 1b, 1b1, 1b2, …). - - Signatures are an optional extension to Denote’s file-naming scheme. - 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 `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. - - 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 3 - -[Sluggification of file name components] See section 5.1 - -[Features of the file-naming scheme for searching or filtering] See -section 5.3 - -5.1 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. The constant - `denote-excluded-punctuation-regexp' holds the relevant value. - - ⁃ 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 6 - -[Features of the file-naming scheme for searching or filtering] See -section 5.3 - -[User-defined sluggification of file name components] See section 5.2 - - -5.2 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 (denote--slug-no-punct 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 (denote--slug-no-punct 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. - - 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 5.1 - -[The file-naming scheme] See section 5 - - -5.3 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. - - -6 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'.. - - -6.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. - - -6.2 Regenerate front matter -─────────────────────────── - - Sometimes the user needs to produce new front matter for an existing - note. Perhaps because they accidentally deleted a line and could not - undo the operation. The command `denote-add-front-matter' can be used - for this very purpose. - - In interactive use, `denote-add-front-matter' must be invoked from a - buffer that visits a Denote note. It prompts for a title and then for - keywords. These are the standard prompts we already use for note - creation, so the keywords’ prompt allows minibuffer completion and the - input of multiple entries, each separated by a comma ([Points of - entry]). - - The newly created front matter is added to the top of the file. - - This command does not rename the file (e.g. to update the keywords). - To rename a file by reading its front matter as input, the user can - rely on `denote-rename-file-using-front-matter' ([Renaming files]). - - Note that `denote-add-front-matter' is useful only for existing Denote - notes. If the user needs to convert a generic text file to a Denote - note, they can use one of the command which first rename the file to - make it comply with our file-naming scheme and then add the relevant - front matter. - - -[Points of entry] See section 3 - -[Renaming files] See section 4 - - -7 Linking notes -═══════════════ - - Denote offers several commands for linking between notes. - - All links target files which are Denote files. This means that they - have our file-naming scheme. Files need to be inside the - `denote-directory' or one of its subdirectories. No other file is - recognised. - - The following sections delve into the details. - - -7.1 Adding a single link -──────────────────────── - - The `denote-link' command inserts a link at point to a file specified - at the minibuffer prompt ([The `denote-org-store-link-to-heading' user - option]). 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]]'. The user - might prefer its simplicity. - - By default, the description of the link is taken from the signature of - the file, if present, and the target file’s front matter’s title or, - if that is not available, from the file name. If the region is - active, its text is used as the link’s description instead. If the - active region has no text, the inserted link uses just the identifier, - as with the `C-u' prefix mentioned above. - - Inserted links are automatically buttonized and remain active for as - long as the buffer is available. In Org this is handled by the major - mode: the `denote:' hyperlink type works exactly like the standard - `file:'. In Markdown and plain text, Denote performs the - buttonization of those links. To buttonize links in existing files - while visiting them, the user must add this snippet to their setup (it - already excludes Org): - - ┌──── - │ (add-hook 'find-file-hook #'denote-link-buttonize-buffer) - └──── - - The `denote-link-buttonize-buffer' is also an interactive function in - case the user needs it. - - Links are created only for files which qualify as a “note” for our - purposes ([Linking notes]). - - Links are styled with the `denote-faces-link' face, which looks - exactly like an ordinary link by default. This is just a convenience - for the user/theme in case they want `denote:' links to remain - distinct from other links. - - 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 aforementioned - buttonization. Interested users can refer to the function - `denote-link-markdown-follow' for the implementation details. - - -[The `denote-org-store-link-to-heading' user option] See section 7.2 - -[Linking notes] See section 7 - - -7.2 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 (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 versions prior to `2.3.0'. - - Note that the optional extension `denote-org-extras.el' defines the - command `denote-org-extras-link-to-heading', which always links to a - file+heading regardless of the aforementioned user option ([Insert - link to an Org file with a further pointer to a 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. ] - - -[Insert link to an Org file with a further pointer to a heading] See -section 7.3 - - -7.3 Insert link to an Org file with a further pointer to a heading -────────────────────────────────────────────────────────────────── - - As part of the optional `denote-org-extras.el' extension, 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 ([The file-naming scheme]). - - This feature is similar to the concept of the user option - `denote-org-store-link-to-heading' ([The - `denote-org-store-link-to-heading' user option]). 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. - - -[The file-naming scheme] See section 5 - -[The `denote-org-store-link-to-heading' user option] See section 7.2 - - -7.4 Insert links matching a regexp -────────────────────────────────── - - The command `denote-add-links' adds links at point matching a regular - expression or plain string. The links 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]). - - -[Linking notes] See section 7 - - -7.5 Insert link to file with signature -────────────────────────────────────── - - The command `denote-link-with-signature' prompts for a file among - those that contain a `==SIGNATURE' and inserts a link to it. The - description of the link includes the text of the signature and that of - the file’s title, if any. For example, a link to the following file: - - ┌──── - │ 20230925T144303==abc--my-first-signature-note__denote_testing.txt - └──── - - - will get this link: `[[denote:20230925T144303][abc My first signature - note]]'. - - For more advanced uses, refer to the doc string of the `denote-link' - function. - - -7.6 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]). - - -[Insert links matching a regexp] See section 7.4 - -[Adding a single link] See section 7.1 - -[Linking notes] See section 7 - - -7.7 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. - - `denote-link-or-create-with-command' - This 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. - - 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 7 - -[Standard note creation] See section 3.1 - -[Adding a single link] See section 7.1 - -[The `denote-save-buffer-after-creation' option] See section 3.1.5 - -[Points of entry] See section 3 - -[Points of entry] See section 3 - -[Open an existing note or create it if missing] See section 3.6 - - -7.8 The backlinks’ buffer -───────────────────────── - - The command `denote-backlinks' produces a bespoke buffer which - displays backlinks to the current note. A “backlink” is a link back - to the present entry. - - By default, the backlinks’ buffer is designed to display the file name - of the note linking to the current entry. Each file name is presented - on its own line, like this: - - ┌──── - │ Backlinks to "On being honest" (20220614T130812) - │ ------------------------------------------------ - │ - │ 20220614T145606--let-this-glance-become-a-stare__journal.txt - │ 20220616T182958--feeling-butterflies-in-your-stomach__journal.txt - └──── - - When the user option `denote-backlinks-show-context' is non-nil, the - backlinks’ buffer displays the line on which a link to the current - note occurs. It also shows multiple occurrences, if present. It - looks like this (and has 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. - - [Speed up backlinks’ buffer creation?] - - The backlinks’ buffer runs the major-mode `denote-backlinks-mode'. It - binds keys to move between links with `n' (next) and `p' (previous). - These are stored in the `denote-backlinks-mode-map' (use `M-x - describe-mode' (`C-h m') in an unfamiliar buffer to learn more about - it). When the user option `denote-backlinks-show-context' is non-nil, - all relevant Xref key bindings are fully functional: again, check - `describe-mode'. - - The backlinking facility uses Emacs’ built-in Xref infrastructure. 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?] - - 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]). - - The placement of the backlinks’ buffer is subject to the user option - `denote-link-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. The doc string of our user option - includes a sample configuration that places the buffer in a left side - window instead. Reproducing it here for the sake of convenience: - - ┌──── - │ (setq denote-link-backlinks-display-buffer-action - │ '((display-buffer-reuse-window - │ display-buffer-in-side-window) - │ (side . left) - │ (slot . 99) - │ (window-width . 0.3))) - └──── - - -[Speed up backlinks’ buffer creation?] See section 23.9 - -[Why do I get “Search failed with status 1” when I search for -backlinks?] See section 23.10 - -[Visiting linked files via the minibuffer] See section 7.10 - - -7.9 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 7.4 - -[Insert links from marked files in Dired] See section 7.6 - - -7.10 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 15 - -[The backlinks’ buffer] See section 7.8 - - -7.11 Convert `denote:' links to `file:' links -───────────────────────────────────────────── - - 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 - ([Insert link to an Org file with a further pointer to a - heading]). - - 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. - - -[Insert link to an Org file with a further pointer to a heading] See -section 7.3 - - -7.12 Miscellaneous information about links -────────────────────────────────────────── - -7.12.1 Aliases for the linking commands -╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - - For convenience, the `denote-link' command has an alias called - `denote-insert-link'. The `denote-backlinks' can also be used as - `denote-show-backlinks-buffer'. While `denote-add-links' is aliased - `denote-link-insert-links-matching-regexp'. The purpose of these - aliases is to offer alternative, more descriptive names of select - commands. - - -7.12.2 The `denote-link-description-function' to format links -╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - - The user option `denote-link-description-function' takes as its value - the symbol of a function. This is used to format the text of the link. - The default function inserts the title. If the file has a signature, - it includes that as well, prepending it to the title. - - The function specified accepts a single `FILE' argument and returns - the description as a string. - - -8 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 3.6 - -[Link to a note or create it if missing] See section 7.7 - -[Points of entry] See section 3 - -[Write your own convenience commands] See section 3.1.4.1 - - -9 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 5 - - -10 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 3 - -[The file-naming scheme] See section 5 - -[The denote-rename-buffer-format] See section 10.1 - -[Renaming files] See section 4 - -10.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' of the file. - • The `%i' is the Denote `IDENTIFIER' of the file. - • 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 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: - - ┌──── - │ ;; 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") - └──── - - 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 5 - - -11 Use Org dynamic blocks -═════════════════════════ - - [ As part of version 2.3.0, all dynamic blocks are defined in the file - `denote-org-extras.el'. The file which was once called - `denote-org-dblock.el' contains 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. ] - - 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 ([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 11.1 - -[Org dynamic block to insert file contents] See section 11.2 - -[Writing metanotes] See section 7.9 - -11.1 Org dynamic blocks to insert links or backlinks -──────────────────────────────────────────────────── - - [ As part of version 2.3.0, all dynamic blocks are defined in the file - `denote-org-extras.el'. The file which was once called - `denote-org-dblock.el' contains 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. ] - - The `denote-links' block can be inserted at point with the command - `denote-org-extras-dblock-insert-links' or by manually including the - following in an Org file: - - ┌──── - │ #+BEGIN: denote-links :regexp "YOUR REGEXP HERE" :sort-by-component nil :reverse-sort nil :id-only nil - │ - │ #+END: - └──── - - - 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 `denote-add-links' command - ([Insert links matching a regexp]). 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 `: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-extras-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 ([Linking - notes]). - - • 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). - - The same as above except for the `:regexp' parameter are true for the - `denote-backlinks' block. The block can be inserted at point with the - command `denote-org-extras-dblock-insert-backlinks' or by manually - writing this in an Org file: - - ┌──── - │ #+BEGIN: denote-backlinks :sort-by-component nil :reverse-sort nil :id-only nil - │ - │ #+END: - └──── - - - Finally, the `denote-missing-links' block is available with the - command `denote-org-extras-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. 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. - - -[Insert links matching a regexp] See section 7.4 - -[Linking notes] See section 7 - - -11.2 Org dynamic block to insert file contents -────────────────────────────────────────────── - - [ As part of version 2.3.0, all dynamic blocks are defined in the file - `denote-org-extras.el'. The file which was once called - `denote-org-dblock.el' contains 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. ] - - 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 ([Insert links matching a - regexp]). - - 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-extras-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" - │ - │ #+END: - └──── - - - To fully control the output, include these additional optional - parameters, which are described further below: - - ┌──── - │ #+BEGIN: denote-files :regexp "YOUR REGEXP HERE" :sort-by-component nil :reverse-sort nil :no-front-matter nil :file-separator nil :add-links nil - │ - │ #+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 `: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-extras-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-extras-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 - ([Linking notes]). 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 11 - -[Insert links matching a regexp] See section 7.4 - -[Org dynamic blocks to insert links or backlinks] See section 11.1 - -[Linking notes] See section 7 - - -12 Sort files by component -══════════════════════════ - - The `denote-sort.el' file is an optional extension to the core - functionality of Denote, which empowers users to sort files by the - given file name component ([The file-naming scheme]). - - The command `denote-sort-dired' produces a Dired file listing with a - flat, filtered, and sorted set of files from the `denote-directory'. - It does so by means of three minibuffer prompts: - - 1. It first asks for a regular expression with which to match Denote - files. Remember that due to Denote’s efficient file-naming scheme, - you do not need to write some complex regular expression. Even - 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'. - 3. Finally, it asks a “yes or no” on whether to reverse the sort - order. - - The resulting Dired listing is a regular Dired buffer, unlike that of - `dired-virtual-mode' ([Use `dired-virtual-mode' for arbitrary file - listings]). - - The dynamic Org blocks that Denote defines to insert file contents - also use this feature ([Org dynamic block to insert file contents]). - - DEVELOPMENT NOTE as of 2023-11-30 07:24 +0200: The sort mechanism will - be incorporated in more functions on a case-by-case basis, subject to - user feedback. For the time being, I am not documenting the functions - that are only accessed from Lisp. Do not hesitate to contact me - (Protesilaos) in person or on one of the development sources (mailing - list or GitHub/GitLab mirror) if you have a use-case where sorting - seems useful. I am happy to help but do not want to roll this feature - everywhere before eliciting relevant feedback: once we add it, we are - not going back. - - -[The file-naming scheme] See section 5 - -[Use `dired-virtual-mode' for arbitrary file listings] See section 15.3 - -[Org dynamic block to insert file contents] See section 11.2 - - -13 Keep a journal or diary -══════════════════════════ - - Denote provides a general-purpose mechanism to create new files that - broadly count as “notes” ([Points of entry]). Such files can be daily - entries in a journal. While it is possible to use the generic - `denote' command to maintain a journal, we provide an optional set of - convenience options and commands as part of - `denote-journal-extras.el'. To use those, add the following the - Denote configuration: - - ┌──── - │ (require 'denote-journal-extras) - └──── - - The command `denote-journal-extras-new-entry' creates a new entry in - the journal. Such a file has the `denote-journal-extras-keyword', - which is `journal' by default ([The file-naming scheme]). The user - can set this keyword to an arbitrary string (single word is - preferred). New journal entries can be stored in the - `denote-directory' or subdirectory thereof. To make it easier for the - user, the new journal entry will be placed in - `denote-journal-extras-directory', which defaults to a subdirectory of - `denote-directory' called `journal'. - - If `denote-journal-extras-directory' is nil, the `denote-directory' is - used. Journal entries will thus be in a flat listing together with - all other notes. They can still be retrieved easily by searching for - the `denote-journal-extras-keyword' ([Features of the file-naming - scheme for searching or filtering]). - - Furthermore, the command `denote-journal-extras-new-entry' will use - the current date as the title of the new entry. The exact format is - controlled by the user option `denote-journal-extras-title-format'. - Acceptable values for `denote-journal-extras-title-format' and their - corresponding styles are: - - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - Symbol Style - ──────────────────────────────────────────────────────────── - day Monday - day-date-month-year Monday 19 September 2023 - day-date-month-year-24h Monday 19 September 2023 20:49 - day-date-month-year-12h Monday 19 September 2023 08:49 PM - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - For example: - - ┌──── - │ (setq denote-journal-extras-title-format 'day-date-month-year) - └──── - - If the value of this user option is `nil', then - `denote-journal-extras-new-entry' will prompt for a title. - - The `denote-journal-extras-new-entry' command also accepts an optional - `DATE' argument. When called internactively, this is a universal - prefix (e.g. `C-u' with the default key bindings). With `DATE', it - prompts for a date to create a new journal entry for. The date prompt - can optionally use the Org date+calendar selection interface ([The - `denote-date-prompt-use-org-read-date' option]). - - In terms of workflow, using the current date as the title is better - for maintaining a daily journal. A prompt for an arbitrary title is - more suitable for those who like to keep a record of something like a - thought or event (though this can also be achieved by the regular - `denote' command or maybe `denote-subdirectory'). - - The `denote-journal-extras-new-entry' command calls the normal hook - `denote-journal-extras-hook' after it is done. The user can leverage - this to produce consequences therefrom, such as to set a timer with - the `tmr' package from GNU ELPA ([Journaling with a timer]). - - The command `denote-journal-extras-new-or-existing-entry' locates an - existing journal entry or creates a new one. A journal entry is one - that has `denote-journal-extras-keyword' as part of its file name. If - there are multiple journal entries for the current date, it prompts - for one among them using minibuffer completion. If there is only one, - it visits it outright. If there is no journal entry, it creates one - by calling `denote-journal-extra-new-entry' (as described above). - - 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. - - -[Points of entry] See section 3 - -[The file-naming scheme] See section 5 - -[Features of the file-naming scheme for searching or filtering] See -section 5.3 - -[The `denote-date-prompt-use-org-read-date' option] See section 3.1.6 - -[Journaling with a timer] See section 13.1 - -13.1 Journaling with a timer -──────────────────────────── - - [ Revised as part of version 2.1.0 to conform with how we now tend to - the needs of users who use Denote for journaling purposes ([Keep a - journal or diary]). ] - - Sometimes journaling is done with the intent to hone one’s writing - skills. Perhaps you are learning a new language or wish to - communicate your ideas with greater clarity and precision. As with - everything that requires a degree of sophistication, you have to work - for it—write, write, write! - - One way to test your progress is to set a timer. It helps you gauge - your output and its quality. To use a timer with Emacs, consider the - `tmr' package. A new timer can be set with something like this: - - ┌──── - │ ;; Set 10 minute timer with the given description - │ (tmr "10" "Practice writing in my journal") - └──── - - To make this timer start as soon as a new journal entry is created - with the command `denote-journal-extras-new-entry', add a function to - the `denote-journal-extras-hook'. For example: - - ┌──── - │ ;; Add an anonymous function, which is more difficult to modify after - │ ;; the fact: - │ (add-hook 'denote-journal-extras-hook (lambda () - │ (tmr "10" "Practice writing in my journal"))) - │ - │ ;; Or write a small function that you can then modify without - │ ;; revaluating the hook: - │ (defun my-denote-tmr () - │ (tmr "10" "Practice writing in my journal")) - │ - │ (add-hook 'denote-journal-extras-hook 'my-denote-tmr) - │ - │ ;; Or to make it fully featured, define variables for the duration and - │ ;; the description and set it up so that you only need to modify - │ ;; those: - │ (defvar my-denote-tmr-duration "10") - │ - │ (defvar my-denote-tmr-description "Practice writing in my journal") - │ - │ (defun my-denote-tmr () - │ (tmr my-denote-tmr-duration my-denote-tmr-description)) - │ - │ (add-hook 'denote-journal-extras-hook 'my-denote-tmr) - └──── - - Once the timer elapses, stop writing and review your performance. - Practice makes perfect! - - Sources for `tmr': - - ⁃ Package name (GNU ELPA): `tmr' - ⁃ Official manual: - ⁃ Change log: - ⁃ Git repo on SourceHut: - • Mirrors: - ⁃ GitHub: - ⁃ GitLab: - ⁃ Mailing list: - - -[Keep a journal or diary] See section 13 - - -14 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) - └──── - - -15 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. This section covers - the details. - - -15.1 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))) - └──── - - -15.2 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 5.3 - - -15.3 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 -23.7 - -[The file-naming scheme] See section 5 - - -15.4 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 15.2 - - -15.5 Search file contents -───────────────────────── - - 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. - - -15.6 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. - - -15.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: - . - - -15.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: - . - - -15.9 Use the `consult-notes' package -──────────────────────────────────── - - 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]). - - -[Features of the file-naming scheme for searching or filtering] See -section 5.3 - - -15.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] - - - -15.11 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). - - -15.12 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))) - └──── - - -15.13 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 15.14 - -[Rename multiple files at once] See section 4.3 - - -15.14 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 4.3 - -[Rename files with Denote in the Image Dired thumbnails buffer] See -section 15.13 - - -15.15 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 3.1.4 - -15.15.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. - - Denote also provides commands to convert `denote:' links to their - `file:' equivalent, in case this is a required pre-processing step for - export purposes ([Convert `denote:' links to `file:' links]). - - -[Convert `denote:' links to `file:' links] See section 7.11 - -◊ 15.15.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 15.15 - - -◊ 15.15.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 3.8 - - -◊ 15.15.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 3.8 - - -15.15.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. - - -16 Installation -═══════════════ - - - - -16.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: - . - - -16.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://git.sr.ht/~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. - - -17 Sample configuration -═══════════════════════ - - ┌──── - │ (require 'denote) - │ - │ ;; Remember to check the doc strings of those variables. - │ (setq denote-directory (expand-file-name "~/Documents/notes/")) - │ (setq denote-save-buffer-after-creation nil) - │ (setq denote-known-keywords '("emacs" "philosophy" "politics" "economics")) - │ (setq denote-infer-keywords t) - │ (setq denote-sort-keywords t) - │ (setq denote-file-type nil) ; Org is the default, set others here - │ (setq denote-prompts '(title keywords)) - │ (setq denote-excluded-directories-regexp nil) - │ (setq denote-excluded-keywords-regexp nil) - │ (setq denote-rename-no-confirm nil) ; Set to t if you are familiar with `denote-rename-file' - │ - │ ;; Pick dates, where relevant, with Org's advanced interface: - │ (setq denote-date-prompt-use-org-read-date t) - │ - │ - │ ;; Read this manual for how to specify `denote-templates'. We do not - │ ;; include an example here to avoid potential confusion. - │ - │ - │ (setq denote-date-format nil) ; read doc string - │ - │ ;; By default, we do not show the context of links. We just display - │ ;; file names. This provides a more informative view. - │ (setq denote-backlinks-show-context t) - │ - │ ;; Also see `denote-link-backlinks-display-buffer-action' which is a bit - │ ;; advanced. - │ - │ ;; If you use Markdown or plain text files (Org renders links as buttons - │ ;; right away) - │ (add-hook 'find-file-hook #'denote-link-buttonize-buffer) - │ - │ ;; 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/books"))) - │ - │ ;; Generic (great if you rename files Denote-style in lots of places): - │ ;; (add-hook 'dired-mode-hook #'denote-dired-mode) - │ ;; - │ ;; OR if only want it in `denote-dired-directories': - │ (add-hook 'dired-mode-hook #'denote-dired-mode-in-directories) - │ - │ - │ ;; Automatically rename Denote buffers using the `denote-rename-buffer-format'. - │ (denote-rename-buffer-mode 1) - │ - │ ;; Denote DOES NOT define any key bindings. This is for the user to - │ ;; decide. For example: - │ (let ((map global-map)) - │ (define-key map (kbd "C-c n n") #'denote) - │ (define-key map (kbd "C-c n c") #'denote-region) ; "contents" mnemonic - │ (define-key map (kbd "C-c n N") #'denote-type) - │ (define-key map (kbd "C-c n d") #'denote-date) - │ (define-key map (kbd "C-c n z") #'denote-signature) ; "zettelkasten" mnemonic - │ (define-key map (kbd "C-c n s") #'denote-subdirectory) - │ (define-key map (kbd "C-c n t") #'denote-template) - │ ;; 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'. - │ (define-key map (kbd "C-c n i") #'denote-link) ; "insert" mnemonic - │ (define-key map (kbd "C-c n I") #'denote-add-links) - │ (define-key map (kbd "C-c n b") #'denote-backlinks) - │ (define-key map (kbd "C-c n f f") #'denote-find-link) - │ (define-key map (kbd "C-c n f b") #'denote-find-backlink) - │ ;; 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'. - │ (define-key map (kbd "C-c n r") #'denote-rename-file) - │ (define-key map (kbd "C-c n R") #'denote-rename-file-using-front-matter)) - │ - │ ;; Key bindings specifically for Dired. - │ (let ((map dired-mode-map)) - │ (define-key map (kbd "C-c C-d C-i") #'denote-link-dired-marked-notes) - │ (define-key map (kbd "C-c C-d C-r") #'denote-dired-rename-files) - │ (define-key map (kbd "C-c C-d C-k") #'denote-dired-rename-marked-files-with-keywords) - │ (define-key map (kbd "C-c C-d C-R") #'denote-dired-rename-marked-files-using-front-matter)) - │ - │ (with-eval-after-load 'org-capture - │ (setq denote-org-capture-specifiers "%l\n%i\n%?") - │ (add-to-list 'org-capture-templates - │ '("n" "New note (with denote.el)" plain - │ (file denote-last-path) - │ #'denote-org-capture - │ :no-save t - │ :immediate-finish nil - │ :kill-buffer t - │ :jump-to-captured t))) - │ - │ ;; Also check the commands `denote-link-after-creating', - │ ;; `denote-link-or-create'. You may want to bind them to keys as well. - │ - │ - │ ;; If you want to have Denote commands available via a right click - │ ;; context menu, use the following and then enable - │ ;; `context-menu-mode'. - │ (add-hook 'context-menu-functions #'denote-context-menu) - └──── - - -18 For developers or advanced users -═══════════════════════════════════ - - Denote is in a stable state and can be relied upon as the basis for - custom extensions. 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. - - 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]). - - Variable `denote-excluded-punctuation-regexp' - Punctionation that is removed from file names. We consider - those characters illegal for our purposes. - - Variable `denote-excluded-punctuation-extra-regexp' - Additional punctuation that is removed from file names. This - variable is for advanced users who need to extend the - `denote-excluded-punctuation-regexp'. Once we have a better - understanding of what we should be omitting, we will update - things accordingly. - - Function `denote-file-is-note-p' - Return non-nil if `FILE' is an actual Denote note. For our - purposes, a note must not 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 - `denote-file-type'. - - Function `denote-file-has-identifier-p' - Return non-nil if `FILE' has a Denote identifier. - - 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'. - - 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]). - - Function `denote-convert-file-name-keywords-to-crm' - Make `STRING' with keywords readable by - `completing-read-multiple'. `STRING' consists of - underscore-separated words, as those appear in the keywords - component of a Denote file name. `STRING' is the same as the - return value of `denote-retrieve-filename-keywords'. - - 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-parse-date' - Return `DATE' as an appropriate value for the `denote' - command. Pass `DATE' through `denote-valid-date-p' and use its - return value. If either that or `DATE' is nil, return - `current-time'. - - 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 only need to have an identifier. The - return value may thus include file types that are not implied by - `denote-file-type'. Remember that the variable - `denote-directory' accepts a dir-local value, as explained in - its doc string ([Maintain separate directories for notes]). 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'. - - 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]). - - 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-get-path-by-id' - Return absolute path of `ID' string in `denote-directory-files'. - - Function `denote-barf-duplicate-id' - Throw a `user-error' if `IDENTIFIER' already exists. - - Function `denote-sluggify' - Make `STR' an appropriate slug for file names and related - ([Sluggification of file name components]). - - 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-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. - - If no file type in `denote-file-types' has the file extension, - the file type is assumed to be the first one in - `denote-file-types'. - - 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 cannot - be nil or an empty string and must match `denote-id-regexp'. - - `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. - - 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, refer to the - `denote-create-unique-file-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'. Try to - find the value of the title in the front matter of FILE, - otherwise use its file name. 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'. `DATE' is parsed by - `denote-valid-date-p'. If `DATE' is nil, use the current time. - - Function `denote-create-unique-file-identifier' - Create a new unique `FILE' identifier. Test that the identifier - is unique among `USED-IDS'. The conditions are as follows: - - • If `DATE' is non-nil, invoke - `denote-prompt-for-date-return-id'. - - • If `DATE' is nil, use the file attributes to determine the - last modified date and format it as an identifier. - - • As a fallback, derive an identifier from the current time. - - With optional `USED-IDS' as nil, test that the identifier is - unique among all files and buffers in variable - `denote-directory'. - - To only return an existing identifier, refer to the function - `denote-retrieve-filename-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. To get a - combined string the way it would appear in a Denote file name, - use `denote-retrieve-front-matter-keywords-value-as-string'. - - Function `denote-retrieve-front-matter-keywords-value-as-string' - Return keywords value from `FILE' front matter per - `FILE-TYPE'. The return value is a string, with the underscrore - as a separator between individual keywords. To get a list of - strings instead, use - `denote-retrieve-front-matter-keywords-value' (the current - function uses that internally). - - Function `denote-retrieve-front-matter-keywords-line' - Return keywords line from `FILE' front matter per `FILE-TYPE'. - - 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 with identifier in variable - `denote-directory'. With optional `FILES-MATCHING-REGEXP', - filter the candidates per the given regular expression. With - optional `PROMPT-TEXT', use it instead of the default “Select - NOTE”. - - 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]). - - 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'). - - Function `denote-rename-file-prompt' - Prompt to rename file named `OLD-NAME' to `NEW-NAME'. - - Function `denote-rename-file-and-buffer' - Rename file named `OLD-NAME' to `NEW-NAME', updating buffer - name. - - 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. With optional - `NO-CONFIRM', do not prompt to confirm the rewriting of the - front matter. Otherwise produce a `y-or-n-p' prompt to that - effect. With optional `NO-CONFIRM', save the buffer after - performing the rewrite. Otherwise leave it unsaved for furthter - review by the user. - - 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]). - - 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'. - - 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]) - - 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-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. - - 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'. - - Variable `denote-link-signature-format' - Format of link description for `denote-link-with-signature'. - - 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. - - -[The file-naming scheme] See section 5 - -[Standard note creation] See section 3.1 - -[Maintain separate directories for notes] See section 3.7 - -[Sluggification of file name components] See section 5.1 - -[The `denote-history-completion-in-prompts' option] See section 3.1.2 - -[The denote-date-prompt-use-org-read-date option] See section 3.1.6 - -[Points of entry] See section 3 - -[Change the front matter format] See section 6.1 - - -19 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. - - -20 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 24 - -20.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-consult.el - This can be a separate package that enhances or replaces the - various prompts we have for files (and maybe more) by using the - `consult' package. Consult provides the preview mechanism and - can probably be used for more things, such as to define a source - for Denote-only buffers in the `consult-buffer' command. If we - need to tweak things in `denote.el', I am happy to do it. For - example, we could have a `denote-file-prompt-function' variable, - which would default to `denote-file-prompt' (what we currently - have) and would also such a hypothetical package to easily plug - into what we have. - - 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). - - Signatures before identifiers - This is probably going to increase the complixity of `denote.el' - and may not be worth pursuing. But just to explore the idea: we - could have an option to rearrange file names such that the - signature appears before the identifier. If we can do this in a - smart way, we can probably extend the principle for all file - name components. Again though, this may be too complex and not - worth doing. - - 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. - - -21 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 20 - - -22 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] - -22.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 22 - -[date2name] - -[filetags] - -[appendfilename] - -[move2archive] - -[blog post] - -[presentation at GLT18] - -[memacs] - - -23 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. - - -23.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. - - -23.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. - - -23.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. - - -23.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). - - -23.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. - - -23.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 20 - - -23.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 12 - - -23.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. - - -23.9 Speed up backlinks’ 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))) - └──── - - -23.10 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] - - -23.11 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. - - -24 Acknowledgements -═══════════════════ - - 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, Adam Růžička, Alan Schmitt, Ashton Wiersdorf, - Benjamin Kästner, Bruno Boal, Charanjit Singh, Clemens - Radermacher, Colin McLear, Damien Cassou, Eduardo Grajeda, Elias - Storms, Eshel Yaron, Florian, Glenna D., Graham Marlow, Hilde - Rhyne, Ivan Sokolov, Jack Baty, Jean-Charles Bagneris, - Jean-Philippe Gagné Guay, Joseph Turner, Jürgen Hötzel, Kaushal - Modi, Kai von Fintel, Kostas Andreadis, Kristoffer Balintona, - Kyle Meyer, Marc Fargas, Matthew Lemon, Noboru Ota (nobiot), - Norwid Behrnd, Peter Prevos, Philip Kaludercic, Quiliro Ordóñez, - Stephen R. Kifer, Stefan Monnier, Stefan Thesing, Thibaut - Benjamin, Tomasz Hołubowicz, Vedang Manerikar, Wesley Harvey, - Zhenxu Xu, arsaber101, ezchi, jarofromel, leinfink (Henrik), - l-o-l-h (Lincoln), mattyonweb, maxbrieiev, mentalisttraceur, - pmenair, relict007. - - Ideas and/or user feedback - Abin Simon, Aditya Yadav, Alan Schmitt, Aleksandr Vityazev, Alex - Hirschfeld, Alfredo Borrás, Ashton Wiersdorf, Benjamin Kästner, - Claudiu Tănăselia, Colin McLear, Damien Cassou, Elias Storms, - Federico Stilman, Florian, Frédéric Willem Frank Ehmsen, Glenna - D., Guo Yong, Hanspeter Gisler, Jack Baty, Jay Rajput, - Jean-Charles Bagneris, Jens Östlund, Jeremy Friesen, Jonathan - Sahar, Johan Bolmsjö, Jousimies, Juanjo Presa, Kai von Fintel, - Kaushal Modi, M. Hadi Timachi, Mark Olson, Mirko Hernandez, - Niall Dooley, Paul van Gelder, Peter Prevos, Peter Smith, Suhail - Singh, Shreyas Ragavan, Stefan Thesing, Summer Emacs, Sven - Seebeck, Taoufik, TJ Stankus, Vick (VicZz), Viktor Haag, Wade - Mealing, Yi Liu, Ypot, atanasj, babusri, doolio, drcxd, - fingerknight, hpgisler, mentalisttraceur, pRot0ta1p, rbenit68, - relict007, 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. - - -25 GNU Free Documentation License -═════════════════════════════════ - - -26 Indices -══════════ - -26.1 Function index -─────────────────── - - -26.2 Variable index -─────────────────── - - -26.3 Concept index -────────────────── blob - 08540f63c17e06cdba24c8ce6c447e6950d89c6e (mode 644) blob + /dev/null --- elpa/denote-2.3.3/README.md +++ /dev/null @@ -1,24 +0,0 @@ -# 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 - 1093db9c23bf95b3bd2b978705bcc473e957138f (mode 644) blob + /dev/null --- elpa/denote-2.3.3/README.org +++ /dev/null @@ -1,5857 +0,0 @@ -#+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 2.3.0 -#+macro: release-date 2024-03-24 -#+macro: development-version 3.0.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-2024 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: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 five 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. - -** 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. - -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). - -#+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 to - establish a sequential relationship between files (e.g. 1, 1a, 1b, - 1b1, 1b2, ...). Signatures have no strictly defined function and - are up to the user to apply as they see fit. One use-case is to - implement Niklas Luhmann's Zettelkasten system for a sequence of - notes (Folgezettel). Signatures are not included in a file's front - matter. They are reserved solely for creating a sequence in a file - listing, at least for the time being. To insert a link that - includes the signature, use the command ~denote-link-with-signature~ - ([[#h:066e5221-9844-474b-8858-398398646f86][Insert link to file with signature]]). - -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 . 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. Below we show some - concrete examples. - -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. - -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 equivalent to calling `denote' when `denote-prompts' is -set to '(subdirectory title keywords)." - (declare (interactive-only t)) - (interactive) - (let ((denote-prompts '(subdirectory title keywords))) - (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. - -Now let's say we want to have a command that (i) asks for a template and -(ii) for a subdirectory ([[#h:f635a490-d29e-4608-9372-7bd13b34d56c][The denote-templates option]]). 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 denote-subdirectory-with-template () - "Create note while also prompting for a template and subdirectory. - -This is equivalent to calling `denote' when `denote-prompts' is -set to '(template subdirectory title keywords)." - (declare (interactive-only t)) - (interactive) - (let ((denote-prompts '(template subdirectory title keywords))) - (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]]). - -*** The ~denote-save-buffer-after-creation~ option -:PROPERTIES: -:CUSTOM_ID: h:bf80f4cd-6f56-4f7c-a991-8573161e4511 -:END: - -#+vindex: denote-save-buffer-after-creation -The user option ~denote-save-buffer-after-creation~ controls whether -commands that creeate 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]]). - -If ~denote-save-buffer-after-creation~ is set to a non-nil value, such -buffers are saved automatically. - -*** 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 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-extras-extract-org-subtree -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. - -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-extras-extract-org-subtree~ prompts for -keywords. Else the new note has no keywords ([[#h:ad4dde4a-8e88-470a-97ae-e7b9d4b41fb4][Add or remove keywords interactively]]). - -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~. - -** 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 - -[ In the future, we might develop Denote in ways which do not require such - manual intervention. More user feedback is required to identify the - relevant workflows. ] - -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 ([[#h:6a92a8b5-d766-42cc-8e5b-8dc255466a23][Standard note creation]]). - -#+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~. - -** 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 a title and keywords. 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 - -** 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. - -** 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. They will not read the global value of ~denote-directory~. -The global value of ~denote-directory~ is read everywhere else except -the silos. - -[[#h:0f72e6ea-97f0-42e1-8fd4-0684af0422e0][Use custom commands to select a silo]]. - -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. - -*** Use custom commands to select a silo -:PROPERTIES: -:CUSTOM_ID: h:0f72e6ea-97f0-42e1-8fd4-0684af0422e0 -:END: - -[ As part of version 2.1.0, the contents of this section - are formally provided in the file =denote-silo-extras.el=. We keep - this here for existing users. Otherwise consult the new entry in - the manual ([[#h:e43baf95-f201-4fec-8620-c0eb5eaa1c85][The =denote-silo-extras.el=]]). ] - -We implement silos as directory-local values of the user option -~denote-directory~. This means that all Denote commands read from the -local value if they are invoked from that context. For example, if -=~/Videos/recordings= is a silo and =~/Documents/notes= is the -default/global value of ~denote-directory~ all Denote commands will -read the video's path when called from there (e.g. by using Emacs' -~dired~); any other context reads the global value. - -[[#h:15719799-a5ff-4e9a-9f10-4ca03ef8f6c5][Maintain separate directory silos for notes]]. - -There are cases where the user (i) wants to maintain multiple silos -and (ii) prefers an interactive way to switch between them without -going through Dired. Since this is specific to the user's workflow, -it is easier to have some custom code for it. The following should be -added to the user's Denote configuration: - -#+begin_src emacs-lisp -(defvar my-denote-silo-directories - `("/home/prot/Videos/recordings" - "/home/prot/Documents/books" - ;; You don't actually need to include the `denote-directory' here - ;; if you use the regular commands in their global context. I am - ;; including it for completeness. - ,denote-directory) - "List of file paths pointing to my Denote silos. - This is a list of strings.") - -(defvar my-denote-commands-for-silos - '(denote - denote-date - denote-subdirectory - denote-template - denote-type) - "List of Denote commands to call after selecting a silo. - This is a list of symbols that specify the note-creating - interactive functions that Denote provides.") - -(defun my-denote-pick-silo-then-command (silo command) - "Select SILO and run Denote COMMAND in it. - SILO is a file path from `my-denote-silo-directories', while - COMMAND is one among `my-denote-commands-for-silos'." - (interactive - (list (completing-read "Select a silo: " my-denote-silo-directories nil t) - (intern (completing-read - "Run command in silo: " - my-denote-commands-for-silos nil t)))) - (let ((denote-directory silo)) - (call-interactively command))) -#+end_src - -With this in place, =M-x my-denote-pick-silo-then-command= will use -minibuffer completion to select a silo among the predefined options -and then ask for the command to run in that context. - -Note that =let= binding ~denote-directory~ can be used in custom -commands and other wrapper functions to override the global default -value of ~denote-directory~ to select silos. - -To see another example of a wrapper function that =let= binds -~denote-directory~, see: - -[[#h:d0c7cb79-21e5-4176-a6af-f4f68578c8dd][Extending Denote: Split an Org subtree into its own note]]. - -*** The =denote-silo-extras.el= -:PROPERTIES: -:CUSTOM_ID: h:e43baf95-f201-4fec-8620-c0eb5eaa1c85 -:END: - -The =denote-silo-extras.el= provides optional convenience functions for -working with silos ([[#h:15719799-a5ff-4e9a-9f10-4ca03ef8f6c5][Maintain separate directory silos for notes]]). -Start by loading the relevant library: - -#+begin_src emacs-lisp -(require 'denote-silo-extras) -#+end_src - -#+vindex: denote-silo-extras-directories -The user option ~denote-silo-extras-directories~ specifies a list of -directories that the user has set up as ~denote-directory~ silos. - -#+findex: denote-silo-extras-create-note -The command ~denote-silo-extras-create-note~ prompts for a directory -among ~denote-silo-extras-directories~ and runs the ~denote~ command -from there. - -#+findex: denote-silo-extras-open-or-create -Similar to the above, the command ~denote-silo-extras-open-or-create~ -prompts for a directory among ~denote-silo-extras-directories~ and runs -the ~denote-open-or-create~ command from there. - -#+findex: denote-silo-extras-select-silo-then-command -The command ~denote-silo-extras-select-silo-then-command~ prompts with -minibuffer completion for a directory among ~denote-silo-extras-directories~. -Once the user selects a silo, a second prompt asks for a Denote -note-creation command to call from inside that silo ([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]). - -** 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~. - -** 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. - -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]]. - -** 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=. If a file -name component is present, but there is no entry for it in -~denote-prompts~, keep it 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, it asks for confirmation, showing the difference -between old and new file names. It does not ask for confirmation if -the user option ~denote-rename-no-confirm~ is set to a non-nil value -([[#h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7][The ~denote-rename-no-confirm~ option]]). - -If =FILE= has front matter for =TITLE= and =KEYWORDS=, -~denote-rename-file~ asks to rewrite their values in order to reflect -the new input, unless ~denote-rename-no-confirm~ is non-nil. When the -~denote-rename-no-confirm~ is nil (the default), the underlying buffer -is not saved, giving the user the change to invoking ~diff-buffer-with-file~ to -double-check the effect. 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~), the ~denote-rename-file~ adds front -matter to the top of it and leaves the buffer unsaved for further -inspection. It actually saves the buffer if ~denote-rename-no-confirm~ -is non-nil ([[#h:13218826-56a5-482a-9b91-5b6de4f14261][Front matter]]). - -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-no-confirm~ option -:PROPERTIES: -:CUSTOM_ID: h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7 -:END: - -#+vindex: denote-rename-no-confirm -The user option ~denote-rename-no-confirm~ makes all commands that -rename files not prompt for confirmation and save buffers outright ([[#h:532e8e2a-9b7d-41c0-8f4b-3c5cbb7d4dca][Renaming files]]). - -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. - -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. It also does not save the affected file's -buffer to let the user inspect and confirm the changes (such as by -invoking the command ~diff-buffer-with-file~). - -With this user option bound to a non-nil value, buffers are saved as -well. The assumption is that the user who opts in to this feature is -familiar with the ~denote-rename-file~ (or related) operation and -knows it is reliable. - -Specialised 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 - -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. - -If called interactively with a prefix argument (=C-u= by default) or -from Lisp with a non-nil =NO-CONFIRM= argument, this "yes or no" -prompt is skipped and the renaming is done outright. - -If called interactively with a double prefix argument (=C-u C-u= by -default) or from Lisp with a non-nil =SAVE-BUFFER= argument, the -buffer is saved after the front matter is updated and the file is -renamed. - -If the user option ~denote-rename-no-confirm~ is non-nil, it is -interpreted the same way as a combination of =NO-CONFIRM= and -=SAVE-BUFFER= ([[#h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7][The ~denote-rename-no-confirm~ 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. - -** 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. 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). ] - -** 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 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. - -** Rename a file by adding or removing keywords interactively -:PROPERTIES: -:CUSTOM_ID: h:ad4dde4a-8e88-470a-97ae-e7b9d4b41fb4 -:END: - -#+findex: denote-keywords-add -#+findex: denote-keywords-remove -The commands ~denote-keywords-add~ and ~denote-keywords-remove~ -streamline the process of interactively updating a file's keywords in -the front matter and renaming it accordingly. - -The ~denote-keywords-add~ 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]]). - -Similarly, the ~denote-keywords-remove~ removes one or more keywords -from the list of existing keywords and then renames the file -accordingly. - -Both commands accept an optional prefix argument to automatically save -the buffer. Similarly, they both interpret a non-nil value for the -user option ~denote-rename-no-confirm~ the same as the prefix argument -([[#h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7][The ~denote-rename-no-confirm~ option]]). - -Furthermore, both commands call the ~denote-after-rename-file-hook~ as -a final step after carrying out their task. - -#+findex: denote-rename-add-keywords -#+findex: denote-rename-remove-keywords -Aliases for these commands are: ~denote-rename-add-keywords~ and -~denote-rename-remove-keywords~. - -** Rename a file by adding or removing a signature interactively -:PROPERTIES: -:CUSTOM_ID: h:b08a350f-b269-47ed-8c2a-b8ecf1b63c7f -:END: - -#+findex: denote-rename-add-signature -#+findex: denote-rename-remove-signature -The commands ~denote-rename-add-signature~ and -~denote-rename-remove-signature~ streamline the process of -interactively adding or removing a signature from a given file ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). - -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. - -Both commands ask for confirmation before carrying out their action. -They do so unless the user option ~denote-rename-no-confirm~ is set to -a non-nil value ([[#h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7][The ~denote-rename-no-confirm~ option]]). They also -both take care to reload any Dired buffers and run the -~denote-after-rename-file-hook~ as a final step. - -** 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 a string of alphanumeric characters in the -=SIGNATURE= field. Signatures have no clearly defined purpose and are up -to the user to define. One use-case is to use them to establish -sequential relations between files (e.g. 1, 1a, 1b, 1b1, 1b2, ...). - -Signatures are an optional extension to Denote's file-naming scheme. -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 =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. - -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. - -** 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. The constant - ~denote-excluded-punctuation-regexp~ holds the relevant value. - -+ 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 (denote--slug-no-punct 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 (denote--slug-no-punct 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. - -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. - -** 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: - -#+findex: denote-add-front-matter -Sometimes the user needs to produce new front matter for an existing -note. Perhaps because they accidentally deleted a line and could not -undo the operation. The command ~denote-add-front-matter~ can be used -for this very purpose. - -In interactive use, ~denote-add-front-matter~ must be invoked from a -buffer that visits a Denote note. It prompts for a title and then for -keywords. These are the standard prompts we already use for note -creation, so the keywords' prompt allows minibuffer completion and the -input of multiple entries, each separated by a comma ([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]). - -The newly created front matter is added to the top of the file. - -This command does not rename the file (e.g. to update the keywords). To -rename a file by reading its front matter as input, the user can rely on -~denote-rename-file-using-front-matter~ ([[#h:532e8e2a-9b7d-41c0-8f4b-3c5cbb7d4dca][Renaming files]]). - -Note that ~denote-add-front-matter~ is useful only for existing Denote -notes. If the user needs to convert a generic text file to a Denote -note, they can use one of the command which first rename the file to -make it comply with our file-naming scheme and then add the relevant -front matter. - -* Linking notes -:PROPERTIES: -:CUSTOM_ID: h:fc913d54-26c8-4c41-be86-999839e8ad31 -:END: - -Denote offers several commands for linking between notes. - -All links target files which are Denote files. This means that they -have our file-naming scheme. Files need to be inside the -~denote-directory~ or one of its subdirectories. No other file is -recognised. - -The following sections delve into the details. - -** Adding a single link -:PROPERTIES: -:CUSTOM_ID: h:5e5e3370-12ab-454f-ba09-88ff44214324 -:END: - -#+findex: denote-link -The ~denote-link~ command inserts a link at point to a file specified -at the minibuffer prompt ([[#h:d99de1fb-b1b7-4a74-8667-575636a4d6a4][The ~denote-org-store-link-to-heading~ user option]]). -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]]=. The user -might prefer its simplicity. - -By default, the description of the link is taken from the signature of -the file, if present, and the target file's front matter's title or, if -that is not available, from the file name. If the region is active, its -text is used as the link's description instead. If the active region -has no text, the inserted link uses just the identifier, as with the -=C-u= prefix mentioned above. - -Inserted links are automatically buttonized and remain active for as -long as the buffer is available. In Org this is handled by the major -mode: the =denote:= hyperlink type works exactly like the standard -=file:=. In Markdown and plain text, Denote performs the buttonization -of those links. To buttonize links in existing files while visiting -them, the user must add this snippet to their setup (it already excludes -Org): - -#+findex: denote-link-buttonize-buffer -#+begin_src emacs-lisp -(add-hook 'find-file-hook #'denote-link-buttonize-buffer) -#+end_src - -The ~denote-link-buttonize-buffer~ is also an interactive function in -case the user needs it. - -Links are created only for files which qualify as a "note" for our -purposes ([[#h:fc913d54-26c8-4c41-be86-999839e8ad31][Linking notes]]). - -#+vindex: denote-faces-link -Links are styled with the ~denote-faces-link~ face, which looks exactly -like an ordinary link by default. This is just a convenience for the -user/theme in case they want =denote:= links to remain distinct from -other links. - -#+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. This method works in addition to -the =RET= key, which is made available by the aforementioned -buttonization. Interested users can refer to the function -~denote-link-markdown-follow~ for the implementation details. - -** 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 (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 versions prior to =2.3.0=. - -Note that the optional extension =denote-org-extras.el= defines the command -~denote-org-extras-link-to-heading~, which always links to a file+heading -regardless of the aforementioned user option ([[#h:fc1ad245-ec08-41be-8d1e-7153d99daf02][Insert link to an Org file with a further pointer to a 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. ] - -** 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-extras-link-to-heading -As part of the optional =denote-org-extras.el= extension, 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 ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). - -This feature is similar to the concept of the user option ~denote-org-store-link-to-heading~ -([[#h:d99de1fb-b1b7-4a74-8667-575636a4d6a4][The ~denote-org-store-link-to-heading~ user option]]). 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. - -** Insert links matching a regexp -:PROPERTIES: -:CUSTOM_ID: h:9bec2c83-36ca-4951-aefc-7187c5463f90 -:END: - -#+findex: denote-add-links -The command ~denote-add-links~ adds links at point matching a -regular expression or plain string. The links 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 link to file with signature -:PROPERTIES: -:CUSTOM_ID: h:066e5221-9844-474b-8858-398398646f86 -:END: - -#+findex: denote-link-with-signature -The command ~denote-link-with-signature~ prompts for a file among -those that contain a ===SIGNATURE= and inserts a link to it. The -description of the link includes the text of the signature and that of -the file's title, if any. For example, a link to the following file: - -: 20230925T144303==abc--my-first-signature-note__denote_testing.txt - -will get this link: =[[denote:20230925T144303][abc My first signature note]]=. - -For more advanced uses, refer to the doc string of the ~denote-link~ -function. - -** 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]]). - -** 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. - -#+findex: denote-link-or-create-with-command -+ ~denote-link-or-create-with-command~ :: This 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. - -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: - -#+findex: denote-backlinks -The command ~denote-backlinks~ produces a bespoke buffer which -displays backlinks to the current note. A "backlink" is a link back -to the present entry. - -By default, the backlinks' buffer is designed to display the file name -of the note linking to the current entry. Each file name is presented -on its own line, like this: - -#+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 - -#+vindex: denote-backlinks-show-context -When the user option ~denote-backlinks-show-context~ is non-nil, the -backlinks' buffer displays the line on which a link to the current -note occurs. It also shows multiple occurrences, if present. It looks -like this (and has 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. - -[[#h:893eec49-d7be-4603-bcff-fcc247244011][Speed up backlinks' buffer creation?]] - -#+findex: denote-backlinks-mode -#+vindex: denote-backlinks-mode-map -The backlinks' buffer runs the major-mode ~denote-backlinks-mode~. It -binds keys to move between links with =n= (next) and =p= (previous). -These are stored in the ~denote-backlinks-mode-map~ (use =M-x -describe-mode= (=C-h m=) in an unfamiliar buffer to learn more about -it). When the user option ~denote-backlinks-show-context~ is non-nil, -all relevant Xref key bindings are fully functional: again, check -~describe-mode~. - -The backlinking facility uses Emacs' built-in Xref infrastructure. 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?]] - -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]]). - -#+vindex: denote-link-backlinks-display-buffer-action -The placement of the backlinks' buffer is subject to the user option -~denote-link-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. The doc string of our user option includes a sample -configuration that places the buffer in a left side window instead. -Reproducing it here for the sake of convenience: - -#+begin_src emacs-lisp -(setq denote-link-backlinks-display-buffer-action - '((display-buffer-reuse-window - display-buffer-in-side-window) - (side . left) - (slot . 99) - (window-width . 0.3))) -#+end_src - -** 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]]). - -** Convert =denote:= links to =file:= links -: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-extras.el= contains two commands -that are relevant for this use-case: - -#+findex: denote-org-extras-convert-links-to-file-type -+ 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 ([[#h:fc1ad245-ec08-41be-8d1e-7153d99daf02][Insert link to an Org file with a further pointer to a heading]]). - -#+findex: denote-org-extras-convert-links-to-denote-type -+ 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. - -** Miscellaneous information about links -:PROPERTIES: -:CUSTOM_ID: h:dd8f2231-8d77-49b9-acc4-af525c68b271 -:END: - -*** Aliases for the linking commands -:PROPERTIES: -:CUSTOM_ID: h:078856d9-f608-43a8-be84-f2cad4c27d0e -:END: - -#+findex: denote-insert-link -#+findex: denote-show-backlinks-buffer -#+findex: denote-link-insert-links-matching-regexp -For convenience, the ~denote-link~ command has an alias called -~denote-insert-link~. The ~denote-backlinks~ can also be used as -~denote-show-backlinks-buffer~. While ~denote-add-links~ is -aliased ~denote-link-insert-links-matching-regexp~. The purpose of -these aliases is to offer alternative, more descriptive names of -select commands. - -*** The ~denote-link-description-function~ to format links -:PROPERTIES: -:CUSTOM_ID: h:f634427c-b451-40e2-993e-e00ac627af68 -:END: - -#+vindex: denote-link-description-function -The user option ~denote-link-description-function~ takes as its value -the symbol of a function. This is used to format the text of the link. -The default function inserts the title. If the file has a signature, -it includes that as well, prepending it to the title. - -The function specified accepts a single =FILE= argument and returns -the description as a string. - -* 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]]): - -- The =%t= is the Denote =TITLE= of the file. -- The =%i= is the Denote =IDENTIFIER= of the file. -- 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 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 -;; 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~. - -* Use Org dynamic blocks -:PROPERTIES: -:CUSTOM_ID: h:8b542c50-dcc9-4bca-8037-a36599b22779 -:END: - -[ As part of version 2.3.0, all dynamic blocks are defined in the file - =denote-org-extras.el=. The file which was once called - =denote-org-dblock.el= contains 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. ] - -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 ([[#h:6060a7e6-f179-4d42-a9de-a9968aaebecc][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 or backlinks -:PROPERTIES: -:CUSTOM_ID: h:50160fae-6515-4d7d-9737-995ad925e64b -:END: - -[ As part of version 2.3.0, all dynamic blocks are defined in the file - =denote-org-extras.el=. The file which was once called - =denote-org-dblock.el= contains 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. ] - -#+findex: denote-org-extras-dblock-insert-links -The =denote-links= block can be inserted at point with the command -~denote-org-extras-dblock-insert-links~ or by manually including the -following in an Org file: - -: #+BEGIN: denote-links :regexp "YOUR REGEXP HERE" :sort-by-component nil :reverse-sort nil :id-only nil -: -: #+END: - -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 ~denote-add-links~ command - ([[#h:9bec2c83-36ca-4951-aefc-7187c5463f90][Insert links matching a regexp]]). 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 =: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-extras-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 ([[#h:fc913d54-26c8-4c41-be86-999839e8ad31][Linking notes]]). - -- 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). - -#+findex: denote-org-extras-dblock-insert-backlinks -The same as above except for the =:regexp= parameter are true for the -=denote-backlinks= block. The block can be inserted at point with the -command ~denote-org-extras-dblock-insert-backlinks~ or by manually writing -this in an Org file: - -: #+BEGIN: denote-backlinks :sort-by-component nil :reverse-sort nil :id-only nil -: -: #+END: - -#+findex: denote-org-extras-dblock-insert-missing-links -Finally, the =denote-missing-links= block is available with the -command ~denote-org-extras-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. 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. - -** Org dynamic block to insert file contents -:PROPERTIES: -:CUSTOM_ID: h:f15fa143-5036-416f-9bff-1bcabbb03456 -:END: - -[ As part of version 2.3.0, all dynamic blocks are defined in the file - =denote-org-extras.el=. The file which was once called - =denote-org-dblock.el= contains 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. ] - -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 ([[#h:9bec2c83-36ca-4951-aefc-7187c5463f90][Insert links matching a regexp]]). - -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-extras-dblock-insert-files -To produce such a block, call the command ~denote-org-extras-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" -: -: #+END: - -To fully control the output, include these additional optional -parameters, which are described further below: - -: #+BEGIN: denote-files :regexp "YOUR REGEXP HERE" :sort-by-component nil :reverse-sort nil :no-front-matter nil :file-separator nil :add-links nil -: -: #+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 =: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-extras-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-extras-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-extras-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 - ([[#h:fc913d54-26c8-4c41-be86-999839e8ad31][Linking notes]]). 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). - -* Sort files by component -:PROPERTIES: -:CUSTOM_ID: h:9fe01e63-f34f-4479-8713-f162a5ca865e -:END: - -The =denote-sort.el= file is an optional extension to the core -functionality of Denote, which empowers users to sort files by the -given file name component ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). - -#+findex: denote-sort-dired -The command ~denote-sort-dired~ produces a Dired file listing with a -flat, filtered, and sorted set of files from the ~denote-directory~. -It does so by means of three minibuffer prompts: - -1. It first asks for a regular expression with which to match Denote - files. Remember that due to Denote's efficient file-naming scheme, - you do not need to write some complex regular expression. Even - 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=. -3. Finally, it asks a "yes or no" on whether to reverse the sort - order. - -The resulting Dired 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]]). - -The dynamic Org blocks that Denote defines to insert file contents -also use this feature ([[#h:f15fa143-5036-416f-9bff-1bcabbb03456][Org dynamic block to insert file contents]]). - -DEVELOPMENT NOTE as of 2023-11-30 07:24 +0200: The sort mechanism will -be incorporated in more functions on a case-by-case basis, subject to -user feedback. For the time being, I am not documenting the functions -that are only accessed from Lisp. Do not hesitate to contact me -(Protesilaos) in person or on one of the development sources (mailing -list or GitHub/GitLab mirror) if you have a use-case where sorting -seems useful. I am happy to help but do not want to roll this feature -everywhere before eliciting relevant feedback: once we add it, we are -not going back. - -* Keep a journal or diary -:PROPERTIES: -:CUSTOM_ID: h:4a6d92dd-19eb-4fcc-a7b5-05ce04da3a92 -:END: - -Denote provides a general-purpose mechanism to create new files that -broadly count as "notes" ([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]). Such files can be daily -entries in a journal. While it is possible to use the generic -~denote~ command to maintain a journal, we provide an optional set of -convenience options and commands as part of =denote-journal-extras.el=. -To use those, add the following the Denote configuration: - -#+begin_src emacs-lisp -(require 'denote-journal-extras) -#+end_src - -#+findex: denote-journal-extras-new-entry -#+vindex: denote-journal-extras-keyword -#+vindex: denote-journal-extras-directory -The command ~denote-journal-extras-new-entry~ creates a new entry in -the journal. Such a file has the ~denote-journal-extras-keyword~, -which is =journal= by default ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). The user can -set this keyword to an arbitrary string (single word is preferred). -New journal entries can be stored in the ~denote-directory~ or -subdirectory thereof. To make it easier for the user, the new journal -entry will be placed in ~denote-journal-extras-directory~, which -defaults to a subdirectory of ~denote-directory~ called =journal=. - -If ~denote-journal-extras-directory~ is nil, the ~denote-directory~ is -used. Journal entries will thus be in a flat listing together with -all other notes. They can still be retrieved easily by searching for -the ~denote-journal-extras-keyword~ ([[#h:1a953736-86c2-420b-b566-fb22c97df197][Features of the file-naming scheme for searching or filtering]]). - -#+vindex: denote-journal-extras-title-format -Furthermore, the command ~denote-journal-extras-new-entry~ will use -the current date as the title of the new entry. The exact format is -controlled by the user option ~denote-journal-extras-title-format~. -Acceptable values for ~denote-journal-extras-title-format~ and their -corresponding styles are: - -| Symbol | Style | -|-------------------------+-----------------------------------| -| day | Monday | -| day-date-month-year | Monday 19 September 2023 | -| day-date-month-year-24h | Monday 19 September 2023 20:49 | -| day-date-month-year-12h | Monday 19 September 2023 08:49 PM | - -For example: - -#+begin_src emacs-lisp -(setq denote-journal-extras-title-format 'day-date-month-year) -#+end_src - -If the value of this user option is ~nil~, then -~denote-journal-extras-new-entry~ will prompt for a title. - -The ~denote-journal-extras-new-entry~ command also accepts an optional -=DATE= argument. When called internactively, this is a universal -prefix (e.g. =C-u= with the default key bindings). With =DATE=, it -prompts for a date to create a new journal entry for. The date prompt -can optionally use the Org date+calendar selection interface -([[#h:e7ef08d6-af1b-4ab3-bb00-494a653e6d63][The ~denote-date-prompt-use-org-read-date~ option]]). - -In terms of workflow, using the current date as the title is better -for maintaining a daily journal. A prompt for an arbitrary title is -more suitable for those who like to keep a record of something like a -thought or event (though this can also be achieved by the regular -~denote~ command or maybe ~denote-subdirectory~). - -#+vindex: denote-journal-extras-hook -The ~denote-journal-extras-new-entry~ command calls the normal hook -~denote-journal-extras-hook~ after it is done. The user can leverage -this to produce consequences therefrom, such as to set a timer with -the ~tmr~ package from GNU ELPA ([[#h:4af1f81e-e93a-43cc-b344-960032a16d42][Journaling with a timer]]). - -#+findex: denote-journal-extras-new-or-existing-entry -The command ~denote-journal-extras-new-or-existing-entry~ locates an -existing journal entry or creates a new one. A journal entry is one -that has ~denote-journal-extras-keyword~ as part of its file name. If -there are multiple journal entries for the current date, it prompts -for one among them using minibuffer completion. If there is only one, -it visits it outright. If there is no journal entry, it creates one -by calling ~denote-journal-extra-new-entry~ (as described above). - -#+findex: denote-journal-extras-link-or-create-entry -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. - -** Journaling with a timer -:PROPERTIES: -:CUSTOM_ID: h:4af1f81e-e93a-43cc-b344-960032a16d42 -:END: - -[ Revised as part of version 2.1.0 to conform with how we - now tend to the needs of users who use Denote for journaling - purposes ([[#h:4a6d92dd-19eb-4fcc-a7b5-05ce04da3a92][Keep a journal or diary]]). ] - -Sometimes journaling is done with the intent to hone one's writing -skills. Perhaps you are learning a new language or wish to communicate -your ideas with greater clarity and precision. As with everything that -requires a degree of sophistication, you have to work for it---write, -write, write! - -One way to test your progress is to set a timer. It helps you gauge -your output and its quality. To use a timer with Emacs, consider the -~tmr~ package. A new timer can be set with something like this: - -#+begin_src emacs-lisp -;; Set 10 minute timer with the given description -(tmr "10" "Practice writing in my journal") -#+end_src - -To make this timer start as soon as a new journal entry is created -with the command ~denote-journal-extras-new-entry~, add a function to -the ~denote-journal-extras-hook~. For example: - -#+begin_src emacs-lisp -;; Add an anonymous function, which is more difficult to modify after -;; the fact: -(add-hook 'denote-journal-extras-hook (lambda () - (tmr "10" "Practice writing in my journal"))) - -;; Or write a small function that you can then modify without -;; revaluating the hook: -(defun my-denote-tmr () - (tmr "10" "Practice writing in my journal")) - -(add-hook 'denote-journal-extras-hook 'my-denote-tmr) - -;; Or to make it fully featured, define variables for the duration and -;; the description and set it up so that you only need to modify -;; those: -(defvar my-denote-tmr-duration "10") - -(defvar my-denote-tmr-description "Practice writing in my journal") - -(defun my-denote-tmr () - (tmr my-denote-tmr-duration my-denote-tmr-description)) - -(add-hook 'denote-journal-extras-hook 'my-denote-tmr) -#+end_src - -Once the timer elapses, stop writing and review your performance. -Practice makes perfect! - -Sources for ~tmr~: - -+ Package name (GNU ELPA): ~tmr~ -+ Official manual: -+ Change log: -+ Git repo on SourceHut: - - Mirrors: - + GitHub: - + GitLab: -+ Mailing list: - -* 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 - -* 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. This section covers the details. - -** 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 - -** 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: - -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. - -** 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: - -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. - -** 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. - -Denote also provides commands to convert =denote:= links to their -=file:= equivalent, in case this is a required pre-processing step for -export purposes ([[#h:ed220cac-7dcb-4bb7-9243-1bb85e452e5f][Convert =denote:= links to =file:= links]]). - -**** 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. - -* 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://git.sr.ht/~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 - -#+begin_src emacs-lisp -(require 'denote) - -;; Remember to check the doc strings of those variables. -(setq denote-directory (expand-file-name "~/Documents/notes/")) -(setq denote-save-buffer-after-creation nil) -(setq denote-known-keywords '("emacs" "philosophy" "politics" "economics")) -(setq denote-infer-keywords t) -(setq denote-sort-keywords t) -(setq denote-file-type nil) ; Org is the default, set others here -(setq denote-prompts '(title keywords)) -(setq denote-excluded-directories-regexp nil) -(setq denote-excluded-keywords-regexp nil) -(setq denote-rename-no-confirm nil) ; Set to t if you are familiar with `denote-rename-file' - -;; Pick dates, where relevant, with Org's advanced interface: -(setq denote-date-prompt-use-org-read-date t) - - -;; Read this manual for how to specify `denote-templates'. We do not -;; include an example here to avoid potential confusion. - - -(setq denote-date-format nil) ; read doc string - -;; By default, we do not show the context of links. We just display -;; file names. This provides a more informative view. -(setq denote-backlinks-show-context t) - -;; Also see `denote-link-backlinks-display-buffer-action' which is a bit -;; advanced. - -;; If you use Markdown or plain text files (Org renders links as buttons -;; right away) -(add-hook 'find-file-hook #'denote-link-buttonize-buffer) - -;; 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/books"))) - -;; Generic (great if you rename files Denote-style in lots of places): -;; (add-hook 'dired-mode-hook #'denote-dired-mode) -;; -;; OR if only want it in `denote-dired-directories': -(add-hook 'dired-mode-hook #'denote-dired-mode-in-directories) - - -;; Automatically rename Denote buffers using the `denote-rename-buffer-format'. -(denote-rename-buffer-mode 1) - -;; Denote DOES NOT define any key bindings. This is for the user to -;; decide. For example: -(let ((map global-map)) - (define-key map (kbd "C-c n n") #'denote) - (define-key map (kbd "C-c n c") #'denote-region) ; "contents" mnemonic - (define-key map (kbd "C-c n N") #'denote-type) - (define-key map (kbd "C-c n d") #'denote-date) - (define-key map (kbd "C-c n z") #'denote-signature) ; "zettelkasten" mnemonic - (define-key map (kbd "C-c n s") #'denote-subdirectory) - (define-key map (kbd "C-c n t") #'denote-template) - ;; 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'. - (define-key map (kbd "C-c n i") #'denote-link) ; "insert" mnemonic - (define-key map (kbd "C-c n I") #'denote-add-links) - (define-key map (kbd "C-c n b") #'denote-backlinks) - (define-key map (kbd "C-c n f f") #'denote-find-link) - (define-key map (kbd "C-c n f b") #'denote-find-backlink) - ;; 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'. - (define-key map (kbd "C-c n r") #'denote-rename-file) - (define-key map (kbd "C-c n R") #'denote-rename-file-using-front-matter)) - -;; Key bindings specifically for Dired. -(let ((map dired-mode-map)) - (define-key map (kbd "C-c C-d C-i") #'denote-link-dired-marked-notes) - (define-key map (kbd "C-c C-d C-r") #'denote-dired-rename-files) - (define-key map (kbd "C-c C-d C-k") #'denote-dired-rename-marked-files-with-keywords) - (define-key map (kbd "C-c C-d C-R") #'denote-dired-rename-marked-files-using-front-matter)) - -(with-eval-after-load 'org-capture - (setq denote-org-capture-specifiers "%l\n%i\n%?") - (add-to-list 'org-capture-templates - '("n" "New note (with denote.el)" plain - (file denote-last-path) - #'denote-org-capture - :no-save t - :immediate-finish nil - :kill-buffer t - :jump-to-captured t))) - -;; Also check the commands `denote-link-after-creating', -;; `denote-link-or-create'. You may want to bind them to keys as well. - - -;; If you want to have Denote commands available via a right click -;; context menu, use the following and then enable -;; `context-menu-mode'. -(add-hook 'context-menu-functions #'denote-context-menu) -#+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. 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. - -#+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]]). - -#+vindex: denote-excluded-punctuation-regexp -+ Variable ~denote-excluded-punctuation-regexp~ :: Punctionation that - is removed from file names. We consider those characters illegal - for our purposes. - -#+vindex: denote-excluded-punctuation-extra-regexp -+ Variable ~denote-excluded-punctuation-extra-regexp~ :: Additional - punctuation that is removed from file names. This variable is for - advanced users who need to extend the ~denote-excluded-punctuation-regexp~. - Once we have a better understanding of what we should be omitting, - we will update things accordingly. - -#+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 not 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 - ~denote-file-type~. - -#+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-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-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]]). - -#+findex: denote-convert-file-name-keywords-to-crm -+ Function ~denote-convert-file-name-keywords-to-crm~ :: Make =STRING= - with keywords readable by ~completing-read-multiple~. =STRING= - consists of underscore-separated words, as those appear in the - keywords component of a Denote file name. =STRING= is the same as - the return value of ~denote-retrieve-filename-keywords~. - -#+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-parse-date -+ Function ~denote-parse-date~ :: Return =DATE= as an appropriate - value for the ~denote~ command. Pass =DATE= through - ~denote-valid-date-p~ and use its return value. If either that or - =DATE= is nil, return ~current-time~. - -#+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 only need to have an - identifier. The return value may thus include file types that are - not implied by ~denote-file-type~. Remember that the variable - ~denote-directory~ accepts a dir-local value, as explained in its - doc string ([[#h:15719799-a5ff-4e9a-9f10-4ca03ef8f6c5][Maintain separate directories for notes]]). 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~. - -#+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]]). - -#+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-get-path-by-id -+ Function ~denote-get-path-by-id~ :: Return absolute path of =ID= - string in ~denote-directory-files~. - -#+findex: denote-barf-duplicate-id -+ Function ~denote-barf-duplicate-id~ :: Throw a ~user-error~ if - =IDENTIFIER= already exists. - -#+findex: denote-sluggify -+ Function ~denote-sluggify~ :: Make =STR= an appropriate slug for - file names and related ([[#h:ae8b19a1-7f67-4258-96b3-370a72c43f4e][Sluggification of file name components]]). - -#+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-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. - - If no file type in ~denote-file-types~ has the file extension, the - file type is assumed to be the first one in ~denote-file-types~. - -#+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 cannot be - nil or an empty string and must match ~denote-id-regexp~. - - =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. - -#+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, refer to the - ~denote-create-unique-file-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=. Try to find the value of the - title in the front matter of FILE, otherwise use its file name. 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~. =DATE= is parsed by - ~denote-valid-date-p~. If =DATE= is nil, use the current time. - -#+findex: denote-create-unique-file-identifier -+ Function ~denote-create-unique-file-identifier~ :: Create a new unique - =FILE= identifier. Test that the identifier is unique among - =USED-IDS=. The conditions are as follows: - - - If =DATE= is non-nil, invoke ~denote-prompt-for-date-return-id~. - - - If =DATE= is nil, use the file attributes to determine the last - modified date and format it as an identifier. - - - As a fallback, derive an identifier from the current time. - - With optional =USED-IDS= as nil, test that the identifier is unique - among all files and buffers in variable ~denote-directory~. - - To only return an existing identifier, refer to the function - ~denote-retrieve-filename-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. To get a combined string the way it would appear in a - Denote file name, use ~denote-retrieve-front-matter-keywords-value-as-string~. - -#+findex: denote-retrieve-front-matter-keywords-value-as-string -+ Function ~denote-retrieve-front-matter-keywords-value-as-string~ :: Return - keywords value from =FILE= front matter per =FILE-TYPE=. The return - value is a string, with the underscrore as a separator between - individual keywords. To get a list of strings instead, use - ~denote-retrieve-front-matter-keywords-value~ (the current function uses that - internally). - -#+findex: denote-retrieve-front-matter-keywords-line -+ Function ~denote-retrieve-front-matter-keywords-line~ :: Return keywords line - from =FILE= front matter per =FILE-TYPE=. - -#+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 with identifier in - variable ~denote-directory~. With optional =FILES-MATCHING-REGEXP=, - filter the candidates per the given regular expression. With - optional =PROMPT-TEXT=, use it instead of the default "Select NOTE". - -#+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]]). - -#+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~). - -#+findex: denote-rename-file-prompt -+ Function ~denote-rename-file-prompt~ :: Prompt to rename file named - =OLD-NAME= to =NEW-NAME=. - -#+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-rewrite-front-matter -+ 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. With optional =NO-CONFIRM=, do not prompt to confirm - the rewriting of the front matter. Otherwise produce a ~y-or-n-p~ - prompt to that effect. With optional =NO-CONFIRM=, save the buffer - after performing the rewrite. Otherwise leave it unsaved for - furthter review by the user. - -#+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]]). - -#+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~. - -#+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]]) - -#+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-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. - -#+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~. - -#+vindex: denote-link-signature-format -+ Variable ~denote-link-signature-format~ :: Format of link - description for ~denote-link-with-signature~. - -#+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. - -* 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-consult.el :: This can be a separate package that enhances or - replaces the various prompts we have for files (and maybe more) by - using the ~consult~ package. Consult provides the preview mechanism - and can probably be used for more things, such as to define a source - for Denote-only buffers in the ~consult-buffer~ command. If we need - to tweak things in =denote.el=, I am happy to do it. For example, we - could have a ~denote-file-prompt-function~ variable, which would - default to ~denote-file-prompt~ (what we currently have) and would - also such a hypothetical package to easily plug into what we have. - -- 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). - -- Signatures before identifiers :: This is probably going to increase - the complixity of =denote.el= and may not be worth pursuing. But - just to explore the idea: we could have an option to rearrange file - names such that the signature appears before the identifier. If we - can do this in a smart way, we can probably extend the principle for - all file name components. Again though, this may be too complex and - not worth doing. - -- 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. - -** Speed up backlinks' 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 :: Abin Simon, Adam Růžička, - Alan Schmitt, Ashton Wiersdorf, Benjamin Kästner, Bruno Boal, - Charanjit Singh, Clemens Radermacher, Colin McLear, Damien Cassou, - Eduardo Grajeda, Elias Storms, Eshel Yaron, Florian, Glenna D., - Graham Marlow, Hilde Rhyne, Ivan Sokolov, Jack Baty, Jean-Charles - Bagneris, Jean-Philippe Gagné Guay, Joseph Turner, Jürgen Hötzel, - Kaushal Modi, Kai von Fintel, Kostas Andreadis, Kristoffer - Balintona, Kyle Meyer, Marc Fargas, Matthew Lemon, Noboru Ota - (nobiot), Norwid Behrnd, Peter Prevos, Philip Kaludercic, Quiliro - Ordóñez, Stephen R. Kifer, Stefan Monnier, Stefan Thesing, Thibaut - Benjamin, Tomasz Hołubowicz, Vedang Manerikar, Wesley Harvey, Zhenxu - Xu, arsaber101, ezchi, jarofromel, leinfink (Henrik), l-o-l-h - (Lincoln), mattyonweb, maxbrieiev, mentalisttraceur, pmenair, - relict007. - -+ Ideas and/or user feedback :: Abin Simon, Aditya Yadav, Alan - Schmitt, Aleksandr Vityazev, Alex Hirschfeld, Alfredo Borrás, Ashton - Wiersdorf, Benjamin Kästner, Claudiu Tănăselia, Colin McLear, Damien - Cassou, Elias Storms, Federico Stilman, Florian, Frédéric Willem - Frank Ehmsen, Glenna D., Guo Yong, Hanspeter Gisler, Jack Baty, Jay - Rajput, Jean-Charles Bagneris, Jens Östlund, Jeremy Friesen, - Jonathan Sahar, Johan Bolmsjö, Jousimies, Juanjo Presa, Kai von - Fintel, Kaushal Modi, M. Hadi Timachi, Mark Olson, Mirko Hernandez, - Niall Dooley, Paul van Gelder, Peter Prevos, Peter Smith, Suhail - Singh, Shreyas Ragavan, Stefan Thesing, Summer Emacs, Sven Seebeck, - Taoufik, TJ Stankus, Vick (VicZz), Viktor Haag, Wade Mealing, Yi - Liu, Ypot, atanasj, babusri, doolio, drcxd, fingerknight, hpgisler, - mentalisttraceur, pRot0ta1p, rbenit68, relict007, 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. - -* 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 - 9eda0122eadfce97592142815119465e8698d34b (mode 644) blob + /dev/null --- elpa/denote-2.3.3/denote-autoloads.el +++ /dev/null @@ -1,990 +0,0 @@ -;;; 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 "denote" "\ -Create a new note with the appropriate metadata and file name. - -Run the `denote-after-new-note-hook' after creating the new note. - -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 `denote-file-type'. - -- SUBDIRECTORY 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 SUBDIRECTORY 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 SUBDIRECTORY 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." 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. - -If TARGET file does not exist, add the user input that was used -to search for it to the minibuffer 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 -\\\\[previous-history-element]. - -(fn TARGET)" t) -(autoload 'denote-open-or-create-with-command "denote" "\ -Visit TARGET file in variable `denote-directory'. -If file does not exist, invoke `denote' to create a file. - -If TARGET file does not exist, add the user input that was used -to search for it to the minibuffer 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 -\\\\[previous-history-element]." 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 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. - -If a file name component is present, but there is no entry for it in -`denote-prompts', keep it 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, read its file type extension (like .org) and -preserve 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-no-confirm' is set to a non-nil -value. - -If FILE has front matter for TITLE and KEYWORDS, ask to rewrite -their values in order to reflect the new input, unless -`denote-rename-no-confirm' is non-nil. When the -`denote-rename-no-confirm' is nil (the default), do not save the -underlying buffer, thus giving the user the option to -double-check the result, such as by invokling 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. Save -the buffer if `denote-rename-no-confirm' is non-nil. - -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' - -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 &optional 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-no-confirm' to a non-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 `denote-file-type'), - such that it includes the new keywords. - -Run the `denote-after-rename-file-hook' after renaming is done. - -[ 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). ]" '(dired-mode)) -(function-put 'denote-dired-rename-marked-files-with-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 return value of the -function `buffer-file-name' 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 `denote-file-type'. - -Unless NO-CONFIRM is non-nil (such as with a prefix argument), -ask for confirmation, showing the difference between the old and -the new file names. - -Never modify the identifier of the FILE, if any, 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. - -If NO-CONFIRM is non-nil (such as with a prefix argument) do not -prompt for confirmation while renaming the file. Do it outright. - -If optional SAVE-BUFFER is non-nil (such as with a double prefix -argument), save the corresponding buffer. - -If the user option `denote-rename-no-confirm' is non-nil, -interpret it the same way as a combination of NO-CONFIRM and -SAVE-BUFFER. - -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. - -(fn FILE &optional NO-CONFIRM SAVE-BUFFER)" 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 `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-keywords-add "denote" "\ -Prompt for KEYWORDS to add to the current note's front matter. -When called from Lisp, KEYWORDS is a list of strings. - -Rename the file without further prompt so that its name reflects -the new front matter, per `denote-rename-file-using-front-matter'. - -With an optional SAVE-BUFFER (such as a prefix argument when -called interactively), save the buffer outright. Otherwise leave -the buffer unsaved for further review. - -If the user option `denote-rename-no-confirm' is non-nil, -interpret it the same way as SAVE-BUFFER, making SAVE-BUFFER -reduntant. - -Run `denote-after-rename-file-hook' as a final step. - -(fn KEYWORDS &optional SAVE-BUFFER)" t) -(autoload 'denote-keywords-remove "denote" "\ -Prompt for keywords in current note and remove them. -Keywords are retrieved from the file's front matter. - -Rename the file without further prompt so that its name reflects -the new front matter, per `denote-rename-file-using-front-matter'. - -With an optional SAVE-BUFFER as a prefix argument, save the -buffer outright. Otherwise leave the buffer unsaved for further -review. - -If the user option `denote-rename-no-confirm' is non-nil, -interpret it the same way as SAVE-BUFFER, making SAVE-BUFFER -reduntant. - -Run `denote-after-rename-file-hook' as a final step. - -(fn &optional SAVE-BUFFER)" t) -(function-put 'denote-keywords-remove 'interactive-only 't) -(autoload 'denote-rename-add-signature "denote" "\ -Add to FILE name the SIGNATURE. -In interactive use, prompt for FILE, defaulting either to the current -buffer's file or the one at point in a Dired buffer. Also prompt for -SIGNATURE, using the existing one, if any, as the initial value. - -When called from Lisp, FILE is a string pointing to a file system path -and SIGNATURE is a string. - -Ask for confirmation before renaming the file to include the new -signature. Do it unless the user option `denote-rename-no-confirm' is -set to a non-nil value. - -Once the operation is done, reload any Dired buffers and run the -`denote-after-rename-file-hook'. - -Also see `denote-rename-remove-signature'. - -(fn FILE SIGNATURE)" t) -(autoload 'denote-rename-remove-signature "denote" "\ -Remove the signature of FILE. -In interactive use, prompt for FILE, defaulting either to the current -buffer's file or the one at point in a Dired buffer. When called from -Lisp, FILE is a string pointing to a file system path. - -Ask for confirmation before renaming the file to remove its signature. -Do it unless the user option `denote-rename-no-confirm' is set to a -non-nil value. - -Once the operation is done, reload any Dired buffers and run the -`denote-after-rename-file-hook'. - -Also see `denote-rename-add-signature'. - -(fn FILE)" t) -(autoload 'denote-add-front-matter "denote" "\ -Insert front matter at the top of FILE. - -When called interactively, FILE is the return value of the -function `buffer-file-name'. FILE is checked to determine -whether it is a note for Denote's purposes. - -TITLE is a string. Interactively, it is the user input at the -minibuffer prompt. - -KEYWORDS is a list of strings. Interactively, it is the user -input at the minibuffer prompt. This one supports completion for -multiple entries, each separated by the `crm-separator' (normally -a comma). - -The purpose of this command is to help the user generate new -front matter for an existing note (perhaps because the user -deleted the previous one and could not undo the change). - -This command does not rename the file (e.g. to update the -keywords). To rename a file by reading its front matter as -input, use `denote-rename-file-using-front-matter'. - -Note that this command is useful only for existing Denote notes. -If the user needs to convert a generic text file to a Denote -note, they can use one of the command which first rename the file -to make it comply with our file-naming scheme and then add the -relevant front matter. - -[ 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-29 09:24 +0200. ] - -(fn FILE TITLE KEYWORDS)" t) -(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 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. - -(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 `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. - -The DESCRIPTION is returned by the function specified in variable -`denote-link-description-function'. If the region is active, its -content is deleted and can be used as the description of the -link. The default value of `denote-link-description-function' -returns the content of the active region, if any, else the title -of the linked file is used as the description. The title comes -either from the front matter or the file name. Note that if you -change the default value of `denote-link-description-function', -make sure to use the `region-text' parameter. Regardless of the -value of this user option, `denote-link' will always replace the -content of the active region. - -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, the link is also as if ID-ONLY were non-nil. The -default value of `denote-link-description-function' returns an -empty string when the region is empty. Thus, the link will have -no description in this case. - -When called from Lisp, FILE is a string representing a full file -system path. FILE-TYPE is a symbol as described in -`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-link-with-signature "denote" "\ -Insert link to file with signature. -Prompt for file using minibuffer completion, limiting the list of -candidates to files with a signature in their file name. - -By default, the description of the link includes the signature, -if present, followed by the file's title, if any. - -For more advanced uses with Lisp, refer to the `denote-link' -function." t) -(function-put 'denote-link-with-signature 'interactive-only 't) -(autoload 'denote-find-link "denote" "\ -Use minibuffer completion to visit linked file." t) -(function-put 'denote-find-link 'interactive-only 't) -(autoload 'denote-find-backlink "denote" "\ -Use minibuffer completion to visit backlink to current file. - -Like `denote-find-link', but select backlink to follow." t) -(function-put 'denote-find-backlink '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. - -If TARGET file does not exist, add the user input that was used -to search for it to the minibuffer 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 -\\\\[previous-history-element]. - -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-link-buttonize-buffer "denote" "\ -Make denote: links actionable buttons in the current buffer. - -Buttonization applies to the plain text and Markdown file types, -per the user option `denote-file-types'. It will not do anything -in `org-mode' buffers, as buttons already work there. If you do -not use Markdown or plain text, then you do not need this. - -Links work when they point to a file inside the variable -`denote-directory'. - -To buttonize links automatically add this function to the -`find-file-hook'. Or call it interactively for on-demand -buttonization. - -When called from Lisp, with optional BEG and END as buffer -positions, limit the process to the region in-between. - -(fn &optional BEG END)" t) -(autoload 'denote-backlinks "denote" "\ -Produce a buffer with backlinks to the current note. - -The backlinks' buffer shows the file name of the note linking to -the current note, as well as the context of each link. - -File names are fontified by Denote if the user option -`denote-link-fontify-backlinks' is non-nil. If this user option -is nil, the buffer is fontified by Xref. - -The placement of the backlinks' buffer is controlled by the user -option `denote-link-backlinks-display-buffer-action'. By -default, it will show up below the current window." t) -(autoload 'denote-add-links "denote" "\ -Insert links to all notes matching 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-dired-marked-notes "denote" "\ -Insert Dired marked FILES as links in BUFFER. - -FILES are Denote notes, meaning that they have our file-naming -scheme, are writable/regular files, and use the appropriate file -type extension (per `denote-file-type'). Furthermore, the marked -files need to be inside the variable `denote-directory' or one of -its subdirectories. No other file is recognised (the list of -marked files ignores whatever does not count as a note for our -purposes). - -The BUFFER is one which visits a Denote note file. If there are -multiple buffers, prompt with completion for one among them. If -there isn't one, 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 -search option akin to that of standard Org `file:' link types. -Read Info node `(org) Search Options'. - -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. -Also see the user option `denote-org-store-link-to-heading'.") -(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-faces-link :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)") -(register-definition-prefixes "denote" '("denote-")) - - -;;; Generated autoloads from denote-journal-extras.el - -(autoload 'denote-journal-extras-new-entry "denote-journal-extras" "\ -Create a new journal entry in variable `denote-journal-extras-directory'. -Use `denote-journal-extras-keyword' as a keyword for the newly -created file. Set the title of the new entry according to the -value of the user option `denote-journal-extras-title-format'. - -With optional DATE as a prefix argument, prompt for a date. If -`denote-date-prompt-use-org-read-date' is non-nil, use the Org -date selection module. - -When called from Lisp DATE is a string and has the same format as -that covered in the documentation of the `denote' function. It -is internally processed by `denote-parse-date'. - -(fn &optional DATE)" t) -(autoload 'denote-journal-extras-new-or-existing-entry "denote-journal-extras" "\ -Locate an existing journal entry or create a new one. -A journal entry is one that has `denote-journal-extras-keyword' as -part of its file name. - -If there are multiple journal entries for the current date, -prompt for one using minibuffer completion. If there is only -one, visit it outright. If there is no journal entry, create one -by calling `denote-journal-extra-new-entry'. - -With optional DATE as a prefix argument, prompt for a date. If -`denote-date-prompt-use-org-read-date' is non-nil, use the Org -date selection module. - -When called from Lisp, DATE is a string and has the same format -as that covered in the documentation of the `denote' function. -It is internally processed by `denote-parse-date'. - -(fn &optional DATE)" t) -(autoload 'denote-journal-extras-link-or-create-entry "denote-journal-extras" "\ -Use `denote-link' on journal entry, creating it if necessary. -A journal entry is one that has `denote-journal-extras-keyword' as -part of its file name. - -If there are multiple journal entries for the current date, -prompt for one using minibuffer completion. If there is only -one, link to it outright. If there is no journal entry, create one -by calling `denote-journal-extra-new-entry' and link to it. - -With optional DATE as a prefix argument, prompt for a date. If -`denote-date-prompt-use-org-read-date' is non-nil, use the Org -date selection module. - -When called from Lisp, DATE is a string and has the same format -as that covered in the documentation of the `denote' function. -It is internally processed by `denote-parse-date'. - -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 &optional DATE ID-ONLY)" t) -(register-definition-prefixes "denote-journal-extras" '("denote-journal-extras-")) - - -;;; Generated autoloads from denote-org-extras.el - -(autoload 'denote-org-extras-link-to-heading "denote-org-extras" "\ -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-extras-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." '(org-mode)) -(function-put 'denote-org-extras-link-to-heading 'interactive-only 't) -(autoload 'denote-org-extras-extract-org-subtree "denote-org-extras" "\ -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 -`denote-file-type'." '(org-mode)) -(autoload 'denote-org-extras-convert-links-to-file-type "denote-org-extras" "\ -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-extras-convert-links-to-denote-type "denote-org-extras" "\ -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-extras-dblock-insert-links "denote-org-extras" "\ -Create Org dynamic block to insert Denote links matching REGEXP. - -(fn REGEXP)" '(org-mode)) -(autoload 'denote-org-extras-dblock-insert-missing-links "denote-org-extras" "\ -Create Org dynamic block to insert Denote links matching REGEXP. - -(fn REGEXP)" '(org-mode)) -(autoload 'denote-org-extras-dblock-insert-backlinks "denote-org-extras" "\ -Create Org dynamic block to insert Denote backlinks to current file." '(org-mode)) -(autoload 'denote-org-extras-dblock-insert-files "denote-org-extras" "\ -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)) -(register-definition-prefixes "denote-org-extras" '("denote-org-extras-" "org-dblock-write:denote-")) - - -;;; Generated autoloads from denote-rename-buffer.el - -(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-rename-buffer" nil) -(autoload 'denote-rename-buffer-mode "denote-rename-buffer" "\ -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-rename-buffer" '("denote-rename-buffer")) - - -;;; Generated autoloads from denote-silo-extras.el - -(autoload 'denote-silo-extras-create-note "denote-silo-extras" "\ -Select SILO and run `denote' in it. -SILO is a file path from `denote-silo-extras-directories'. - -When called from Lisp, SILO is a file system path to a directory. - -(fn SILO)" t) -(autoload 'denote-silo-extras-open-or-create "denote-silo-extras" "\ -Select SILO and run `denote-open-or-create' in it. -SILO is a file path from `denote-silo-extras-directories'. - -When called from Lisp, SILO is a file system path to a directory. - -(fn SILO)" t) -(autoload 'denote-silo-extras-select-silo-then-command "denote-silo-extras" "\ -Select SILO and run Denote COMMAND in it. -SILO is a file path from `denote-silo-extras-directories', while -COMMAND is one among `denote-silo-extras-commands'. - -When called from Lisp, SILO is a file system path to a directory. - -(fn SILO COMMAND)" t) -(register-definition-prefixes "denote-silo-extras" '("denote-silo-extras-")) - - -;;; Generated autoloads from denote-sort.el - -(autoload 'denote-sort-files "denote-sort" "\ -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 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-sort" "\ -Produce Dired dired-buffer with sorted files from variable `denote-directory'. -When called interactively, prompt for FILES-MATCHING-REGEXP, -SORT-BY-COMPONENT, and REVERSE. - -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'). - -3. REVERSE is a boolean to reverse the order when it is a non-nil value. - -When called from Lisp, the arguments are a string, a keyword, and -a non-nil value, respectively. - -(fn FILES-MATCHING-REGEXP SORT-BY-COMPONENT REVERSE)" t) -(register-definition-prefixes "denote-sort" '("denote-sort-")) - -;;; 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 - c017fab2996d7bc0f661798482dd37722c65901a (mode 644) blob + /dev/null --- elpa/denote-2.3.3/denote-journal-extras.el +++ /dev/null @@ -1,249 +0,0 @@ -;;; denote-journal-extras.el --- Convenience functions for daily journaling -*- lexical-binding: t; -*- - -;; Copyright (C) 2023-2024 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: - -;; This is a set of optional convenience functions that used to be -;; provided in the Denote manual. They facilitate the use of Denote -;; for daily journaling. - -;;; Code: - -(require 'denote) - -(defgroup denote-journal-extras nil - "Denote for daily journaling." - :group 'denote - :link '(info-link "(denote) Top") - :link '(url-link :tag "Homepage" "https://protesilaos.com/emacs/denote")) - -(defcustom denote-journal-extras-directory - (expand-file-name "journal" denote-directory) - "Directory for storing daily journal entries. -This can either be the same as the variable `denote-directory' or -a subdirectory of it. - -A value of nil means to use the variable `denote-directory'. -Journal entries will thus be in a flat listing together with all -other notes. They can still be retrieved easily by searching for -the `denote-journal-extras-keyword'." - :group 'denote-journal-extras - :type '(choice (directory :tag "Provide directory path (is created if missing)") - (const :tag "Use the `denote-directory'" nil))) - -(defcustom denote-journal-extras-keyword "journal" - "Single word keyword to tag journal entries. -It is used by `denote-journal-extras-new-entry' to add a keyword -to the newly created file." - :group 'denote-journal-extras - :type 'string) - -(defcustom denote-journal-extras-title-format 'day-date-month-year-24h - "Date format to construct the title with `denote-journal-extras-new-entry'. -The value is either a symbol or an arbitrary string that is -passed to `format-time-string' (consult its documentation for the -technicalities). - -Acceptable symbols and their corresponding styles are: - -| Symbol | Style | -|-------------------------+-----------------------------------| -| day | Monday | -| day-date-month-year | Monday 19 September 2023 | -| day-date-month-year-24h | Monday 19 September 2023 20:49 | -| day-date-month-year-12h | Monday 19 September 2023 08:49 PM | - -With a nil value, make `denote-journal-extras-new-entry' prompt -for a title." - :group 'denote-journal-extras - :type '(choice - (const :tag "Prompt for title with `denote-journal-extras-new-entry'" nil) - (const :tag "Monday" - :doc "The `format-time-string' is: %A" - day) - (const :tag "Monday 19 September 2023" - :doc "The `format-time-string' is: %A %e %B %Y" - day-date-month-year) - (const :tag "Monday 19 September 2023 20:49" - :doc "The `format-time-string' is: %A %e %B %Y %H:%M" - day-date-month-year-24h) - (const :tag "Monday 19 September 2023 08:49 PM" - :doc "The `format-time-string' is: %A %e %B %Y %I:%M %^p" - day-date-month-year-12h) - (string :tag "Custom string with `format-time-string' specifiers"))) - -(defcustom denote-journal-extras-hook nil - "Normal hook called after `denote-journal-extras-new-entry'. -Use this to, for example, set a timer after starting a new -journal entry (refer to the `tmr' package on GNU ELPA)." - :group 'denote-journal-extras - :type 'hook) - -(defun denote-journal-extras-directory () - "Make the variable `denote-journal-extras-directory' and its parents." - (if-let (((stringp denote-journal-extras-directory)) - (directory (file-name-as-directory (expand-file-name denote-journal-extras-directory)))) - (progn - (when (not (file-directory-p denote-journal-extras-directory)) - (make-directory directory :parents)) - directory) - (denote-directory))) - -(defun denote-journal-extras-daily--title-format (&optional date) - "Return present date in `denote-journal-extras-title-format' or prompt for title. -With optional DATE, use it instead of the present date. DATE has -the same format as that returned by `current-time'." - (format-time-string - (if (and denote-journal-extras-title-format - (stringp denote-journal-extras-title-format)) - denote-journal-extras-title-format - (pcase denote-journal-extras-title-format - ('day "%A") - ('day-date-month-year "%A %e %B %Y") - ('day-date-month-year-24h "%A %e %B %Y %H:%M") - ('day-date-month-year-12h "%A %e %B %Y %I:%M %^p") - (_ (denote-title-prompt (format-time-string "%F" date))))) - date)) - -(defun denote-journal-extras--get-template () - "Return template that has `journal' key in `denote-templates'. -If no template with `journal' key exists but `denote-templates' -is non-nil, prompt the user for a template among -`denote-templates'. Else return nil. - -Also see `denote-journal-extras-new-entry'." - (if-let ((template (alist-get 'journal denote-templates))) - template - (when denote-templates - (denote-template-prompt)))) - -;;;###autoload -(defun denote-journal-extras-new-entry (&optional date) - "Create a new journal entry in variable `denote-journal-extras-directory'. -Use `denote-journal-extras-keyword' as a keyword for the newly -created file. Set the title of the new entry according to the -value of the user option `denote-journal-extras-title-format'. - -With optional DATE as a prefix argument, prompt for a date. If -`denote-date-prompt-use-org-read-date' is non-nil, use the Org -date selection module. - -When called from Lisp DATE is a string and has the same format as -that covered in the documentation of the `denote' function. It -is internally processed by `denote-parse-date'." - (interactive (list (when current-prefix-arg (denote-date-prompt)))) - (let ((internal-date (denote-parse-date date)) - (denote-directory (denote-journal-extras-directory))) - (denote - (denote-journal-extras-daily--title-format internal-date) - `(,denote-journal-extras-keyword) - nil nil date - (denote-journal-extras--get-template)) - (run-hooks 'denote-journal-extras-hook))) - -(defun denote-journal-extras--entry-today (&optional date) - "Return list of files matching a journal for today or optional DATE. -DATE has the same format as that returned by `denote-parse-date'." - (denote-directory-files - (format "%sT[0-9]\\{6\\}.*_%s" - (format-time-string "%Y%m%d" date) - denote-journal-extras-keyword))) - -;;;###autoload -(defun denote-journal-extras-new-or-existing-entry (&optional date) - "Locate an existing journal entry or create a new one. -A journal entry is one that has `denote-journal-extras-keyword' as -part of its file name. - -If there are multiple journal entries for the current date, -prompt for one using minibuffer completion. If there is only -one, visit it outright. If there is no journal entry, create one -by calling `denote-journal-extra-new-entry'. - -With optional DATE as a prefix argument, prompt for a date. If -`denote-date-prompt-use-org-read-date' is non-nil, use the Org -date selection module. - -When called from Lisp, DATE is a string and has the same format -as that covered in the documentation of the `denote' function. -It is internally processed by `denote-parse-date'." - (interactive - (list - (when current-prefix-arg - (denote-date-prompt)))) - (let* ((internal-date (denote-parse-date date)) - (files (denote-journal-extras--entry-today internal-date))) - (cond - ((length> files 1) - (find-file (completing-read "Select journal entry: " files nil :require-match))) - (files - (find-file (car files))) - (t - (denote-journal-extras-new-entry date))))) - -;;;###autoload -(defun denote-journal-extras-link-or-create-entry (&optional date id-only) - "Use `denote-link' on journal entry, creating it if necessary. -A journal entry is one that has `denote-journal-extras-keyword' as -part of its file name. - -If there are multiple journal entries for the current date, -prompt for one using minibuffer completion. If there is only -one, link to it outright. If there is no journal entry, create one -by calling `denote-journal-extra-new-entry' and link to it. - -With optional DATE as a prefix argument, prompt for a date. If -`denote-date-prompt-use-org-read-date' is non-nil, use the Org -date selection module. - -When called from Lisp, DATE is a string and has the same format -as that covered in the documentation of the `denote' function. -It is internally processed by `denote-parse-date'. - -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 - (pcase current-prefix-arg - ('(16) (list (denote-date-prompt) :id-only)) - ('(4) (list (denote-date-prompt))))) - (let* ((internal-date (denote-parse-date date)) - (files (denote-journal-extras--entry-today internal-date)) - (path)) - (cond - ((length> files 1) - (setq path (completing-read "Select journal entry: " files nil :require-match))) - (files - (setq path (car files))) - (t - (save-window-excursion - (denote-journal-extras-new-entry date) - (save-buffer) - (setq path (buffer-file-name))))) - (denote-link path - (denote-filetype-heuristics (buffer-file-name)) - (denote--link-get-description path) - id-only))) - -(provide 'denote-journal-extras) -;;; denote-journal-extras.el ends here blob - 03a97dbce0e8ac1329591adbdf74df68cdde1487 (mode 644) blob + /dev/null --- elpa/denote-2.3.3/denote-org-dblock.el +++ /dev/null @@ -1,70 +0,0 @@ -;;; denote-org-dblock.el --- Compatibility alieases for Denote Org Dynamic blocks -*- lexical-binding: t -*- - -;; Copyright (C) 2022-2024 Free Software Foundation, Inc. - -;; Authors: Elias Storms , -;; Protesilaos Stavrou -;; Maintainer: Protesilaos Stavrou -;; Deprecated-since: 3.0.0 -;; 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: -;; -;; This file defines compatibility aliases for Org dynamic blocks set -;; up by Denote. The new source of these is the denote-org-extras.el. -;; -;; Below is the old commentary. -;; -;; * * * -;; -;; This file 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") - -;;; Code: - -(require 'denote-org-extras) - -(define-obsolete-function-alias - 'denote-org-dblock-insert-links - 'denote-org-extras-dblock-insert-links - "3.0.0") - -(define-obsolete-function-alias - 'denote-org-dblock-insert-backlinks - 'denote-org-extras-dblock-insert-backlinks - "3.0.0") - -(define-obsolete-function-alias - 'denote-org-dblock-insert-files - 'denote-org-extras-dblock-insert-files - "3.0.0") - -(display-warning - 'denote - "`denote-org-dblock.el' is obsolete; use `denote-org-extras.el'" - :warning) - -(provide 'denote-org-dblock) -;;; denote-org-dblock.el ends here blob - 984b6166ce8c93f8b3bfa03fc01ef13d3aa08a18 (mode 644) blob + /dev/null --- elpa/denote-2.3.3/denote-org-extras.el +++ /dev/null @@ -1,528 +0,0 @@ -;;; denote-org-extras.el --- Denote extensions for Org mode -*- lexical-binding: t -*- - -;; Copyright (C) 2024 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: -;; -;; WORK-IN-PROGRESS - -;;; Code: - -(require 'denote) -(require 'denote-sort) -(require 'org) - -;;;; Link to file and heading - -(defun denote-org-extras--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) "[*\^L]+"))) - 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"))))) - -(defun denote-org-extras--outline-prompt (&optional file) - "Prompt for outline among headings retrieved by `denote-org-extras--get-outline'. -With optional FILE use the outline of it, otherwise use that of -the current file." - (completing-read - (format "Select heading inside `%s': " - (propertize (file-name-nondirectory file) 'face 'denote-faces-prompt-current-name)) - (denote--completion-table-no-sort 'imenu (denote-org-extras--get-outline (or file buffer-file-name))) - nil :require-match)) - -(defun denote-org-extras--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 line) - (cons (denote-link-ol-get-heading) (denote-link-ol-get-id))))) - -(defun denote-org-extras-format-link-with-heading (file heading-id description) - "Prepare link to FILE with HEADING-ID using DESCRIPTION." - (format "[[denote:%s::#%s][%s]]" - (denote-retrieve-filename-identifier file) - heading-id - description)) - -;;;###autoload -(defun denote-org-extras-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-extras-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." - (declare (interactive-only t)) - (interactive nil org-mode) - (unless (derived-mode-p 'org-mode) - (user-error "Links to headings only work between Org files")) - (when-let ((file (denote-file-prompt ".*\\.org")) - (file-text (denote--link-get-description file)) - (heading (denote-org-extras--outline-prompt file)) - (line (string-to-number (car (split-string heading "\t")))) - (heading-data (denote-org-extras--get-heading-and-id-from-line line file)) - (heading-text (car heading-data)) - (heading-id (cdr heading-data)) - (description (denote-link-format-heading-description file-text heading-text))) - (insert (denote-org-extras-format-link-with-heading file heading-id description)))) - -;;;; Extract subtree into its own note - -(defun denote-org-extras--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))) - -;;;###autoload -(defun denote-org-extras-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 -`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-extras--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 nil signature) - (insert text)) - (user-error "No subtree to extract; aborting"))) - -;;;; Convert links from `:denote' to `:file' and vice versa - -;; TODO 2024-02-28: Do we need to convert between other link types? I -;; think not, since the `denote:' type is modelled after the `file:' -;; one. -(defun denote-org-extras--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))) - -;;;###autoload -(defun denote-org-extras-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) - (progn - (goto-char (point-min)) - (while (re-search-forward (denote-org-extras--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-get-path-by-id id)))) - (when id - (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))))) - ;; TODO 2024-02-28: notify how many changed. - (message "Converted `denote:' links to `file:' links")) - (user-error "The current file is not using Org mode"))) - -;;;###autoload -(defun denote-org-extras-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) - (progn - (goto-char (point-min)) - (while (re-search-forward (denote-org-extras--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))))) - ;; TODO 2024-02-28: notify how many changed. - (message "Converted as `file:' links to `denote:' links")) - (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-extras-dblock--files (files-matching-regexp &optional sort-by-component reverse) - "Return list of FILES-MATCHING-REGEXP in variable `denote-directory'. -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-extras-dblock--files-missing-only'." - (cond - ((and sort-by-component reverse) - (denote-sort-get-directory-files files-matching-regexp sort-by-component reverse :omit-current)) - (sort-by-component - (denote-sort-get-directory-files files-matching-regexp sort-by-component nil :omit-current)) - (reverse - (denote-sort-get-directory-files files-matching-regexp :no-component-specified reverse :omit-current)) - (t - (denote-directory-files files-matching-regexp :omit-current)))) - -(defun denote-org-extras-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-extras-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-extras-dblock--files'." - (denote-sort-files - (denote-org-extras-dblock--get-missing-links files-matching-regexp) - sort-by-component - reverse)) - -;;;;; Dynamic block to insert links - -;;;###autoload -(defun denote-org-extras-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 - :sort-by-component nil - :reverse-sort nil - :id-only nil)) - (org-update-dblock)) - -(org-dynamic-block-define "denote-links" 'denote-org-extras-dblock-insert-links) - -(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* ((regexp (plist-get params :regexp)) - (rx (if (listp regexp) (macroexpand `(rx ,regexp)) regexp)) - (sort (plist-get params :sort-by-component)) - (reverse (plist-get params :reverse-sort)) - (block-name (plist-get params :block-name)) - (files (denote-org-extras-dblock--files 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) - (join-line))) ; remove trailing empty line - -;;;;; Dynamic block to insert missing links - -;;;###autoload -(defun denote-org-extras-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 - :sort-by-component nil - :reverse-sort nil - :id-only nil)) - (org-update-dblock)) - -(org-dynamic-block-define "denote-missing-links" 'denote-org-extras-dblock-insert-links) - -(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* ((regexp (plist-get params :regexp)) - (rx (if (listp regexp) (macroexpand `(rx ,regexp)) regexp)) - (sort (plist-get params :sort-by-component)) - (reverse (plist-get params :reverse-sort)) - (block-name (plist-get params :block-name)) - (files (denote-org-extras-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) - (join-line))) ; remove trailing empty line - -;;;;; Dynamic block to insert backlinks - -(defun denote-org-extras-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))) - -;;;###autoload -(defun denote-org-extras-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" - :sort-by-component nil - :reverse-sort nil - :id-only nil)) - (org-update-dblock)) - -(org-dynamic-block-define "denote-backlinks" 'denote-org-extras-dblock-insert-backlinks) - -(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 (denote-link-return-backlinks))) - (let* ((sort (plist-get params :sort-by-component)) - (reverse (plist-get params :reverse-sort)) - (files (denote-org-extras-dblock--maybe-sort-backlinks files sort reverse))) - (denote-link--insert-links files 'org (plist-get params :id-only) :no-other-sorting) - (join-line)))) ; remove trailing empty line - -;;;;; Dynamic block to insert entire file contents - -(defun denote-org-extras-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--link-get-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-extras-dblock-file-contents-separator - (concat "\n\n" (make-string 50 ?-) "\n\n\n") - "Fallback separator used by `denote-org-extras-dblock-add-files'.") - -(defun denote-org-extras-dblock--separator (separator) - "Return appropriate value of SEPARATOR for `denote-org-extras-dblock-add-files'." - (cond - ((null separator) "") - ((stringp separator) separator) - (t denote-org-extras-dblock-file-contents-separator))) - -(defun denote-org-extras-dblock-add-files (regexp &optional separator no-front-matter add-links sort-by-component reverse) - "Insert files matching REGEXP. - -Seaprate them with the optional SEPARATOR. If SEPARATOR is nil, -use the `denote-org-extras-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." - (let* ((files (denote-org-extras-dblock--files regexp sort-by-component reverse)) - (files-contents (mapcar - (lambda (file) (denote-org-extras-dblock--get-file-contents file no-front-matter add-links)) - files))) - (insert (string-join files-contents (denote-org-extras-dblock--separator separator))))) - -;;;###autoload -(defun denote-org-extras-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 - :sort-by-component sort-by-component - :reverse-sort nil - :no-front-matter nil - :file-separator nil - :add-links nil)) - (org-update-dblock)) - -(org-dynamic-block-define "denote-files" 'denote-org-extras-dblock-insert-files) - -(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* ((regexp (plist-get params :regexp)) - (rx (if (listp regexp) (macroexpand `(rx ,regexp)) 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))) - (when block-name (insert "#+name: " block-name "\n")) - (when rx (denote-org-extras-dblock-add-files rx separator no-f-m add-links sort reverse))) - (join-line)) ; remove trailing empty line - - -(provide 'denote-org-extras) -;;; denote-org-extras.el ends here blob - c285aaada40e6298a3e3d8028eb635890b755159 (mode 644) blob + /dev/null --- elpa/denote-2.3.3/denote-pkg.el +++ /dev/null @@ -1,2 +0,0 @@ -;; Generated package description from denote.el -*- no-byte-compile: t -*- -(define-package "denote" "2.3.3" "Simple notes with an efficient file-naming scheme" '((emacs "28.1")) :commit "12c08b96a4f13e64758ebe2dbfba4d239a555485" :authors '(("Protesilaos Stavrou" . "info@protesilaos.com")) :maintainer '("Protesilaos Stavrou" . "info@protesilaos.com") :url "https://github.com/protesilaos/denote") blob - 1335d3a22c7e8cf1e62e3db855dcab15123bb3f0 (mode 644) blob + /dev/null --- elpa/denote-2.3.3/denote-rename-buffer.el +++ /dev/null @@ -1,159 +0,0 @@ -;;; denote-rename-buffer.el --- Rename Denote buffers to be shorter and easier to read -*- lexical-binding: t -*- - -;; Copyright (C) 2023-2024 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: -;; -;; Rename Denote buffers to be shorter and easier to read. Enable -;; `denote-rename-buffer-mode' to automatically rename the buffer of a -;; Denote file. The renaming function is specified in the user option -;; `denote-rename-buffer-function'. - -;;; Code: - -(require 'denote) - -(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")) - -(defcustom denote-rename-buffer-format "%t" - "The format of the buffer name `denote-rename-buffer' should use. -Thie value is a string that treats specially the following -specifiers: - -- The %t is the Denote TITLE of the file. -- The %i is the Denote IDENTIFIER of the file. -- 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 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 . "2.1.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)) - (type (denote-filetype-heuristics 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 ?i (or (denote-retrieve-filename-identifier file) "")) - (cons ?d (or (denote-retrieve-filename-identifier file) "")) - (cons ?s (or (denote-retrieve-filename-signature file) "")) - (cons ?k (if-let ((kws (denote-retrieve-front-matter-keywords-value file type))) - (denote-keywords-combine kws) - (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))) - -(make-obsolete - 'denote-rename-buffer-with-title - 'denote-rename-buffer - "2.1.0") - -(make-obsolete - 'denote-rename-buffer-with-identifier - 'denote-rename-buffer - "2.1.0") - -(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-rename-buffer) -;;; denote-rename-buffer.el ends here blob - 1e4cfd5155e4e1ada1ede5f2a2e09e5d76fd3d92 (mode 644) blob + /dev/null --- elpa/denote-2.3.3/denote-silo-extras.el +++ /dev/null @@ -1,102 +0,0 @@ -;;; denote-silo-extras.el --- Convenience functions for using Denote in multiple silos -*- lexical-binding: t; -*- - -;; Copyright (C) 2023-2024 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: - -;; This is a set of convenience functions that used to be provided in -;; the Denote manual. A "silo" is a `denote-directory' that is -;; self-contained. Users can maintain multiple silos. Consult the -;; manual for the details. With the Denote package installed, -;; evaluate the following to read the relevant node: -;; -;; (info "(denote) Maintain separate directory silos for notes") - -;;; Code: - -(require 'denote) - -(defgroup denote-silo-extras nil - "Make it easier to use Denote across Silos." - :group 'denote - :link '(info-link "(denote) Top") - :link '(url-link :tag "Homepage" "https://protesilaos.com/emacs/denote")) - -(defcustom denote-silo-extras-directories - `(,denote-directory) - "List of file paths pointing to Denote silos. -Each file path points to a directory, which takes the same value -as the variable `denote-directory'." - :group 'denote-silo-extras - :link '(info-link "(denote) Maintain separate directories for notes") - :type '(repeat directory)) - -(defvar denote-silo-extras-directory-history nil - "Minibuffer history for `denote-silo-extras--directory-prompt'.") - -(defalias 'denote-silo-extras--directory-history 'denote-silo-extras-directory-history - "Compatibility alias for `denote-silo-extras-directory-history'.") - -(defun denote-silo-extras--directory-prompt () - "Prompt for directory among `denote-silo-extras-directories'." - (let ((default (car denote-silo-extras-directory-history))) - (completing-read - (format-prompt "Select a silo" default) - denote-silo-extras-directories nil :require-match - nil 'denote-silo-extras-directory-history))) - -;;;###autoload -(defun denote-silo-extras-create-note (silo) - "Select SILO and run `denote' in it. -SILO is a file path from `denote-silo-extras-directories'. - -When called from Lisp, SILO is a file system path to a directory." - (interactive (list (denote-silo-extras--directory-prompt))) - (let ((denote-directory silo)) - (call-interactively #'denote))) - -;;;###autoload -(defun denote-silo-extras-open-or-create (silo) - "Select SILO and run `denote-open-or-create' in it. -SILO is a file path from `denote-silo-extras-directories'. - -When called from Lisp, SILO is a file system path to a directory." - (interactive (list (denote-silo-extras--directory-prompt))) - (let ((denote-directory silo)) - (call-interactively #'denote-open-or-create))) - -;;;###autoload -(defun denote-silo-extras-select-silo-then-command (silo command) - "Select SILO and run Denote COMMAND in it. -SILO is a file path from `denote-silo-extras-directories', while -COMMAND is one among `denote-silo-extras-commands'. - -When called from Lisp, SILO is a file system path to a directory." - (interactive - (list - (denote-silo-extras--directory-prompt) - (denote-command-prompt))) - (let ((denote-directory silo)) - (call-interactively command))) - -(provide 'denote-silo-extras) -;;; denote-silo-extras.el ends here blob - de443dc9bcecbcc118891fb70e71484189a24131 (mode 644) blob + /dev/null --- elpa/denote-2.3.3/denote-sort.el +++ /dev/null @@ -1,201 +0,0 @@ -;;; denote-sort.el --- Sort Denote files based on a file name component -*- lexical-binding: t -*- - -;; Copyright (C) 2023-2024 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: -;; -;; Sort Denote files based on their file name components, namely, the -;; signature, title, or keywords. - -;;; Code: - -(require 'denote) - -(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")) - -(defvar denote-sort-comparison-function #'string-collate-lessp - "String comparison function used by `denote-sort-files' subroutines.") - -(defvar denote-sort-components '(title keywords signature identifier) - "List of sorting keys applicable for `denote-sort-files' and related.") - -;; 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)))) - `(defun ,(intern (format "denote-sort-%s-lessp" component)) (file1 file2) - ,(format - "Return smallest between FILE1 and FILE2 based on their %s. -The comparison is done with `denote-sort-comparison-function' between the -two title values." - component) - (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 denote-sort-comparison-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 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 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 (when component - (pcase component - ('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) - "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." - (denote-sort-files - (denote-directory-files files-matching-regexp omit-current) - sort-by-component - reverse)) - -(defun denote-sort-get-links (files-matching-regexp sort-by-component current-file-type id-only &optional reverse) - "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 -`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." - (denote-link--prepare-links - (denote-sort-get-directory-files files-matching-regexp sort-by-component reverse) - 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 `denote-sort-files' 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-local denote-sort--dired-buffer nil - "Buffer object of current `denote-sort-dired'.") - -;;;###autoload -(defun denote-sort-dired (files-matching-regexp sort-by-component reverse) - "Produce Dired dired-buffer with sorted files from variable `denote-directory'. -When called interactively, prompt for FILES-MATCHING-REGEXP, -SORT-BY-COMPONENT, and REVERSE. - -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'). - -3. REVERSE is a boolean to reverse the order when it is a non-nil value. - -When called from Lisp, the arguments are a string, a keyword, and -a non-nil value, respectively." - (interactive - (list - (denote-files-matching-regexp-prompt) - (denote-sort-component-prompt) - (y-or-n-p "Reverse sort? "))) - (if-let ((default-directory (denote-directory)) - (files (denote-sort-get-directory-files files-matching-regexp sort-by-component reverse)) - ;; 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 sort-by-component)) - (buffer-name (format "Denote sort by `%s' at %s" sort-by-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 _) - (kill-buffer dired-buffer) - (denote-sort-dired files-matching-regexp sort-by-component reverse)))) - ;; 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))) - -(provide 'denote-sort) -;;; denote-sort.el ends here blob - 33e2cf99f04d7035281ede64abe9b9ae640b302d (mode 644) blob + /dev/null --- elpa/denote-2.3.3/denote.el +++ /dev/null @@ -1,4687 +0,0 @@ -;;; denote.el --- Simple notes with an efficient file-naming scheme -*- lexical-binding: t -*- - -;; Copyright (C) 2022-2024 Free Software Foundation, Inc. - -;; Author: Protesilaos Stavrou -;; Maintainer: Protesilaos Stavrou -;; URL: https://github.com/protesilaos/denote -;; Version: 2.3.3 -;; 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") - -(define-obsolete-variable-alias - 'denote-user-enforced-denote-directory - 'denote-directory - "2.3.0") - -;;;###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) - -(defcustom denote-save-buffer-after-creation nil - "Control whether commands that creeate 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. - -If this user option is set to a non-nil value, such buffers are -saved automatically." - :group 'denote - :package-version '(denote . "2.3.0") - :type 'boolean) - -;;;###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 `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 - to establish a sequential relationship between files (e.g. 1, - 1a, 1b, 1b1, 1b2, ...). Signatures have no strictly defined - function and are up to the user to apply as they see fit. One - use-case is to implement Niklas Luhmann's Zettelkasten system - for a sequence of notes (Folgezettel). Signatures are not - included in a file's front matter. They are reserved solely - for creating a sequence in a file listing, at least for the - time being. To insert a link that includes the signature, use - the command `denote-link-with-signature'. - -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 (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'." - :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-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) - -(make-obsolete - 'denote-allow-multi-word-keywords - 'denote-file-name-letter-casing - "2.1.0") - -(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 `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 t - "Determine whether `org-store-link' links to the current Org heading. - -When non-nil store link to the current Org heading inside the -Denote file. If the heading does not have a CUSTOM_ID, create it -and include it in its PROPERTIES drawer. If a CUSTOM_ID exists, -take it as-is. - -Make 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][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 (though I deleted a few characters to not get -complaints from the byte compiler about long lines in the doc -string...). - -If this user option is set to nil, only store links to the Denote -file (using its identifier), but not to the given heading. This -is what Denote was doing in versions prior to 2.3.0. - -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. - -[ 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 . "2.3.0") - :type 'boolean) - -(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 . 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 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 string) - :package-version '(denote . "0.5.0") - :link '(info-link "(denote) The denote-templates option") - :group 'denote) - -(defcustom denote-backlinks-show-context nil - "When non-nil, show link context in the backlinks buffer. - -The context is the line a link to the current note is found in. -The context includes multiple links to the same note, if those -are present. - -When nil, only show a simple list of file names that link to the -current note." - :group 'denote - :package-version '(denote . "1.2.0") - :type 'boolean) - -(defcustom denote-rename-no-confirm nil - "Make renaming commands not prompt for confirmation and save buffers outright. - -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. - -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. It also does not -save the affected file's buffer to let the user inspect and -confirm the changes (such as by invoking the command -`diff-buffer-with-file'). - -With this user option bound to a non-nil value, buffers are saved -as well. 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. - -Specialised 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 - :package-version '(denote . "2.3.0") - :type 'boolean) - -(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-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) - "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)) - -(make-obsolete - 'denote-file-name-letter-casing - 'denote-file-name-slug-functions - "2.3.0") - -(defcustom denote-link-description-function #'denote-link-description-with-signature-and-title - "Function to create the description of links. - -The function specified takes a FILE argument and returns the description -as a string. - -By default, the title of the file is returned as the description. If -the file has a signature, it is prepended to the title." - :group 'denote - :type '(choice - (function :tag "Link to title and include signature, if present" denote-link-description-with-signature-and-title) - (function :tag "Custom function like `denote-link-description-with-signature-and-title'")) - :package-version '(denote . "2.3.0")) - -;;;; 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.") - -(defconst denote-excluded-punctuation-regexp "[][{}!@#$%^&*()=+'\"?,.\|;:~`‘’“”/]*" - "Punctionation that is removed from file names. -We consider those characters illegal for our purposes.") - -(defvar denote-excluded-punctuation-extra-regexp nil - "Additional punctuation that is removed from file names. -This variable is for advanced users who need to extend the -`denote-excluded-punctuation-regexp'. Once we have a better -understanding of what we should be omitting, we will update -things accordingly.") - -;;;; 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." - ;; NOTE 2024-02-09: We may want to remove this condition eventually. - ;; The reason is that we want to stop supporting the dir-local - ;; values of `default-directory' or `local' in favour of just - ;; specifying a string. I don't think we can delete this altogether - ;; though, as it will break existing configurations. - (if-let (((or (eq denote-directory 'default-directory) (eq denote-directory 'local))) - (silo-dir (denote--default-directory-is-silo-p))) - silo-dir - (let ((denote-directory (file-name-as-directory (expand-file-name denote-directory)))) - (denote--make-denote-directory) - denote-directory))) - -(defun denote--slug-no-punct (str &optional extra-characters) - "Remove punctuation from STR. -Concretely, replace with an empty string anything that matches -the `denote-excluded-punctuation-regexp' and -`denote-excluded-punctuation-extra-regexp'. - -EXTRA-CHARACTERS is an optional string that has the same meaning -as the aforementioned variables." - (dolist (regexp (list denote-excluded-punctuation-regexp - denote-excluded-punctuation-extra-regexp - extra-characters)) - (when (stringp regexp) - (setq str (replace-regexp-in-string regexp "" str)))) - str) - -(defun denote--slug-no-punct-for-signature (str &optional extra-characters) - "Remove punctuation (except = signs) from STR. - -This works the same way as `denote--slug-no-punct', except that = -signs are not removed from STR. - -EXTRA-CHARACTERS is an optional string. See -`denote--slug-no-punct' for its documentation." - (dolist (regexp (list denote-excluded-punctuation-regexp - denote-excluded-punctuation-extra-regexp - extra-characters)) - (when (stringp regexp) - (setq str (replace-regexp-in-string (string-replace "=" "" regexp) "" str)))) - str) - -(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--remove-dot-characters (str) - "Remove the dot character from STR." - (replace-regexp-in-string "\\." "" str)) - -(defun denote--trim-right-token-characters (str) - "Remove =, - and _ from the end of STR." - (string-trim-right str "[=_-]+")) - -(defun denote--replace-consecutive-token-characters (str) - "Replace consecutive characters with a single one in STR. -Spaces, underscores and equal signs are replaced with a single -one in str." - (replace-regexp-in-string - "-\\{2,\\}" "-" - (replace-regexp-in-string - "_\\{2,\\}" "_" - (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 -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))))) - -(make-obsolete - 'denote-letter-case - 'denote-sluggify - "2.3.0") - -(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-sluggify-title (str) - "Make STR an appropriate slug for title." - (downcase (denote--slug-hyphenate (denote--slug-no-punct str)))) - -(defun denote-sluggify-signature (str) - "Make STR an appropriate slug for signature." - (downcase (denote--slug-put-equals (denote--slug-no-punct-for-signature str "-+")))) - -(defun denote-sluggify-keyword (str) - "Sluggify STR while joining separate words." - (downcase - (replace-regexp-in-string - "-" "" - (denote--slug-hyphenate (denote--slug-no-punct str))))) - -(make-obsolete - 'denote-sluggify-and-join - 'denote-sluggify-keyword - "2.3.0") - -(defun denote-sluggify-keywords (keywords) - "Sluggify KEYWORDS, which is a list of strings." - (mapcar (lambda (keyword) - (denote-sluggify 'keyword keyword)) - keywords)) - -(defun denote--file-empty-p (file) - "Return non-nil if FILE is empty." - (zerop (or (file-attribute-size (file-attributes file)) 0))) - -(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 `denote-file-type'." - (seq-some (lambda (e) - (string-suffix-p e file)) - (denote-file-type-extensions-with-encryption))) - -(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 `denote-file-type'." - (and (string-prefix-p (denote-directory) (expand-file-name 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 not be a directory, must satisfy -`file-regular-p' and `denote-filename-is-note-p'." - (and (not (file-directory-p file)) - (file-regular-p file) - (denote-filename-is-note-p file))) - -(defun denote-file-has-signature-p (file) - "Return non-nil if FILE has a Denote identifier." - (denote-retrieve-filename-signature file)) - -(make-obsolete 'denote-file-directory-p nil "2.0.0") - -(defun denote--file-regular-writable-p (file) - "Return non-nil if FILE is regular and writable." - (and (file-regular-p file) - (file-writable-p file))) - -(defun denote-file-is-writable-and-supported-p (file) - "Return non-nil if FILE is writable and has supported extension." - (and (denote--file-regular-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))) - -(define-obsolete-function-alias - 'denote--default-dir-has-denote-prefix - 'denote--dir-in-denote-directory-p - "2.1.0") - -(defun denote--exclude-directory-regexp-p (file) - "Return non-nil if FILE matches `denote-excluded-directories-regexp'." - (and 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--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 (denote-file-has-identifier-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) - "Return list of absolute file paths in variable `denote-directory'. - -Files only need to have an identifier. The return value may thus -include file types that are not implied by `denote-file-type'. - -Remember that the variable `denote-directory' accepts a dir-local -value, as explained in its doc string. - -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'." - (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-is-note-p files))) - files)) - -;; NOTE 2023-11-30: We are declaring `denote-directory-text-only-files' -;; obsolete, though we keep it around for the foreseeable future. It -;; WILL BE REMOVED ahead of version 3.0.0 of Denote, whenever that -;; happens. -(make-obsolete 'denote-directory-text-only-files 'denote-directory-files "2.2.0") - -(defun denote-directory-text-only-files () - "Return list of text files in variable `denote-directory'. -Filter `denote-directory-files' using `denote-file-is-note-p'." - (denote-directory-files nil nil :text-only)) - -(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))) - -(define-obsolete-variable-alias - 'denote--encryption-file-extensions - 'denote-encryption-file-extensions - "2.0.0") - -;; 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.") - -(define-obsolete-function-alias - 'denote--extensions-with-encryption - 'denote-file-type-extensions-with-encryption - "2.0.0") - -(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-prefix-p id (file-name-nondirectory file))) - (denote-directory-files)))) - (if (length< files 2) - (car files) - (seq-find - (lambda (file) - (let ((file-extension (denote-get-file-extension file))) - (and (denote-file-is-note-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’)." - (file-relative-name (denote-get-path-by-id id) directory)) - -;; NOTE 2023-11-30: We are declaring `denote-directory-files-matching-regexp' -;; obsolete, though we keep it around for the foreseeable future. It -;; WILL BE REMOVED ahead of version 3.0.0 of Denote, whenever that -;; happens. -(make-obsolete 'denote-directory-files-matching-regexp 'denote-directory-files "2.2.0") - -(defun denote-directory-files-matching-regexp (regexp) - "Return list of files matching REGEXP in `denote-directory-files'." - (denote-directory-files regexp)) - -;; NOTE 2023-11-30: We are declaring `denote-all-files' obsolete, -;; though we keep it around for the foreseeable future. It WILL BE -;; REMOVED ahead of version 3.0.0 of Denote, whenever that happens. -(make-obsolete 'denote-all-files 'denote-directory-files "2.2.0") - -(defun denote-all-files (&optional omit-current) - "Return the list of Denote files in variable `denote-directory'. -With optional OMIT-CURRENT, do not include the current Denote -file in the returned list." - (denote-directory-files nil omit-current nil)) - -(defvar denote-file-history nil - "Minibuffer history of `denote-file-prompt'.") - -(defalias 'denote--file-history 'denote-file-history - "Compatibility alias for `denote-file-history'.") - -;; NOTE 2024-02-29: Based on `project--read-file-cpd-relative' from -;; the built-in project.el -(defun denote-file-prompt (&optional files-matching-regexp prompt-text) - "Prompt for file with identifier in variable `denote-directory'. -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 NOTE\"." - (when-let ((all-files (denote-directory-files files-matching-regexp :omit-current))) - (let* ((common-parent-directory - (let ((common-prefix (try-completion "" all-files))) - (if (> (length common-prefix) 0) - (file-name-directory common-prefix)))) - (cpd-length (length common-parent-directory)) - (prompt-prefix (or prompt-text "Select FILE")) - (prompt (if (zerop cpd-length) - (format "%s: " prompt-prefix) - (format "%s in %s: " prompt-prefix common-parent-directory))) - (included-cpd (when (member common-parent-directory all-files) - (setq all-files - (delete common-parent-directory all-files)) - t)) - (substrings (mapcar (lambda (s) (substring s cpd-length)) all-files)) - (_ (when included-cpd - (setq substrings (cons "./" substrings)))) - (new-collection (denote--completion-table 'file substrings)) - (relname (completing-read prompt new-collection nil nil nil 'denote-file-history)) - (absname (expand-file-name relname common-parent-directory))) - ;; NOTE 2024-02-29: This delete and add feels awkward. I wish - ;; we could tell `completing-read' to just leave this up to us. - (setq denote-file-history (delete relname denote-file-history)) - (add-to-history 'denote-file-history absname) - absname))) - -;;;; 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." - (when-let ((kws (denote-retrieve-filename-keywords path))) - (split-string kws "_"))) - -(defun denote--inferred-keywords () - "Extract keywords from `denote-directory-files'. -This function returns duplicates. The `denote-keywords' is the -one that doesn't." - (let ((kw (mapcan #'denote-extract-keywords-from-path (denote-directory-files)))) - (if-let ((regexp denote-excluded-keywords-regexp)) - (seq-remove (apply-partially #'string-match-p regexp) kw) - kw))) - -(defun 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. - -Inferred keywords are filtered by the user option -`denote-excluded-keywords-regexp'." - (delete-dups - (if denote-infer-keywords - (append (denote--inferred-keywords) denote-known-keywords) - denote-known-keywords))) - -(defun denote-convert-file-name-keywords-to-crm (string) - "Make STRING with keywords readable by `completing-read-multiple'. -STRING consists of underscore-separated words, as those appear in -the keywords component of a Denote file name. STRING is the same -as the return value of `denote-retrieve-filename-keywords'." - (string-join (split-string string "_" :omit-nulls "_") ",")) - -(defvar denote-keyword-history nil - "Minibuffer history of inputted keywords.") - -(defalias 'denote--keyword-history 'denote-keyword-history - "Compatibility alias for `denote-keyword-history'.") - -(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) - "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. - -Return an empty list if the minibuffer input is empty." - (denote--keywords-crm (denote-keywords) 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)) - -(define-obsolete-function-alias - 'denote--keywords-combine - 'denote-keywords-combine - "2.1.0") - -(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 -\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 ----\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 -+++\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 ----------------------------\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'.") - -(define-obsolete-function-alias - 'denote-surround-with-quotes - 'denote-format-string-for-md-front-matter - "2.3.0") - -(defun denote-format-string-for-md-front-matter (s) - "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." - (if (stringp s) - (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. -If S is not a string, return an empty string." - (if (stringp s) 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 "[][ \"']+")) - -(defvar denote-file-types - '((org - :extension ".org" - :date-function denote-date-org-timestamp - :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 - :link denote-org-link-format - :link-in-context-regexp denote-org-link-in-context-regexp) - (markdown-yaml - :extension ".md" - :date-function denote-date-rfc3339 - :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 - :link denote-md-link-format - :link-in-context-regexp denote-md-link-in-context-regexp) - (markdown-toml - :extension ".md" - :date-function denote-date-rfc3339 - :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 - :link denote-md-link-format - :link-in-context-regexp denote-md-link-in-context-regexp) - (text - :extension ".txt" - :date-function denote-date-iso-8601 - :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 - :link denote-org-link-format - :link-in-context-regexp denote-org-link-in-context-regexp)) - "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: - -- `: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 `denote-file-type' is nil, use the first element of this list -for new note creation. The default is `org'.") - -(defun denote--date-format-function (file-type) - "Return date format function of FILE-TYPE." - (plist-get - (alist-get file-type denote-file-types) - :date-function)) - -(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." - (plist-get - (alist-get file-type denote-file-types) - :title-key-regexp)) - -(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." - (plist-get - (alist-get file-type denote-file-types) - :keywords-key-regexp)) - -(defun denote--keywords-value-function (file-type) - "Convert keywords' string 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--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))) - -(define-obsolete-function-alias - 'denote--extensions - 'denote-file-type-extensions - "2.0.0") - -(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--format-front-matter (title date keywords id filetype) - "Front matter for new notes. - -TITLE, DATE, and ID are all strings or functions that return a -string. KEYWORDS is a list of strings. FILETYPE is one of the -values of `denote-file-type'." - (let* ((fm (denote--front-matter filetype)) - (title (denote--format-front-matter-title title filetype)) - (kws (denote--format-front-matter-keywords keywords filetype))) - (if fm (format fm title date kws id) ""))) - -(defun denote--get-title-line-from-front-matter (title file-type) - "Retrieve title line from front matter based on FILE-TYPE. -Format TITLE in the title line. The returned line does not -contain the newline." - (let ((front-matter (denote--format-front-matter title "" nil "" file-type)) - (key-regexp (denote--title-key-regexp file-type))) - (with-temp-buffer - (insert front-matter) - (goto-char (point-min)) - (when (re-search-forward key-regexp nil t 1) - (buffer-substring-no-properties (line-beginning-position) (line-end-position)))))) - -(defun denote--get-keywords-line-from-front-matter (keywords file-type) - "Retrieve keywords line from front matter based on FILE-TYPE. -Format KEYWORDS in the keywords line. The returned line does not -contain the newline." - (let ((front-matter (denote--format-front-matter "" "" keywords "" file-type)) - (key-regexp (denote--keywords-key-regexp file-type))) - (with-temp-buffer - (insert front-matter) - (goto-char (point-min)) - (when (re-search-forward key-regexp nil t 1) - (buffer-substring-no-properties (line-beginning-position) (line-end-position)))))) - -;;;; 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, refer to the function -`denote-create-unique-file-identifier'." - (let ((filename (file-name-nondirectory file))) - (if (string-match (concat "\\`" denote-id-regexp) filename) - (match-string-no-properties 0 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 (&optional date) - "Convert DATE into a Denote identifier using `denote-id-format'. -DATE is parsed by `denote-valid-date-p'. If DATE is nil, use the -current time." - (format-time-string - denote-id-format - (when date (denote-valid-date-p date)))) - -(defun denote-create-unique-file-identifier (file used-ids &optional date) - "Generate a unique identifier for FILE not in USED-IDS hash-table. - -The conditions are as follows: - -- If optional DATE is non-nil pass it to `denote-get-identifier'. - DATE will have to conform with `denote-valid-date-p'. If it - does not, return an error. - -- If optional DATE is nil, use the file attributes to determine - the last modified date and format it as an identifier. - -- As a fallback, derive an identifier from the current time. - -To only return an existing identifier, refer to the function -`denote-retrieve-filename-identifier'." - (let ((id (cond - (date (denote-get-identifier date)) - ((denote--file-attributes-time file)) - (t (denote-get-identifier))))) - (denote--find-first-unused-id id used-ids))) - -(define-obsolete-function-alias - 'denote-retrieve-or-create-file-identifier - 'denote-retrieve-filename-identifier - "2.1.0") - -(defun denote-retrieve-filename-keywords (file) - "Extract keywords from FILE name, if present, else return nil. -Return matched keywords as a single string." - (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))) - -(defun denote-retrieve-front-matter-title-value (file file-type) - "Return title value from FILE front matter per FILE-TYPE." - (denote--file-with-temp-buffer file - (when (re-search-forward (denote--title-key-regexp file-type) nil t 1) - (funcall (denote--title-value-reverse-function file-type) - (buffer-substring-no-properties (point) (line-end-position)))))) - -(defun denote-retrieve-front-matter-title-line (file file-type) - "Return title line from FILE front matter per FILE-TYPE." - (denote--file-with-temp-buffer file - (when (re-search-forward (denote--title-key-regexp file-type) nil t 1) - (buffer-substring-no-properties (line-beginning-position) (line-end-position))))) - -(defun denote-retrieve-front-matter-keywords-value (file file-type) - "Return keywords value from FILE front matter per FILE-TYPE. -The return value is a list of strings. To get a combined string -the way it would appear in a Denote file name, use -`denote-retrieve-front-matter-keywords-value-as-string'." - (denote--file-with-temp-buffer file - (when (re-search-forward (denote--keywords-key-regexp file-type) nil t 1) - (funcall (denote--keywords-value-reverse-function file-type) - (buffer-substring-no-properties (point) (line-end-position)))))) - -(defun denote-retrieve-front-matter-keywords-value-as-string (file file-type) - "Return keywords value from FILE front matter per FILE-TYPE. -The return value is a string, with the underscrore as a separator -between individual keywords. To get a list of strings instead, -use `denote-retrieve-front-matter-keywords-value' (the current function uses -that internally)." - (denote-keywords-combine (denote-retrieve-front-matter-keywords-value file file-type))) - -(defun denote-retrieve-front-matter-keywords-line (file file-type) - "Return keywords line from FILE front matter per FILE-TYPE." - (denote--file-with-temp-buffer file - (when (re-search-forward (denote--keywords-key-regexp file-type) nil t 1) - (buffer-substring-no-properties (line-beginning-position) (line-end-position))))) - -(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'.") - -(defalias 'denote-retrieve-keywords-value-as-string 'denote-retrieve-front-matter-keywords-value-as-string - "Alias for `denote-retrieve-front-matter-keywords-value-as-string'.") - -(define-obsolete-function-alias - 'denote--retrieve-title-or-filename - 'denote-retrieve-title-or-filename - "2.3.0") - -(defun denote-retrieve-title-or-filename (file type) - "Return appropriate title for FILE given its TYPE. -Try to find the value of the title in the front matter of FILE, -otherwise use its file name. - -This is a wrapper for `denote-retrieve-front-matter-title-value' and -`denote-retrieve-filename-title'." - (if-let (((denote-file-is-note-p file)) - (title (denote-retrieve-front-matter-title-value file type)) - ((not (string-blank-p title)))) - title - (or (denote-retrieve-filename-title file) - (file-name-base file)))) - -(defun denote--retrieve-location-in-xrefs (identifier) - "Return list of xrefs for IDENTIFIER with their respective location. -Limit the search to text files, per `denote-directory-files' with -non-nil `text-only' parameter." - (mapcar #'xref-match-item-location - (xref-matches-in-files identifier - (denote-directory-files nil nil :text-only)))) - -(defun denote--retrieve-group-in-xrefs (identifier) - "Access location of xrefs for IDENTIFIER and group them per file. -See `denote--retrieve-locations-in-xrefs'." - (mapcar #'xref-location-group - (denote--retrieve-location-in-xrefs identifier))) - -(defun denote--retrieve-files-in-xrefs (identifier) - "Return sorted, deduplicated file names with IDENTIFIER in their contents." - (sort - (delete-dups - (denote--retrieve-group-in-xrefs identifier)) - #'string-collate-lessp)) - -;;;; 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 cannot be -nil or an empty string and must match `denote-id-regexp'. - -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")) - ((null id) - (error "ID must not be nil")) - ((string-empty-p id) - (error "ID must not be an empty string")) - ((not (string-match-p denote-id-regexp id)) - (error "ID `%s' does not match `denote-id-regexp'" id))) - (let ((file-name (concat dir-path id))) - (when (and signature (not (string-empty-p signature))) - (setq file-name (concat file-name "==" (denote-sluggify 'signature signature)))) - (when (and title (not (string-empty-p title))) - (setq file-name (concat file-name "--" (denote-sluggify 'title title)))) - (when keywords - (setq file-name (concat file-name "__" (denote-keywords-combine (denote-sluggify-keywords keywords))))) - (concat file-name extension))) - -(defun denote--format-front-matter-title (title file-type) - "Format TITLE according to FILE-TYPE for the file's front matter." - (funcall (denote--title-value-function file-type) title)) - -(defun denote--format-front-matter-keywords (keywords file-type) - "Format KEYWORDS according to FILE-TYPE for the file's front matter. -Apply `denote-sluggify' to KEYWORDS." - (let ((kws (denote-sluggify-keywords keywords))) - (funcall (denote--keywords-value-function file-type) kws))) - -(defun denote--path (title keywords dir id file-type signature) - "Return path to new file. -Use ID, TITLE, KEYWORDS, FILE-TYPE and SIGNATURE to construct -path to DIR." - (denote-format-file-name - dir id keywords title (denote--file-extension file-type) signature)) - -;; 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." - (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." - (format-time-string "[%F %a %R]" date)) - -(defun denote-date-iso-8601 (date) - "Format DATE according to ISO 8601 standard." - (format-time-string "%F" date)) - -(defun denote--date (date file-type) - "Expand DATE in an appropriate format for FILE-TYPE." - (let ((format denote-date-format)) - (cond - (format - (format-time-string format date)) - ((when-let ((fn (denote--date-format-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. - -Arguments TITLE, KEYWORDS, DATE, ID, DIRECTORY, FILE-TYPE, -TEMPLATE, and SIGNATURE should be valid for note creation." - (let* ((path (denote--path title keywords directory id file-type signature)) - (buffer (find-file path)) - (header (denote--format-front-matter - title (denote--date date file-type) keywords - id - file-type))) - (with-current-buffer buffer - (insert header) - (insert template)))) - -(defun denote--dir-in-denote-directory-p (directory) - "Return non-nil if DIRECTORY is in variable `denote-directory'." - (and 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))))) - (if (memq type (denote--file-type-keys)) - type - (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--valid-date - 'denote-valid-date-p - "2.3.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, return nil." - (if (and (or (numberp date) (listp date)) - (decode-time date)) - date - (date-to-time (denote--date-add-current-time date)))) - -(defun denote-parse-date (date) - "Return DATE as an appropriate value for the `denote' command. -Pass DATE through `denote-valid-date-p' and use its return value. -If either that or DATE is nil, return `current-time'." - (or (denote-valid-date-p date) (current-time))) - -(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--id-exists-p (identifier) - "Return non-nil if IDENTIFIER already exists." - (seq-some - (lambda (file) - (string-prefix-p identifier (file-name-nondirectory file))) - (append (denote-directory-files) (denote--buffer-file-names)))) - -(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) - (let ((id (denote-retrieve-filename-identifier name))) - (puthash id t ids))) - ids)) - -(defun denote--find-first-unused-id (id used-ids) - "Return the first unused id starting at ID from USED-IDS. -USED-IDS is a hash-table of all used IDs. If ID is already used, -increment it 1 second at a time until an available id is found." - (let ((current-id id)) - (while (gethash current-id used-ids) - (setq current-id (denote-get-identifier (time-add (date-to-time current-id) 1)))) - current-id)) - -(make-obsolete 'denote-barf-duplicate-id nil "2.1.0") - -(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-buffer-after-creation' -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-buffer-after-creation - (or force-save denote-save-buffer-after-creation)) - (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 - (when denote-file-history - (file-name-nondirectory (pop denote-file-history))) - denote-title-prompt-current-default)) - (path)) - (if in-background - (save-window-excursion - (call-interactively command) - (setq path (buffer-file-name))) - (call-interactively command) - (setq path (buffer-file-name))) - path)) - -;;;###autoload -(defun denote (&optional title keywords file-type subdirectory 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. - -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 `denote-file-type'. - -- SUBDIRECTORY 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 SUBDIRECTORY 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 - (let ((args (make-vector 7 nil))) - (dolist (prompt denote-prompts) - (pcase prompt - ('title (aset args 0 (denote-title-prompt - (when (and (not denote-ignore-region-in-denote-command) - (use-region-p)) - (buffer-substring-no-properties - (region-beginning) - (region-end)))))) - ('keywords (aset args 1 (denote-keywords-prompt))) - ('file-type (aset args 2 (denote-file-type-prompt))) - ('subdirectory (aset args 3 (denote-subdirectory-prompt))) - ('date (aset args 4 (denote-date-prompt))) - ('template (aset args 5 (denote-template-prompt))) - ('signature (aset args 6 (denote-signature-prompt))))) - (append args nil))) - (let* ((title (or title "")) - (file-type (denote--valid-file-type (or file-type denote-file-type))) - (kws (denote-keywords-sort keywords)) - (date (denote-parse-date date)) - (id (denote--find-first-unused-id - (denote-get-identifier date) - (denote--get-all-used-ids))) - (directory (if (denote--dir-in-denote-directory-p subdirectory) - (file-name-as-directory subdirectory) - (denote-directory))) - (template (if (stringp template) - template - (or (alist-get template denote-templates) ""))) - (signature (or signature ""))) - (denote--prepare-note title kws date id directory file-type template signature) - (when denote-save-buffer-after-creation (save-buffer)) - (denote--keywords-add-to-history keywords) - (run-hooks 'denote-after-new-note-hook))) - -(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 - initial-title - 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 `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-prompt () - "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." - (if (and denote-date-prompt-use-org-read-date - (require 'org nil :no-error)) - (let* ((time (org-read-date nil t)) - (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 - "DATE and TIME for note (e.g. 2022-06-16 14:30): " - nil 'denote-date-history))) - -(defun denote-prompt-for-date-return-id () - "Use `denote-date-prompt' and return it as `denote-id-format'." - (denote-get-identifier (denote-date-prompt))) - -(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 (if def - (format "Select SUBDIRECTORY [%s]: " def) - "Select SUBDIRECTORY: "))) - (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.") - -(defun denote--add-prompts (additional-prompts) - "Add all the elements in the ADDITIONAL-PROMPTS list to `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." - (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)) - (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. - -If TARGET file does not exist, add the user input that was used -to search for it to the minibuffer 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 -\\\\[previous-history-element]." - (interactive (list (denote-file-prompt))) - (if (and target (file-exists-p target)) - (find-file target) - (denote--command-with-features #'denote :use-file-prompt-as-def-title nil nil nil))) - -;;;###autoload -(defun denote-open-or-create-with-command () - "Visit TARGET file in variable `denote-directory'. -If file does not exist, invoke `denote' to create a file. - -If TARGET file does not exist, add the user input that was used -to search for it to the minibuffer 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 -\\\\[previous-history-element]." - (declare (interactive-only t)) - (interactive) - (let ((target (denote-file-prompt))) - (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-capture-p () - "Return non-nil if this is an `org-capture' buffer." - (and (bound-and-true-p org-capture-mode) - (derived-mode-p 'org-mode) - (string-match-p "\\`CAPTURE.*\\.org" (buffer-name)))) - -(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 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. - -If no file type in `denote-file-types' has the file extension, -the file type is assumed to be the first one in `denote-file-types'." - (cond - ((denote--file-type-org-capture-p) 'org) - (file - (let* ((extension (denote-get-file-extension-sans-encryption file)) - (types (denote--file-types-with-extension extension))) - (cond ((null types) - (caar denote-file-types)) - ((= (length types) 1) - (caar types)) - (t - (or (car (seq-find - (lambda (type) - (denote--regexp-in-file-p (plist-get (cdr type) :title-key-regexp) file)) - types)) - (caar types)))))))) - -(defun denote--file-attributes-time (file) - "Return `file-attribute-modification-time' of FILE as identifier." - (denote-get-identifier (file-attribute-modification-time (file-attributes 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 (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." - (unless (string= (expand-file-name old-name) (expand-file-name 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-buffer-after-creation (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))))) - -(defun denote--add-front-matter (file title keywords id file-type &optional save-buffer) - "Prepend front matter to FILE if `denote-file-is-note-p'. -The TITLE, KEYWORDS ID, and FILE-TYPE are passed from the -renaming command and are used to construct a new front matter -block if appropriate. - -With optional SAVE-BUFFER, save the buffer corresponding to FILE." - (when-let ((date (denote--date (date-to-time id) file-type)) - (new-front-matter (denote--format-front-matter title date keywords id file-type))) - (with-current-buffer (find-file-noselect file) - (goto-char (point-min)) - (insert new-front-matter) - (when save-buffer (save-buffer))))) - -(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--edit-front-matter-p (file file-type) - "Test if FILE should be subject to front matter rewrite. -Use FILE-TYPE to look for the front matter lines. This is -relevant for operations that insert or rewrite the front matter -in a Denote note. - -For the purposes of this test, FILE is a Denote note when it -contains a title line, a keywords line or both." - (and (denote--front-matter file-type) - (or (denote--regexp-in-file-p (denote--title-key-regexp file-type) file) - (denote--regexp-in-file-p (denote--keywords-key-regexp file-type) file)))) - -(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." - (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 (denote--get-keywords-line-from-front-matter keywords file-type)) - (delete-region (point) (line-end-position)) - (when save-buffer (save-buffer))))))) - -(define-obsolete-function-alias - 'denote--rewrite-keywords - 'denote-rewrite-keywords - "2.0.0") - -(defun denote-rewrite-front-matter (file title keywords file-type &optional no-confirm) - "Rewrite front matter of note after `denote-rename-file'. -The FILE, TITLE, KEYWORDS, and FILE-TYPE are given by the -renaming command and are used to construct new front matter -values if appropriate. - -With optional NO-CONFIRM, do not prompt to confirm the rewriting -of the front matter. Otherwise produce a `y-or-n-p' prompt to -that effect. - -With optional NO-CONFIRM, save the buffer after performing the -rewrite. Otherwise leave it unsaved for furthter review by the -user." - (when-let ((old-title-line (denote-retrieve-front-matter-title-line file file-type)) - (old-keywords-line (denote-retrieve-front-matter-keywords-line file file-type)) - (new-title-line (denote--get-title-line-from-front-matter title file-type)) - (new-keywords-line (denote--get-keywords-line-from-front-matter keywords file-type))) - (with-current-buffer (find-file-noselect file) - (when (or no-confirm - (y-or-n-p (format - "Replace front matter?\n-%s\n+%s\n\n-%s\n+%s?" - (propertize old-title-line 'face 'denote-faces-prompt-old-name) - (propertize new-title-line 'face 'denote-faces-prompt-new-name) - (propertize old-keywords-line 'face 'denote-faces-prompt-old-name) - (propertize new-keywords-line 'face 'denote-faces-prompt-new-name)))) - (save-excursion - (save-restriction - (widen) - (goto-char (point-min)) - (re-search-forward (denote--title-key-regexp file-type) nil t 1) - (goto-char (line-beginning-position)) - (insert new-title-line) - (delete-region (point) (line-end-position)) - (goto-char (point-min)) - (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 no-confirm (save-buffer)))))))) - -(define-obsolete-function-alias - 'denote--rewrite-front-matter - 'denote-rewrite-front-matter - "2.0.0") - -;;;;; The renaming commands and their prompts - -(defun denote--rename-dired-file-or-prompt () - "Return Dired file at point, else prompt for one. -Throw error if FILE is not regular, else return FILE." - (or (dired-get-filename nil t) - (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." - (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))))) - -;; 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.") - -;;;###autoload -(defun denote-rename-file (file &optional 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 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. - -If a file name component is present, but there is no entry for it in -`denote-prompts', keep it 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, read its file type extension (like .org) and -preserve 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-no-confirm' is set to a non-nil -value. - -If FILE has front matter for TITLE and KEYWORDS, ask to rewrite -their values in order to reflect the new input, unless -`denote-rename-no-confirm' is non-nil. When the -`denote-rename-no-confirm' is nil (the default), do not save the -underlying buffer, thus giving the user the option to -double-check the result, such as by invokling 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. Save -the buffer if `denote-rename-no-confirm' is non-nil. - -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' - -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-prompt)) - (file-type (denote-filetype-heuristics file)) - (file-in-prompt (propertize (file-relative-name file) 'face 'denote-faces-prompt-current-name)) - (date nil) - (title (denote-retrieve-title-or-filename file file-type)) - (keywords (denote-convert-file-name-keywords-to-crm (or (denote-retrieve-filename-keywords 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) - keywords))) - ('signature - (setq signature (denote-signature-prompt - signature - (format "Rename `%s' with SIGNATURE (empty to remove)" file-in-prompt)))) - ('date - (unless (denote-file-has-identifier-p file) - (setq date (denote-date-prompt)))))) - (list file title keywords signature date))) - (setq keywords (denote-keywords-sort - (if (stringp keywords) - (split-string keywords "," :omit-nulls) - keywords))) - (let* ((dir (file-name-directory file)) - (id (or (denote-retrieve-filename-identifier file) - (denote-create-unique-file-identifier file (denote--get-all-used-ids) date))) - ;; TODO 2024-02-13: Should we derive the extension from the - ;; `denote-file-type-prompt' if we are conforming with the - ;; `denote-prompts'? - (extension (denote-get-file-extension file)) - (file-type (denote-filetype-heuristics file)) - (new-name (denote-format-file-name dir id keywords title extension signature)) - (max-mini-window-height denote-rename-max-mini-window-height)) - (when (or denote-rename-no-confirm (denote-rename-file-prompt file new-name)) - (denote-rename-file-and-buffer file new-name) - (denote-update-dired-buffers) - (when (denote-file-is-writable-and-supported-p new-name) - (if (denote--edit-front-matter-p new-name file-type) - (denote-rewrite-front-matter new-name title keywords file-type denote-rename-no-confirm) - (denote--add-front-matter new-name title keywords id file-type denote-rename-no-confirm))) - (run-hooks 'denote-after-rename-file-hook)) - new-name)) - -;;;###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-no-confirm' to a non-nil value)." - (declare (interactive-only t)) - (interactive nil dired-mode) - (if-let ((marks (dired-get-marked-files))) - (let ((used-ids (unless (seq-every-p #'denote-file-has-identifier-p marks) - (denote--get-all-used-ids)))) - (dolist (file marks) - (let* ((file-type (denote-filetype-heuristics file)) - (file-in-prompt (propertize (file-relative-name file) 'face 'denote-faces-prompt-current-name)) - (dir (file-name-directory file)) - (id (or (denote-retrieve-filename-identifier file) - (denote-create-unique-file-identifier file used-ids))) - (title (denote-retrieve-title-or-filename file file-type)) - (keywords (denote-convert-file-name-keywords-to-crm (or (denote-retrieve-filename-keywords file) ""))) - (signature (or (denote-retrieve-filename-signature file) "")) - (extension (denote-get-file-extension 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) - keywords))) - ('signature - (setq signature (denote-signature-prompt - signature - (format "Rename `%s' with SIGNATURE (empty to remove)" file-in-prompt)))) - ('date - (setq id (denote-prompt-for-date-return-id))))) - (setq keywords (denote-keywords-sort - (if (stringp keywords) - (split-string keywords "," :omit-nulls) - keywords))) - (let ((new-name (denote-format-file-name dir id keywords title extension signature))) - (denote-rename-file-and-buffer file new-name) - (when (denote-file-is-writable-and-supported-p new-name) - (if (denote--edit-front-matter-p new-name file-type) - (denote-rewrite-front-matter new-name title keywords file-type :no-confirm) - (denote--add-front-matter new-name title keywords id file-type :save-buffer))) - (run-hooks 'denote-after-rename-file-hook) - (when used-ids - (puthash id t used-ids))))) - (denote-update-dired-buffers)) - (user-error "No marked files; aborting"))) - -(make-obsolete - 'denote-dired-rename-marked-files - 'denote-dired-rename-marked-files-with-keywords - "2.1.0") - -(defalias 'denote-dired-rename-marked-files 'denote-dired-rename-files - "Alias for `denote-dired-rename-files'.") - -;;;###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 `denote-file-type'), - such that it includes the new keywords. - -Run the `denote-after-rename-file-hook' after renaming is done. - -[ 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). ]" - (declare (interactive-only t)) - (interactive nil dired-mode) - (if-let ((marks (dired-get-marked-files))) - (let ((keywords (denote-keywords-sort - (denote-keywords-prompt "Rename marked files with KEYWORDS, overwriting existing (empty to ignore/remove)"))) - (used-ids (unless (seq-every-p #'denote-file-has-identifier-p marks) - (denote--get-all-used-ids)))) - (dolist (file marks) - (let* ((dir (file-name-directory file)) - (id (or (denote-retrieve-filename-identifier file) - (denote-create-unique-file-identifier file used-ids))) - (signature (or (denote-retrieve-filename-signature file) "")) - (file-type (denote-filetype-heuristics file)) - (title (denote-retrieve-title-or-filename file file-type)) - (extension (denote-get-file-extension file)) - (new-name (denote-format-file-name dir id keywords title extension signature))) - (denote-rename-file-and-buffer file new-name) - (when (denote-file-is-writable-and-supported-p new-name) - (if (denote--edit-front-matter-p new-name file-type) - (denote-rewrite-keywords new-name keywords file-type denote-rename-no-confirm) - (denote--add-front-matter new-name title keywords id file-type denote-rename-no-confirm))) - (run-hooks 'denote-after-rename-file-hook) - (when used-ids - (puthash id t used-ids)))) - (denote-update-dired-buffers)) - (user-error "No marked files; aborting"))) - -;;;###autoload -(defun denote-rename-file-using-front-matter (file &optional no-confirm save-buffer) - "Rename FILE using its front matter as input. -When called interactively, FILE is the return value of the -function `buffer-file-name' 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 `denote-file-type'. - -Unless NO-CONFIRM is non-nil (such as with a prefix argument), -ask for confirmation, showing the difference between the old and -the new file names. - -Never modify the identifier of the FILE, if any, 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. - -If NO-CONFIRM is non-nil (such as with a prefix argument) do not -prompt for confirmation while renaming the file. Do it outright. - -If optional SAVE-BUFFER is non-nil (such as with a double prefix -argument), save the corresponding buffer. - -If the user option `denote-rename-no-confirm' is non-nil, -interpret it the same way as a combination of NO-CONFIRM and -SAVE-BUFFER. - -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." - (interactive - (let (no-confirm save-buffer) - (cond - ((and current-prefix-arg (> (prefix-numeric-value current-prefix-arg) 4)) - (setq no-confirm t - save-buffer t)) - (current-prefix-arg - (setq no-confirm t))) - (list buffer-file-name no-confirm save-buffer))) - (unless (denote-file-is-writable-and-supported-p file) - (user-error "The file is not writable or does not have a supported file extension")) - (if-let ((file-type (denote-filetype-heuristics file)) - (title (denote-retrieve-front-matter-title-value file file-type)) - (id (denote-retrieve-filename-identifier file))) - (let* ((keywords (denote-retrieve-front-matter-keywords-value file file-type)) - (signature (or (denote-retrieve-filename-signature file) "")) - (extension (denote-get-file-extension file)) - (dir (file-name-directory file)) - (new-name (denote-format-file-name dir id keywords title extension signature))) - (when (or denote-rename-no-confirm - no-confirm - (denote-rename-file-prompt file new-name)) - (denote-rename-file-and-buffer file new-name) - (denote-update-dired-buffers) - (when (or denote-rename-no-confirm save-buffer) - (save-buffer)) - (run-hooks 'denote-after-rename-file-hook))) - (user-error "No identifier or front matter for title"))) - -;;;###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 `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 (denote-file-is-writable-and-supported-p m) - (denote-file-has-identifier-p m))) - (dired-get-marked-files)))) - (progn - (dolist (file marks) - (denote-rename-file-using-front-matter file :no-confirm denote-rename-no-confirm)) - (denote-update-dired-buffers)) - (user-error "No marked Denote files; aborting"))) - -;;;;;; Interactively modify keywords and rename accordingly - -;;;###autoload -(defun denote-keywords-add (keywords &optional save-buffer) - "Prompt for KEYWORDS to add to the current note's front matter. -When called from Lisp, KEYWORDS is a list of strings. - -Rename the file without further prompt so that its name reflects -the new front matter, per `denote-rename-file-using-front-matter'. - -With an optional SAVE-BUFFER (such as a prefix argument when -called interactively), save the buffer outright. Otherwise leave -the buffer unsaved for further review. - -If the user option `denote-rename-no-confirm' is non-nil, -interpret it the same way as SAVE-BUFFER, making SAVE-BUFFER -reduntant. - -Run `denote-after-rename-file-hook' as a final step." - (interactive (list (denote-keywords-prompt "Add KEYWORDS") current-prefix-arg)) - ;; A combination of if-let and let, as we need to take into account - ;; the scenario in which there are no keywords yet. - (if-let ((file (buffer-file-name)) - ((denote-file-is-note-p file)) - (file-type (denote-filetype-heuristics file))) - (let* ((cur-keywords (denote-retrieve-front-matter-keywords-value file file-type)) - (new-keywords (denote-keywords-sort - (seq-uniq (append keywords cur-keywords))))) - (denote-rewrite-keywords file new-keywords file-type) - (denote-rename-file-using-front-matter file :no-confirm (or denote-rename-no-confirm save-buffer)) - (run-hooks 'denote-after-rename-file-hook)) - (user-error "Buffer not visiting a Denote file"))) - -(defalias 'denote-rename-add-keywords 'denote-keywords-add - "Alias for `denote-keywords-add'.") - -(defun denote--keywords-delete-prompt (keywords) - "Prompt for one or more KEYWORDS. -In the case of multiple entries, those are separated by the -`crm-separator', which typically is a comma. In such a case, the -output is sorted with `string-collate-lessp'." - (let ((choice (denote--keywords-crm keywords "Keywords to remove"))) - (if denote-sort-keywords - (sort choice #'string-collate-lessp) - choice))) - -;;;###autoload -(defun denote-keywords-remove (&optional save-buffer) - "Prompt for keywords in current note and remove them. -Keywords are retrieved from the file's front matter. - -Rename the file without further prompt so that its name reflects -the new front matter, per `denote-rename-file-using-front-matter'. - -With an optional SAVE-BUFFER as a prefix argument, save the -buffer outright. Otherwise leave the buffer unsaved for further -review. - -If the user option `denote-rename-no-confirm' is non-nil, -interpret it the same way as SAVE-BUFFER, making SAVE-BUFFER -reduntant. - -Run `denote-after-rename-file-hook' as a final step." - (declare (interactive-only t)) - (interactive "P") - (if-let ((file (buffer-file-name)) - ((denote-file-is-note-p file)) - (file-type (denote-filetype-heuristics file))) - (when-let ((cur-keywords (denote-retrieve-front-matter-keywords-value file file-type)) - (del-keyword (denote--keywords-delete-prompt cur-keywords))) - (denote-rewrite-keywords - file - (seq-difference cur-keywords del-keyword) - file-type) - (denote-rename-file-using-front-matter file :no-confirm (or denote-rename-no-confirm save-buffer)) - (run-hooks 'denote-after-rename-file-hook)) - (user-error "Buffer not visiting a Denote file"))) - -(defalias 'denote-rename-remove-keywords 'denote-keywords-remove - "Alias for `denote-keywords-remove'.") - -;;;;;; Interactively add or remove file name signature - -;;;###autoload -(defun denote-rename-add-signature (file signature) - "Add to FILE name the SIGNATURE. -In interactive use, prompt for FILE, defaulting either to the current -buffer's file or the one at point in a Dired buffer. Also prompt for -SIGNATURE, using the existing one, if any, as the initial value. - -When called from Lisp, FILE is a string pointing to a file system path -and SIGNATURE is a string. - -Ask for confirmation before renaming the file to include the new -signature. Do it unless the user option `denote-rename-no-confirm' is -set to a non-nil value. - -Once the operation is done, reload any Dired buffers and run the -`denote-after-rename-file-hook'. - -Also see `denote-rename-remove-signature'." - (interactive - (let* ((file (denote--rename-dired-file-or-prompt)) - (file-in-prompt (propertize (file-relative-name file) 'face 'denote-faces-prompt-current-name))) - (list - file - (denote-signature-prompt - (or (denote-retrieve-filename-signature file) "") - (format "Rename `%s' with SIGNATURE (empty to remove)" file-in-prompt))))) - (let* ((type (denote-filetype-heuristics file)) - (title (denote-retrieve-title-or-filename file type)) - (keywords-string (denote-retrieve-filename-keywords file)) - (keywords (when keywords-string (split-string keywords-string "_" :omit-nulls "_"))) - (dir (file-name-directory file)) - (id (or (denote-retrieve-filename-identifier file) - (denote-create-unique-file-identifier file (denote--get-all-used-ids)))) - (extension (denote-get-file-extension file)) - (new-name (denote-format-file-name dir id keywords title extension signature))) - (when (or denote-rename-no-confirm (denote-rename-file-prompt file new-name)) - (denote-rename-file-and-buffer file new-name) - (denote-update-dired-buffers) - (run-hooks 'denote-after-rename-file-hook)))) - -;;;###autoload -(defun denote-rename-remove-signature (file) - "Remove the signature of FILE. -In interactive use, prompt for FILE, defaulting either to the current -buffer's file or the one at point in a Dired buffer. When called from -Lisp, FILE is a string pointing to a file system path. - -Ask for confirmation before renaming the file to remove its signature. -Do it unless the user option `denote-rename-no-confirm' is set to a -non-nil value. - -Once the operation is done, reload any Dired buffers and run the -`denote-after-rename-file-hook'. - -Also see `denote-rename-add-signature'." - (interactive (list (denote--rename-dired-file-or-prompt))) - (when (denote-retrieve-filename-signature file) - (let* ((type (denote-filetype-heuristics file)) - (title (denote-retrieve-title-or-filename file type)) - (keywords-string (denote-retrieve-filename-keywords file)) - (keywords (when keywords-string (split-string keywords-string "_" :omit-nulls "_"))) - (dir (file-name-directory file)) - (id (or (denote-retrieve-filename-identifier file) - (denote-create-unique-file-identifier file (denote--get-all-used-ids)))) - (extension (denote-get-file-extension file)) - (new-name (denote-format-file-name dir id keywords title extension nil))) - (when (or denote-rename-no-confirm (denote-rename-file-prompt file new-name)) - (denote-rename-file-and-buffer file new-name) - (denote-update-dired-buffers) - (run-hooks 'denote-after-rename-file-hook))))) - -;;;;; Creation of front matter - -;;;###autoload -(defun denote-add-front-matter (file title keywords) - "Insert front matter at the top of FILE. - -When called interactively, FILE is the return value of the -function `buffer-file-name'. FILE is checked to determine -whether it is a note for Denote's purposes. - -TITLE is a string. Interactively, it is the user input at the -minibuffer prompt. - -KEYWORDS is a list of strings. Interactively, it is the user -input at the minibuffer prompt. This one supports completion for -multiple entries, each separated by the `crm-separator' (normally -a comma). - -The purpose of this command is to help the user generate new -front matter for an existing note (perhaps because the user -deleted the previous one and could not undo the change). - -This command does not rename the file (e.g. to update the -keywords). To rename a file by reading its front matter as -input, use `denote-rename-file-using-front-matter'. - -Note that this command is useful only for existing Denote notes. -If the user needs to convert a generic text file to a Denote -note, they can use one of the command which first rename the file -to make it comply with our file-naming scheme and then add the -relevant front matter. - -[ 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-29 09:24 +0200. ]" - (interactive - (list - (buffer-file-name) - (denote-title-prompt nil "Add TITLE (empty to ignore)") - (denote-keywords-sort (denote-keywords-prompt "Add KEYWORDS (empty to ignore)")))) - (when-let ((denote-file-is-writable-and-supported-p file) - (id (denote-retrieve-filename-identifier file)) - (file-type (denote-filetype-heuristics file))) - (denote--add-front-matter file title keywords id file-type))) - -(define-obsolete-function-alias - 'denote-change-file-type - 'denote-change-file-type-and-front-matter - "2.1.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 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." - (interactive - (list - (denote--rename-dired-file-or-prompt) - (denote--valid-file-type (or (denote-file-type-prompt) denote-file-type)))) - (let* ((dir (file-name-directory file)) - (old-file-type (denote-filetype-heuristics file)) - (id (or (denote-retrieve-filename-identifier file) "")) - (title (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) "")) - (old-extension (denote-get-file-extension 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 (and (not (eq old-extension new-extension)) - (denote-rename-file-prompt file new-name)) - (denote-rename-file-and-buffer file new-name) - (denote-update-dired-buffers) - (when (denote-file-is-writable-and-supported-p new-name) - (denote--add-front-matter new-name title keywords id new-file-type))))) - -;;;; 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-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")) - -(defvar denote-faces--file-name-regexp - (concat "\\(?11:[\t\s]+\\|.*/\\)?" - "\\(?1:[0-9]\\{4\\}\\)\\(?12:[0-9]\\{2\\}\\)\\(?13:[0-9]\\{2\\}\\)" - "\\(?10:T\\)" - "\\(?2:[0-9]\\{2\\}\\)\\(?14:[0-9]\\{2\\}\\)\\(?15:[0-9]\\{2\\}\\)" - "\\(?:\\(?3:==\\)\\(?4:[^.]*?\\)\\)?" - "\\(?:\\(?5:--\\)\\(?6:[^.]*?\\)\\)?" - "\\(?:\\(?7:__\\)\\(?8:[^.]*?\\)\\)?" - "\\(?9:\\..*\\)?$") - "Regexp of file names for fontification.") - -(defconst denote-faces-file-name-keywords - `((,denote-faces--file-name-regexp - (11 'denote-faces-subdirectory nil t) - (1 'denote-faces-year nil t) - (12 'denote-faces-month nil t) - (13 'denote-faces-day nil t) - (10 'denote-faces-time-delimiter nil t) - (2 'denote-faces-hour nil t) - (14 'denote-faces-minute nil t) - (15 'denote-faces-second nil t) - (3 'denote-faces-delimiter nil t) - (4 'denote-faces-signature nil t) - (5 'denote-faces-delimiter nil t) - (6 'denote-faces-title nil t) - (7 'denote-faces-delimiter nil t) - (8 'denote-faces-keywords nil t) - (9 'denote-faces-extension nil t ))) - "Keywords for fontification of file names.") - -(make-obsolete-variable 'denote-faces-file-name-keywords-for-backlinks nil "2.2.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 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))) - -(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 - -(defgroup denote-link () - "Link facility for Denote." - :group 'denote) - -;;;;; User options - -(defcustom denote-link-backlinks-display-buffer-action - '((display-buffer-reuse-window display-buffer-below-selected) - (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-link-backlinks-display-buffer-action - (quote ((display-buffer-reuse-window - display-buffer-in-side-window) - (side . left) - (slot . 99) - (window-width . 0.3)))) - -See Info node `(elisp) Displaying Buffers' for more details -and/or the documentation string of `display-buffer'." - :type '(cons (choice (function :tag "Display Function") - (repeat :tag "Display Functions" function)) - alist) - :package-version '(denote . "0.1.0") - :group 'denote-link) - -;;;;; 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:" denote-id-regexp "\\)" "]" "\\[.*?]]") - "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 "\\[.*?]" "(denote:" "\\(?1:" denote-id-regexp "\\)" ")") - "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:" denote-id-regexp "\\)" "]]") - "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) - "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'." - (format - (if (or id-only (null description) (string-empty-p description)) - denote-id-only-link-format - (denote--link-format file-type)) - (denote-retrieve-filename-identifier file) - description)) - -(make-obsolete 'denote-link--format-link 'denote-format-link "2.1.0") -(make-obsolete 'denote-link-signature-format nil "2.3.0") - -(defun denote--link-get-description (file) - "Return link description for FILE." - (funcall - (or denote-link-description-function #'denote-link-description-with-signature-and-title) - file)) - -(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 as 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)) - (t title)))) - -(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))) - -;;;###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. - -The DESCRIPTION is returned by the function specified in variable -`denote-link-description-function'. If the region is active, its -content is deleted and can be used as the description of the -link. The default value of `denote-link-description-function' -returns the content of the active region, if any, else the title -of the linked file is used as the description. The title comes -either from the front matter or the file name. Note that if you -change the default value of `denote-link-description-function', -make sure to use the `region-text' parameter. Regardless of the -value of this user option, `denote-link' will always replace the -content of the active region. - -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, the link is also as if ID-ONLY were non-nil. The -default value of `denote-link-description-function' returns an -empty string when the region is empty. Thus, the link will have -no description in this case. - -When called from Lisp, FILE is a string representing a full file -system path. FILE-TYPE is a symbol as described in -`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--link-get-description file)))) - (list file file-type description current-prefix-arg))) - (unless (or (denote--file-type-org-capture-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")) - (let* ((beg (point))) - (denote--delete-active-region-content) - (insert (denote-format-link file description file-type id-only)) - (unless (derived-mode-p 'org-mode) - (make-button beg (point) 'type 'denote-link-button)))) - -(define-obsolete-function-alias - 'denote-link-insert-link - 'denote-insert-link - "2.0.0") - -(defalias 'denote-insert-link 'denote-link - "Alias for `denote-link' command.") - -;;;###autoload -(defun denote-link-with-signature () - "Insert link to file with signature. -Prompt for file using minibuffer completion, limiting the list of -candidates to files with a signature in their file name. - -By default, the description of the link includes the signature, -if present, followed by the file's title, if any. - -For more advanced uses with Lisp, refer to the `denote-link' -function." - (declare (interactive-only t)) - (interactive) - (unless (or (denote--file-type-org-capture-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 (denote-file-prompt "=")) - (type (denote-filetype-heuristics (buffer-file-name))) - (description (denote--link-get-description file))) - (denote-link file type description))) - -(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-prefix-p i (file-name-nondirectory 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-link--find-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))) - -(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))) - (with-temp-buffer - (insert-file-contents current-file) - (denote-link--expand-identifiers regexp)))) - -(defalias 'denote-link-return-forelinks 'denote-link-return-links - "Alias for `denote-link-return-links'.") - -(define-obsolete-function-alias - 'denote-link-find-file - 'denote-find-link - "2.0.0") - -;;;###autoload -(defun denote-find-link () - "Use minibuffer completion to visit linked file." - (declare (interactive-only t)) - (interactive) - (find-file - (concat - (denote-directory) - (denote-link--find-file-prompt - (or (denote-link-return-links) - (user-error "No links found")))))) - -(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-in-xrefs id)))) - -(define-obsolete-function-alias - 'denote-link-find-backlink - 'denote-find-backlink - "2.0.0") - -;;;###autoload -(defun denote-find-backlink () - "Use minibuffer completion to visit backlink to current file. - -Like `denote-find-link', but select backlink to follow." - (declare (interactive-only t)) - (interactive) - (find-file - (denote-get-path-by-id - (denote-extract-id-from-string - (denote-link--find-file-prompt - (or (denote-link-return-backlinks) - (user-error "No backlinks 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-capture-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--link-get-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-capture-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--link-get-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. - -If TARGET file does not exist, add the user input that was used -to search for it to the minibuffer 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 -\\\\[previous-history-element]. - -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))) - (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-capture-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--link-get-description target) - id-only)) - -(defalias 'denote-link-to-existing-or-new-note 'denote-link-or-create - "Alias for `denote-link-or-create' command.") - -;;;;; Link buttons - -;; Evaluate: (info "(elisp) Button Properties") -;; -;; Button can provide a help-echo function as well, but I think we might -;; not need it. -(define-button-type 'denote-link-button - 'follow-link t - 'face 'denote-faces-link - 'action #'denote-link--find-file-at-button) - -(autoload 'thing-at-point-looking-at "thingatpt") - -(defun denote-link--link-at-point-string () - "Return identifier at point." - (when (or (thing-at-point-looking-at denote-id-only-link-in-context-regexp) - (thing-at-point-looking-at denote-md-link-in-context-regexp) - (thing-at-point-looking-at denote-org-link-in-context-regexp) - ;; Meant to handle the case where a link is broken by - ;; `fill-paragraph' into two lines, in which case it - ;; buttonizes only the "denote:ID" part. Example: - ;; - ;; [[denote:20220619T175212][This is a - ;; test]] - ;; - ;; Maybe there is a better way? - (thing-at-point-looking-at "\\[\\(denote:.*\\)]")) - (match-string-no-properties 0))) - -;; NOTE 2022-06-15: I add this as a variable for advanced users who may -;; prefer something else. If there is demand for it, we can make it a -;; defcustom, but I think it would be premature at this stage. -(defvar denote-link-button-action #'find-file-other-window - "Display buffer action for Denote buttons.") - -(defun denote-link--find-file-at-button (button) - "Visit file referenced by BUTTON." - (let* ((id (denote-extract-id-from-string - (buffer-substring-no-properties - (button-start button) - (button-end button)))) - (file (denote-get-path-by-id id))) - (funcall denote-link-button-action file))) - -;;;###autoload -(defun denote-link-buttonize-buffer (&optional beg end) - "Make denote: links actionable buttons in the current buffer. - -Buttonization applies to the plain text and Markdown file types, -per the user option `denote-file-types'. It will not do anything -in `org-mode' buffers, as buttons already work there. If you do -not use Markdown or plain text, then you do not need this. - -Links work when they point to a file inside the variable -`denote-directory'. - -To buttonize links automatically add this function to the -`find-file-hook'. Or call it interactively for on-demand -buttonization. - -When called from Lisp, with optional BEG and END as buffer -positions, limit the process to the region in-between." - (interactive) - (when (and (not (derived-mode-p 'org-mode)) - buffer-file-name - (denote-file-has-identifier-p buffer-file-name)) - (save-excursion - (goto-char (or beg (point-min))) - (while (re-search-forward denote-id-regexp end t) - (when-let ((string (denote-link--link-at-point-string)) - (beg (match-beginning 0)) - (end (match-end 0))) - (make-button beg end 'type 'denote-link-button)))))) - -(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-link-button-action - (denote-get-path-by-id (match-string 0 link))))) - -(eval-after-load 'markdown-mode - '(add-hook 'markdown-follow-link-functions #'denote-link-markdown-follow)) - -;;;;; Backlinks' buffer - -(define-button-type 'denote-link-backlink-button - 'follow-link t - 'action #'denote-link--backlink-find-file - 'face nil) ; we use this face though we style it later - -(defun denote-link--backlink-find-file (button) - "Action for BUTTON to `find-file'." - (funcall denote-link-button-action (buffer-substring (button-start button) (button-end button)))) - -(defun denote-link--display-buffer (buf) - "Run `display-buffer' on BUF. -Expand `denote-link-backlinks-display-buffer-action'." - (display-buffer - buf - `(,@denote-link-backlinks-display-buffer-action))) - -(define-obsolete-function-alias - 'denote-backlinks-next - 'denote-backlinks-mode-next - "2.3.0") - -(defun denote-backlinks-mode-next (n) - "Use appropriate command for forward motion in backlinks buffer. -With N as a numeric argument, move to the Nth button from point. -A nil value of N is understood as 1. - -When `denote-backlinks-show-context' is nil, move between files -in the backlinks buffer. - -When `denote-backlinks-show-context' is non-nil move between -matching identifiers." - (interactive "p" denote-backlinks-mode) - (unless (derived-mode-p 'denote-backlinks-mode) - (user-error "Only use this in a Denote backlinks buffer")) - (if denote-backlinks-show-context - (xref-next-line) - (forward-button n))) - -(define-obsolete-function-alias - 'denote-backlinks-prev - 'denote-backlinks-mode-previous - "2.3.0") - -(defun denote-backlinks-mode-previous (n) - "Use appropriate command for backward motion in backlinks buffer. -With N as a numeric argument, move to the Nth button from point. -A nil value of N is understood as 1. - -When `denote-backlinks-show-context' is nil, move between files -in the backlinks buffer. - -When `denote-backlinks-show-context' is non-nil move between -matching identifiers." - (interactive "p" denote-backlinks-mode) - (unless (derived-mode-p 'denote-backlinks-mode) - (user-error "Only use this in a Denote backlinks buffer")) - (if denote-backlinks-show-context - (xref-prev-line) - (backward-button n))) - -(defvar denote-backlinks-mode-map - (let ((m (make-sparse-keymap))) - (define-key m "n" #'denote-backlinks-mode-next) - (define-key m "p" #'denote-backlinks-mode-previous) - (define-key m "g" #'revert-buffer) - m) - "Keymap for `denote-backlinks-mode'.") - -(define-derived-mode denote-backlinks-mode xref--xref-buffer-mode "Backlinks" - :interactive nil - "Major mode for backlinks buffers." - (unless denote-backlinks-show-context - (font-lock-add-keywords nil denote-faces-file-name-keywords t))) - -(defun denote-link--prepare-backlinks (fetcher _alist) - "Create backlinks' buffer for the current note. -FETCHER is a function that fetches a list of xrefs. It is called -with `funcall' with no argument like `xref--fetcher'. - -In the case of `denote', `apply-partially' is used to create a -function that has already applied another function to multiple -arguments. - -ALIST is not used in favour of using -`denote-link-backlinks-display-buffer-action'." - (let* ((inhibit-read-only t) - (file (buffer-file-name)) - (file-type (denote-filetype-heuristics file)) - (id (denote-retrieve-filename-identifier-with-error file)) - (buf (format "*denote-backlinks to %s*" id)) - ;; We retrieve results in absolute form and change the absolute - ;; path to a relative path a few lines below. We could add a - ;; suitable function to project-find-functions and the results - ;; would be automatically in relative form, but eventually - ;; notes may not be all under a common directory (or project). - (xref-file-name-display 'abs) - (xref-alist (xref--analyze (funcall fetcher))) - (dir (denote-directory))) - ;; Change the GROUP of each item in xref-alist to a relative path - (mapc (lambda (x) - (setf (car x) (denote-get-file-name-relative-to-denote-directory (car x)))) - xref-alist) - (with-current-buffer (get-buffer-create buf) - (setq-local default-directory dir) - (erase-buffer) - (setq overlay-arrow-position nil) - (denote-backlinks-mode) - (goto-char (point-min)) - (when-let ((title (denote-retrieve-front-matter-title-value file file-type)) - (heading (format "Backlinks to %S (%s)" title id)) - (l (length heading))) - (insert (format "%s\n%s\n\n" heading (make-string l ?-)))) - (if denote-backlinks-show-context - (xref--insert-xrefs xref-alist) - (mapc (lambda (x) - (insert (car x)) - (make-button (line-beginning-position) (line-end-position) :type 'denote-link-backlink-button) - (newline)) - xref-alist)) - (goto-char (point-min)) - (setq-local revert-buffer-function - (lambda (_ignore-auto _noconfirm) - (when-let ((buffer-file-name file)) - (denote-link--prepare-backlinks - (apply-partially #'xref-matches-in-files id - (denote-directory-files nil :omit-current :text-only)) - nil))))) - (denote-link--display-buffer buf))) - -(define-obsolete-function-alias - 'denote-link-backlinks - 'denote-backlinks - "2.0.0") - -;;;###autoload -(defun denote-backlinks () - "Produce a buffer with backlinks to the current note. - -The backlinks' buffer shows the file name of the note linking to -the current note, as well as the context of each link. - -File names are fontified by Denote if the user option -`denote-link-fontify-backlinks' is non-nil. If this user option -is nil, the buffer is fontified by Xref. - -The placement of the backlinks' buffer is controlled by the user -option `denote-link-backlinks-display-buffer-action'. By -default, it will show up below the current window." - (interactive) - (let ((file (buffer-file-name))) - (when (denote-file-is-writable-and-supported-p file) - (let* ((id (denote-retrieve-filename-identifier-with-error file)) - (xref-show-xrefs-function #'denote-link--prepare-backlinks)) - (xref--show-xrefs - (apply-partially #'xref-matches-in-files id - (denote-directory-files nil :omit-current :text-only)) - nil))))) - -(define-obsolete-function-alias - 'denote-link-show-backlinks-buffer - 'denote-show-backlinks-buffer - "2.0.0") - -(defalias 'denote-show-backlinks-buffer 'denote-backlinks - "Alias for `denote-backlinks' command.") - -;;;;; Add links matching regexp - -(defvar denote-link--prepare-links-format "- %s\n" - "Format specifiers for `denote-link-add-links'.") - -;; NOTE 2022-06-16: There is no need to overwhelm the user with options, -;; though I expect someone to want to change the sort order. -(defvar denote-link-add-links-sort nil - "When t, add REVERSE to `sort-lines' of `denote-link-add-links'.") - -(defun denote-link--prepare-links (files current-file-type id-only &optional no-sort) - "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'." - (with-temp-buffer - (mapc - (lambda (file) - (let ((description (denote--link-get-description file))) - (insert - (format - denote-link--prepare-links-format - (denote-format-link file description current-file-type id-only))))) - files) - (unless no-sort - (sort-lines denote-link-add-links-sort (point-min) (point-max))) - (buffer-string))) - -(define-obsolete-function-alias - 'denote-link-add-links - 'denote-add-links - "2.0.0") - -(defun denote-link--insert-links (files current-file-type &optional id-only no-sort) - "Insert at point a typographic list of links matching FILES. - -With CURRENT-FILE-TYPE as a symbol among those specified in -`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'." - (insert (denote-link--prepare-links files current-file-type id-only no-sort))) - -;;;###autoload -(defun denote-add-links (regexp &optional id-only) - "Insert links to all notes matching 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 matching REGEXP") - current-prefix-arg)) - (unless (or (denote--file-type-org-capture-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)) - (beg (point))) - (progn - (denote-link--insert-links files file-type id-only) - (denote-link-buttonize-buffer beg (point))) - (message "No links matching `%s'" regexp)))) - -(defalias 'denote-link-insert-links-matching-regexp 'denote-add-links - "Alias for `denote-add-links' command.") - -(define-obsolete-function-alias - 'denote-link-add-missing-links - 'denote-add-missing-links - "2.0.0") - -(make-obsolete 'denote-add-missing-links nil "2.2.0") - -;;;;; Links from Dired marks - -;; NOTE 2022-07-21: I don't think we need a history for this one. -(defun denote-link--buffer-prompt (buffers) - "Select buffer from BUFFERS visiting Denote notes." - (let ((buffer-file-names (mapcar #'file-name-nondirectory - buffers))) - (completing-read - "Select note buffer: " - (denote--completion-table 'buffer buffer-file-names) - nil t))) - -(defun denote-link--map-over-notes () - "Return list of `denote-file-is-note-p' from Dired marked items." - (when (denote--dir-in-denote-directory-p default-directory) - (seq-filter #'denote-file-is-note-p (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 are Denote notes, meaning that they have our file-naming -scheme, are writable/regular files, and use the appropriate file -type extension (per `denote-file-type'). Furthermore, the marked -files need to be inside the variable `denote-directory' or one of -its subdirectories. No other file is recognised (the list of -marked files ignores whatever does not count as a note for our -purposes). - -The BUFFER is one which visits a Denote note file. If there are -multiple buffers, prompt with completion for one among them. If -there isn't one, 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 - (list - (denote-link--map-over-notes) - (let ((file-names (denote--buffer-file-names))) - (find-file - (cond - ((null file-names) - (user-error "No buffers visiting Denote notes")) - ((eq (length file-names) 1) - (car file-names)) - (t - (denote-link--buffer-prompt file-names))))) - current-prefix-arg) - dired-mode) - (if (null files) - (user-error "No note files to link to") - (when (y-or-n-p (format "Create links at point in %s?" buffer)) - (with-current-buffer buffer - (insert (denote-link--prepare-links - files - (denote-filetype-heuristics (buffer-file-name)) - id-only)) - (denote-link-buttonize-buffer))))) - -;;;;; Define menu - -(defvar denote--menu-contents - '("Denote" - ["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 link" denote-link - :help "Insert link to a file in the `denote-directory'" - :enable (derived-mode-p 'text-mode)] - ["Insert links with regexp" denote-add-links - :help "Insert links to files matching regexp in the `denote-directory'" - :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)] - ["Link to existing note or newly created one with the chosen command" denote-link-or-create-with-command - :help "Insert a link to an existing file, else create it with the given command 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)] - "---" - ["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 search terms. -With optional FULL-DATA return a list in the form of (path id search)." - (let* ((search (and (string-match "::\\(.*\\)\\'" link) - (match-string 1 link))) - (id (if (and search (not (string-empty-p search))) - (substring link 0 (match-beginning 0)) - link)) - (path (denote-get-path-by-id id))) - (cond - (full-data - (list path id search)) - ((and search (not (string-empty-p search))) - (concat path "::" search)) - (path)))) - -;;;###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 -search option akin to that of standard Org `file:' link types. -Read Info node `(org) Search Options'. - -Uses the function `denote-directory' to establish the path to the -file." - (org-link-open-as-file - (denote-link--ol-resolve-link-to-target link) - nil)) - -;;;###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 id (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 () - "Handler for `org-store-link' adding support for denote: links. -Also see the user option `denote-org-store-link-to-heading'." - (when-let ((file (buffer-file-name)) - ((denote-file-is-note-p file)) - (file-id (denote-retrieve-filename-identifier file)) - (description (denote--link-get-description file))) - (let ((heading-links (and denote-org-store-link-to-heading (derived-mode-p 'org-mode)))) - (org-link-store-props - :type "denote" - :description (if heading-links - (denote-link-format-heading-description - description - (denote-link-ol-get-heading)) - description) - :link (if heading-links - (format "denote:%s::#%s" file-id (denote-link-ol-get-id)) - (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." - (let* ((path-id (denote-link--ol-resolve-link-to-target link :full-data)) - (path (file-relative-name (nth 0 path-id))) - (id (nth 1 path-id)) - (search (nth 2 path-id)) - (anchor (file-name-sans-extension path)) - (desc (cond - (description) - (search (format "denote:%s::%s" id search)) - (t (concat "denote:" id))))) - (cond - ((eq format 'html) - (if search - (format "%s" anchor search desc) - (format "%s" anchor desc))) - ((eq format 'latex) (format "\\href{%s}{%s}" (replace-regexp-in-string "[\\{}$%&_#~^]" "\\\\\\&" path) desc)) - ((eq format 'texinfo) (format "@uref{%s,%s}" path desc)) - ((eq format 'ascii) (format "[%s] " desc path)) - ((eq format 'md) (format "[%s](%s)" desc path)) - (t 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-faces-link - :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) - -(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." - (let (title keywords subdirectory date template signature) - (dolist (prompt denote-prompts) - (pcase prompt - ('title (setq title (denote-title-prompt - (when (use-region-p) - (buffer-substring-no-properties - (region-beginning) - (region-end)))))) - ('keywords (setq keywords (denote-keywords-prompt))) - ('subdirectory (setq subdirectory (denote-subdirectory-prompt))) - ('date (setq date (denote-date-prompt))) - ('template (setq template (denote-template-prompt))) - ('signature (setq signature (denote-signature-prompt))))) - (let* ((title (or title "")) - (date (if (or (null date) (string-empty-p date)) - (current-time) - (denote-valid-date-p date))) - (id (denote--find-first-unused-id - (denote-get-identifier date) - (denote--get-all-used-ids))) - (keywords (denote-keywords-sort keywords)) - (directory (if (denote--dir-in-denote-directory-p subdirectory) - (file-name-as-directory subdirectory) - (denote-directory))) - (template (or (alist-get template denote-templates) "")) - (signature (or signature "")) - (front-matter (denote--format-front-matter - title (denote--date nil 'org) keywords - (denote-get-identifier) 'org))) - (setq denote-last-path - (denote--path title keywords directory id 'org signature)) - (denote--keywords-add-to-history keywords) - (concat front-matter template 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) - -(provide 'denote) -;;; denote.el ends here blob - 3ebe86750baf8285471eb08bf6f9438b8f9af040 (mode 644) blob + /dev/null Binary files elpa/denote-2.3.3/denote.info and /dev/null differ blob - f87165c99de687bfbadf03e7d99cf25a1a5a2530 (mode 644) blob + /dev/null --- elpa/denote-2.3.3/dir +++ /dev/null @@ -1,19 +0,0 @@ -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 - df1af93ade6eb525703582ca79f9792cecce80bf (mode 644) blob + /dev/null --- elpa/denote-2.3.3/tests/denote-test.el +++ /dev/null @@ -1,456 +0,0 @@ -;;; denote-test.el --- Unit tests for Denote -*- lexical-binding: t -*- - -;; Copyright (C) 2023 Free Software Foundation, Inc. - -;; Author: Protesilaos Stavrou -;; Maintainer: Denote Development <~protesilaos/denote@lists.sr.ht> -;; URL: https://git.sr.ht/~protesilaos/denote -;; Mailing-List: https://lists.sr.ht/~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: - -;; WORK-IN-PROGRESS - -;;; Code: - -(require 'ert) -(require 'denote) - -(ert-deftest denote-test--denote--make-denote-directory () - "Test that `denote--make-denote-directory' creates the directory." - (should (null (denote--make-denote-directory)))) - -(ert-deftest denote-test--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 denote-test--denote--slug-no-punct () - "Test that `denote--slug-no-punct' removes punctuation from the string. -Concretely, replace with spaces anything that matches the -`denote-excluded-punctuation-regexp' and -`denote-excluded-punctuation-extra-regexp'." - (should (equal (denote--slug-no-punct "This is !@# test") - "This is test"))) - -(ert-deftest denote-test--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 denote-test--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 denote-test--denote--slug-put-equals () - "Test that `denote--slug-put-equals' replaces spaces/underscores with =. -Otherwise do the same as what is described in -`denote-test--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 denote-test--denote-sluggify-signature () - "Test that `denote-sluggify-signature' sluggifies the string for file signatures. -This is like `denote-test--denote-sluggify', except that it also -accounts for what we describe in `denote-test--denote--slug-put-equals'." - (should (equal (denote-sluggify-signature "--- ___ !~!!$%^ This -iS- a tEsT ++ ?? ") - "this=is=a=test"))) - -(ert-deftest denote-test--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 `denote-test--denote-sluggify'." - (should (equal (denote-sluggify-keyword "--- ___ !~!!$%^ This iS a - tEsT ++ ?? ") - "thisisatest"))) - -(ert-deftest denote-test--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 denote-test--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 denote-test--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 `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 denote-test--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 denote-test--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 denote-test--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 denote-test--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 denote-test--denote-file-type-extensions-with-encryption () - "Test that `denote-file-type-extensions-with-encryption' covers encryption. -Extend what we do in `denote-test--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 denote-test--denote-surround-with-quotes () - "Test that `denote-surround-with-quotes' returns a string in quotes." - (should (and (equal (denote-surround-with-quotes "test") "\"test\"") - (equal (denote-surround-with-quotes "") "\"\"") - (equal (denote-surround-with-quotes nil) "\"\"") - (equal (denote-surround-with-quotes 'wrong) "\"\"") - (equal (denote-surround-with-quotes '(wrong)) "\"\"")))) - -(ert-deftest denote-test--denote--format-front-matter () - "Test that `denote--format-front-matter' formats front matter correctly." - (should (and (equal (denote--format-front-matter "" "" '("") "" 'text) - (mapconcat #'identity - '("title: " - "date: " - "tags: " - "identifier: " - "---------------------------\n\n") - "\n")) - - (equal - (denote--format-front-matter - "Some test" "2023-06-05" '("one" "two") - "20230605T102234" 'text) - (mapconcat #'identity - '("title: Some test" - "date: 2023-06-05" - "tags: one two" - "identifier: 20230605T102234" - "---------------------------\n\n") - "\n")))) - - (should (and (equal (denote--format-front-matter "" "" nil "" 'org) - (mapconcat #'identity - '("#+title: " - "#+date: " - "#+filetags: " - "#+identifier: " - "\n") - "\n")) - - (equal - (denote--format-front-matter - "Some test" "2023-06-05" '("one" "two") - "20230605T102234" 'org) - (mapconcat #'identity - '("#+title: Some test" - "#+date: 2023-06-05" - "#+filetags: :one:two:" - "#+identifier: 20230605T102234" - "\n") - "\n")))) - - (should (and (equal (denote--format-front-matter "" "" nil "" 'markdown-yaml) - (mapconcat #'identity - '("---" - "title: \"\"" - "date: " - "tags: []" - "identifier: \"\"" - "---" - "\n") - "\n")) - - (equal - (denote--format-front-matter - "Some test" "2023-06-05" '("one" "two") - "20230605T102234" 'markdown-yaml) - (mapconcat #'identity - '("---" - "title: \"Some test\"" - "date: 2023-06-05" - "tags: [\"one\", \"two\"]" - "identifier: \"20230605T102234\"" - "---" - "\n") - "\n")))) - -(should (and (equal (denote--format-front-matter "" "" nil "" 'markdown-toml) - (mapconcat #'identity - '("+++" - "title = \"\"" - "date = " - "tags = []" - "identifier = \"\"" - "+++" - "\n") - "\n")) - - (equal - (denote--format-front-matter - "Some test" "2023-06-05" '("one" "two") - "20230605T102234" 'markdown-toml) - (mapconcat #'identity - '("+++" - "title = \"Some test\"" - "date = 2023-06-05" - "tags = [\"one\", \"two\"]" - "identifier = \"20230605T102234\"" - "+++" - "\n") - "\n"))))) - -(ert-deftest denote-test--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 - kws - title - (denote--file-extension 'org) - "")) - - (should-error (denote-format-file-name - (denote-directory) - "" - kws - title - (denote--file-extension 'org) - "")) - - (should-error (denote-format-file-name - (denote-directory) - "0123456" - kws - title - (denote--file-extension '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 denote-test--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 denote-test--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 denote-test--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") (caar denote-file-types)) - (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") '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 denote-test--denote-convert-file-name-keywords-to-crm () - "Ensure that `denote-convert-file-name-keywords-to-crm' returns words as comma-separated string." - (should - (and (equal (denote-convert-file-name-keywords-to-crm "_denote_keywords_testing") "denote,keywords,testing") - (equal (denote-convert-file-name-keywords-to-crm "_denote") "denote") - (equal (denote-convert-file-name-keywords-to-crm "") "")))) - -(ert-deftest denote-test--denote-get-identifier () - "Test that `denote-get-identifier' returns an identifier." - (should (and (equal (denote-get-identifier) (format-time-string denote-id-format (current-time))) - (equal (denote-get-identifier "2024-02-01 10:34") "20240201T103400") - (equal (denote-get-identifier 1705644188) "20240119T080308") - (equal (denote-get-identifier '(26026 4251)) "20240119T080307"))) - (should-error (denote-get-identifier "Invalid date"))) - -;;;; denote-journal-extras.el - -(require 'denote-journal-extras) - -(ert-deftest denote-test--denote-journal-extras-daily--title-format () - "Make sure that `denote-journal-extras-daily--title-format' yields the desired format." - (should (and - ;; These three should prompt, but I am here treating the - ;; prompt as if already returned a string. The test for - ;; the `denote-title-prompt' can be separate. - (stringp - (cl-letf (((symbol-function 'denote-title-prompt) #'identity) - (denote-journal-extras-title-format nil)) - (denote-journal-extras-daily--title-format))) - - (stringp - (cl-letf (((symbol-function 'denote-title-prompt) #'identity) - (denote-journal-extras-title-format t)) - (denote-journal-extras-daily--title-format))) - - (stringp - (cl-letf (((symbol-function 'denote-title-prompt) #'identity) - (denote-journal-extras-title-format :some-arbitrary-keyword)) - (denote-journal-extras-daily--title-format))) - - ;; And these return the following values - (string-match-p - "\\<.*?\\>" - (let ((denote-journal-extras-title-format 'day)) - (denote-journal-extras-daily--title-format))) - - (string-match-p - "\\<.*?\\> [0-9]\\{,2\\} \\<.*?\\> [0-9]\\{,4\\}" - (let ((denote-journal-extras-title-format 'day-date-month-year)) - (denote-journal-extras-daily--title-format))) - - (string-match-p - "\\<.*?\\> [0-9]\\{,2\\} \\<.*?\\> [0-9]\\{,4\\} [0-9]\\{,2\\}:[0-9]\\{,2\\} \\<.*?\\>" - (let ((denote-journal-extras-title-format 'day-date-month-year-12h)) - (denote-journal-extras-daily--title-format))) - - (string-match-p - "\\<.*?\\> [0-9]\\{,2\\} \\<.*?\\> [0-9]\\{,4\\} [0-9]\\{,2\\}:[0-9]\\{,2\\}" - (let ((denote-journal-extras-title-format 'day-date-month-year-24h)) - (denote-journal-extras-daily--title-format)))))) - -(provide 'denote-test) -;;; denote-test.el ends here blob - /dev/null blob + 1f7c4b790168032cdf15e155f3d3f3419b908ce0 (mode 644) --- /dev/null +++ elpa/denote-2.3.5/.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 + 619d1542450db444eb03d4af7ce133cd953fdd1b (mode 644) --- /dev/null +++ elpa/denote-2.3.5/CHANGELOG.org @@ -0,0 +1,4558 @@ +#+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 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 + 5b040d3ee212f49e6d097d3d83df82e81933b798 (mode 644) --- /dev/null +++ elpa/denote-2.3.5/README-elpa @@ -0,0 +1,5813 @@ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + 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 2.3.0, +released on 2024-03-24. 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 3.0.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. Overview +3. 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-buffer-after-creation' option +..... 6. The `denote-date-prompt-use-org-read-date' option +.. 2. Create a note from the current Org subtree +.. 3. Create note using Org capture +.. 4. Create note with specific prompts using Org capture +.. 5. Create a note with the region’s contents +.. 6. Open an existing note or create it if missing +.. 7. Maintain separate directory silos for notes +..... 1. Use custom commands to select a silo +..... 2. The `denote-silo-extras.el' +.. 8. Exclude certain directories from all operations +.. 9. Exclude certain keywords from being inferred +.. 10. Use Denote commands from the menu bar or context menu +4. Renaming files +.. 1. Rename a single file +..... 1. The `denote-rename-no-confirm' 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 keywords interactively +.. 8. Rename a file by adding or removing a signature interactively +.. 9. Faces used by rename commands +5. The file-naming scheme +.. 1. Sluggification of file name components +.. 2. User-defined sluggification of file name components +.. 3. Features of the file-naming scheme for searching or filtering +6. Front matter +.. 1. Change the front matter format +.. 2. Regenerate front matter +7. Linking notes +.. 1. Adding a single link +.. 2. The `denote-org-store-link-to-heading' user option +.. 3. Insert link to an Org file with a further pointer to a heading +.. 4. Insert links matching a regexp +.. 5. Insert link to file with signature +.. 6. Insert links from marked files in Dired +.. 7. Link to an existing note or create a new one +.. 8. The backlinks’ buffer +.. 9. Writing metanotes +.. 10. Visiting linked files via the minibuffer +.. 11. Convert `denote:' links to `file:' links +.. 12. Miscellaneous information about links +..... 1. Aliases for the linking commands +..... 2. The `denote-link-description-function' to format links +8. Choose which commands to prompt for +9. Fontification in Dired +10. Automatically rename Denote buffers +.. 1. The `denote-rename-buffer-format' option +11. Use Org dynamic blocks +.. 1. Org dynamic blocks to insert links or backlinks +.. 2. Org dynamic block to insert file contents +12. Sort files by component +13. Keep a journal or diary +.. 1. Journaling with a timer +14. Minibuffer histories +15. Extending Denote +.. 1. Create a new note in any directory +.. 2. Narrow the list of files in Dired +.. 3. Use `dired-virtual-mode' for arbitrary file listings +.. 4. Use Embark to collect minibuffer candidates +.. 5. Search file contents +.. 6. Bookmark the directory with the notes +.. 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. Treat your notes as a project +.. 12. Use the tree-based file prompt for select commands +.. 13. Rename files with Denote in the Image Dired thumbnails buffer +.. 14. Rename files with Denote using `dired-preview' +.. 15. Avoid duplicate identifiers when exporting Denote notes +..... 1. Export Denote notes with Org Mode +..... 2. Export Denote notes with Markdown +16. Installation +.. 1. GNU ELPA package +.. 2. Manual installation +17. Sample configuration +18. For developers or advanced users +19. Troubleshoot Denote in a pristine environment +20. Contributing +.. 1. Wishlist of what we can do to extend Denote +21. Publications about Denote +22. Alternatives to Denote +.. 1. Alternative implementations and further reading +23. 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. Speed up backlinks’ buffer creation? +.. 10. Why do I get “Search failed with status 1” when I search for backlinks? +.. 11. Why do I get a double `#+title' in Doom Emacs? +24. Acknowledgements +25. GNU Free Documentation License +26. Indices +.. 1. Function index +.. 2. Variable index +.. 3. Concept index + + +1 COPYING +═════════ + + Copyright (C) 2022-2024 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 +══════════ + + 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 5 + +[Renaming files] See section 4 + +[Points of entry] See section 3 + +[Writing metanotes] See section 7.9 + +[Keep a journal or diary] See section 13 + + +3 Points of entry +═════════════════ + + There are five 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. + + +3.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. + + 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). + + 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 5 + +[Front matter] See section 6 + +[The denote-prompts option] See section 3.1.1 + +3.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 to + establish a sequential relationship between files (e.g. 1, 1a, 1b, + 1b1, 1b2, …). Signatures have no strictly defined function and are + up to the user to apply as they see fit. One use-case is to + implement Niklas Luhmann’s Zettelkasten system for a sequence of + notes (Folgezettel). Signatures are not included in a file’s front + matter. They are reserved solely for creating a sequence in a file + listing, at least for the time being. To insert a link that + includes the signature, use the command `denote-link-with-signature' + ([Insert link to file with signature]). + + 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 3.1 + +[Convenience commands for note creation] See section 3.1.4 + +[Renaming files] See section 4 + +[The `denote-history-completion-in-prompts' option] See section 3.1.2 + +[The denote-date-prompt-use-org-read-date option] See section 3.1.6 + +[The denote-templates option] See section 3.1.3 + +[Insert link to file with signature] See section 7.5 + +[The file-naming scheme] See section 5 + + +3.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.] + + +3.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 . 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. Below we show some + concrete examples. + + 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. + + 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 3.1.1 + +[Convenience commands for note creation] See section 3.1.4 + + +3.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 3.1 + +[The denote-prompts option] See section 3.1.1 + +[The denote-date-prompt-use-org-read-date option] See section 3.1.6 + +[The denote-templates option] See section 3.1.3 + +◊ 3.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 equivalent to calling `denote' when `denote-prompts' is + │ set to '(subdirectory title keywords)." + │ (declare (interactive-only t)) + │ (interactive) + │ (let ((denote-prompts '(subdirectory title keywords))) + │ (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. + + Now let’s say we want to have a command that (i) asks for a template + and (ii) for a subdirectory ([The denote-templates option]). 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 denote-subdirectory-with-template () + │ "Create note while also prompting for a template and subdirectory. + │ + │ This is equivalent to calling `denote' when `denote-prompts' is + │ set to '(template subdirectory title keywords)." + │ (declare (interactive-only t)) + │ (interactive) + │ (let ((denote-prompts '(template subdirectory title keywords))) + │ (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]). + + + [Convenience commands for note creation] See section 3.1.4 + + [The denote-templates option] See section 3.1.3 + + [The denote-prompts option] See section 3.1.1 + + [Choose which commands to prompt for] See section 8 + + +3.1.5 The `denote-save-buffer-after-creation' option +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The user option `denote-save-buffer-after-creation' controls whether + commands that creeate 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]). + + If `denote-save-buffer-after-creation' is set to a non-nil value, such + buffers are saved automatically. + + +[Points of entry] See section 3 + +[The `denote-save-buffer-after-creation' option] See section 3.1.5 + + +3.1.6 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 3.1.1 + + +3.2 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-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. + + 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-extras-extract-org-subtree' prompts for + keywords. Else the new note has no keywords ([Add or remove keywords + interactively]). + + 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'. + + +[Add or remove keywords interactively] See section 4.7 + + +3.3 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))) + └──── + + [ In the future, we might develop Denote in ways which do not require + such manual intervention. More user feedback is required to + identify the relevant workflows. ] + + 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 + ([Standard note creation]). + + 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'. + + +[Standard note creation] See section 3.1 + + +3.4 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 a title and keywords. 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)) + └──── + + +[Create note using Org capture] See section 3.3 + +[The file-naming scheme] See section 5 + +[Points of entry] See section 3 + + +3.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. + + +3.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 3.1 + +[Points of entry] See section 3 + +[Link to a note or create it if missing] See section 7.7 + + +3.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. They will not read the global value of + `denote-directory'. The global value of `denote-directory' is read + everywhere else except the silos. + + [Use custom commands to select a silo]. + + 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. + + +[Use custom commands to select a silo] See section 3.7.1 + +3.7.1 Use custom commands to select a silo +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + [ As part of version 2.1.0, the contents of this section are formally + provided in the file `denote-silo-extras.el'. We keep this here for + existing users. Otherwise consult the new entry in the manual ([The + `denote-silo-extras.el']). ] + + We implement silos as directory-local values of the user option + `denote-directory'. This means that all Denote commands read from the + local value if they are invoked from that context. For example, if + `~/Videos/recordings' is a silo and `~/Documents/notes' is the + default/global value of `denote-directory' all Denote commands will + read the video’s path when called from there (e.g. by using Emacs’ + `dired'); any other context reads the global value. + + [Maintain separate directory silos for notes]. + + There are cases where the user (i) wants to maintain multiple silos + and (ii) prefers an interactive way to switch between them without + going through Dired. Since this is specific to the user’s workflow, + it is easier to have some custom code for it. The following should be + added to the user’s Denote configuration: + + ┌──── + │ (defvar my-denote-silo-directories + │ `("/home/prot/Videos/recordings" + │ "/home/prot/Documents/books" + │ ;; You don't actually need to include the `denote-directory' here + │ ;; if you use the regular commands in their global context. I am + │ ;; including it for completeness. + │ ,denote-directory) + │ "List of file paths pointing to my Denote silos. + │ This is a list of strings.") + │ + │ (defvar my-denote-commands-for-silos + │ '(denote + │ denote-date + │ denote-subdirectory + │ denote-template + │ denote-type) + │ "List of Denote commands to call after selecting a silo. + │ This is a list of symbols that specify the note-creating + │ interactive functions that Denote provides.") + │ + │ (defun my-denote-pick-silo-then-command (silo command) + │ "Select SILO and run Denote COMMAND in it. + │ SILO is a file path from `my-denote-silo-directories', while + │ COMMAND is one among `my-denote-commands-for-silos'." + │ (interactive + │ (list (completing-read "Select a silo: " my-denote-silo-directories nil t) + │ (intern (completing-read + │ "Run command in silo: " + │ my-denote-commands-for-silos nil t)))) + │ (let ((denote-directory silo)) + │ (call-interactively command))) + └──── + + With this in place, `M-x my-denote-pick-silo-then-command' will use + minibuffer completion to select a silo among the predefined options + and then ask for the command to run in that context. + + Note that `let' binding `denote-directory' can be used in custom + commands and other wrapper functions to override the global default + value of `denote-directory' to select silos. + + To see another example of a wrapper function that `let' binds + `denote-directory', see: + + [Extending Denote: Split an Org subtree into its own note]. + + +[The `denote-silo-extras.el'] See section 3.7.2 + +[Maintain separate directory silos for notes] See section 3.7 + +[Extending Denote: Split an Org subtree into its own note] See section +3.2 + + +3.7.2 The `denote-silo-extras.el' +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The `denote-silo-extras.el' provides optional convenience functions + for working with silos ([Maintain separate directory silos for + notes]). Start by loading the relevant library: + + ┌──── + │ (require 'denote-silo-extras) + └──── + + The user option `denote-silo-extras-directories' specifies a list of + directories that the user has set up as `denote-directory' silos. + + The command `denote-silo-extras-create-note' prompts for a directory + among `denote-silo-extras-directories' and runs the `denote' command + from there. + + Similar to the above, the command `denote-silo-extras-open-or-create' + prompts for a directory among `denote-silo-extras-directories' and + runs the `denote-open-or-create' command from there. + + The command `denote-silo-extras-select-silo-then-command' prompts with + minibuffer completion for a directory among + `denote-silo-extras-directories'. Once the user selects a silo, a + second prompt asks for a Denote note-creation command to call from + inside that silo ([Points of entry]). + + +[Maintain separate directory silos for notes] See section 3.7 + +[Points of entry] See section 3 + + +3.8 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 3.7 + +[For developers or advanced users] See section 18 + + +3.9 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'. + + +3.10 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) + └──── + + +4 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. + + 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]. + + +[The file-naming scheme] See section 5 + +[Linking notes] See section 7 + +[Automatically rename Denote buffers] See section 10 + +4.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'. If a file name component is present, + but there is no entry for it in `denote-prompts', keep it 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, it asks for confirmation, showing the difference + between old and new file names. It does not ask for confirmation if + the user option `denote-rename-no-confirm' is set to a non-nil value + ([The `denote-rename-no-confirm' option]). + + If `FILE' has front matter for `TITLE' and `KEYWORDS', + `denote-rename-file' asks to rewrite their values in order to reflect + the new input, unless `denote-rename-no-confirm' is non-nil. When the + `denote-rename-no-confirm' is nil (the default), the underlying buffer + is not saved, giving the user the change to invoking + `diff-buffer-with-file' to double-check the effect. 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'), the `denote-rename-file' adds front + matter to the top of it and leaves the buffer unsaved for further + inspection. It actually saves the buffer if `denote-rename-no-confirm' + is non-nil ([Front matter]). + + 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 4.3 + +[The `denote-prompts' option] See section 3.1.1 + +[The `denote-rename-no-confirm' option] See section 4.1.1 + +[Front matter] See section 6 + +4.1.1 The `denote-rename-no-confirm' option +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The user option `denote-rename-no-confirm' makes all commands that + rename files not prompt for confirmation and save buffers outright + ([Renaming files]). + + 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. + + 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. It also does not save the affected file’s + buffer to let the user inspect and confirm the changes (such as by + invoking the command `diff-buffer-with-file'). + + With this user option bound to a non-nil value, buffers are saved as + well. The assumption is that the user who opts in to this feature is + familiar with the `denote-rename-file' (or related) operation and + knows it is reliable. + + Specialised 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 4 + +[Rename multiple files interactively] See section 4.3 + + +4.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 + └──── + + + 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. + + If called interactively with a prefix argument (`C-u' by default) or + from Lisp with a non-nil `NO-CONFIRM' argument, this “yes or no” + prompt is skipped and the renaming is done outright. + + If called interactively with a double prefix argument (`C-u C-u' by + default) or from Lisp with a non-nil `SAVE-BUFFER' argument, the + buffer is saved after the front matter is updated and the file is + renamed. + + If the user option `denote-rename-no-confirm' is non-nil, it is + interpreted the same way as a combination of `NO-CONFIRM' and + `SAVE-BUFFER' ([The `denote-rename-no-confirm' 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. + + +[Rename a single file] See section 4.1 + +[Front matter] See section 6 + +[The `denote-rename-no-confirm' option] See section 4.1.1 + + +4.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. 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 4.1 + +[Rename by writing only keywords] See section 4.4 + +[Rename multiple files based on their front matter] See section 4.5 + + +4.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). ] + + +4.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 4.2 + +[Rename multiple files interactively] See section 4.3 + + +4.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 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. + + +4.7 Rename a file by adding or removing keywords interactively +────────────────────────────────────────────────────────────── + + The commands `denote-keywords-add' and `denote-keywords-remove' + streamline the process of interactively updating a file’s keywords in + the front matter and renaming it accordingly. + + The `denote-keywords-add' 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]). + + Similarly, the `denote-keywords-remove' removes one or more keywords + from the list of existing keywords and then renames the file + accordingly. + + Both commands accept an optional prefix argument to automatically save + the buffer. Similarly, they both interpret a non-nil value for the + user option `denote-rename-no-confirm' the same as the prefix argument + ([The `denote-rename-no-confirm' option]). + + Furthermore, both commands call the `denote-after-rename-file-hook' as + a final step after carrying out their task. + + Aliases for these commands are: `denote-rename-add-keywords' and + `denote-rename-remove-keywords'. + + +[Standard note creation] See section 3.1 + +[Rename a single file based on its front matter] See section 4.2 + +[The `denote-rename-no-confirm' option] See section 4.1.1 + + +4.8 Rename a file by adding or removing a signature interactively +───────────────────────────────────────────────────────────────── + + The commands `denote-rename-add-signature' and + `denote-rename-remove-signature' streamline the process of + interactively adding or removing a signature from a given file ([The + file-naming scheme]). + + 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. + + Both commands ask for confirmation before carrying out their action. + They do so unless the user option `denote-rename-no-confirm' is set to + a non-nil value ([The `denote-rename-no-confirm' option]). They also + both take care to reload any Dired buffers and run the + `denote-after-rename-file-hook' as a final step. + + +[The file-naming scheme] See section 5 + +[The `denote-rename-no-confirm' option] See section 4.1.1 + + +4.9 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' + + +5 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 a string of alphanumeric characters in the + `SIGNATURE' field. Signatures have no clearly defined purpose and are + up to the user to define. One use-case is to use them to establish + sequential relations between files (e.g. 1, 1a, 1b, 1b1, 1b2, …). + + Signatures are an optional extension to Denote’s file-naming scheme. + 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 `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. + + 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 3 + +[Sluggification of file name components] See section 5.1 + +[Features of the file-naming scheme for searching or filtering] See +section 5.3 + +5.1 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. The constant + `denote-excluded-punctuation-regexp' holds the relevant value. + + ⁃ 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 6 + +[Features of the file-naming scheme for searching or filtering] See +section 5.3 + +[User-defined sluggification of file name components] See section 5.2 + + +5.2 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 (denote--slug-no-punct 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 (denote--slug-no-punct 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. + + 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 5.1 + +[The file-naming scheme] See section 5 + + +5.3 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. + + +6 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'.. + + +6.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. + + +6.2 Regenerate front matter +─────────────────────────── + + Sometimes the user needs to produce new front matter for an existing + note. Perhaps because they accidentally deleted a line and could not + undo the operation. The command `denote-add-front-matter' can be used + for this very purpose. + + In interactive use, `denote-add-front-matter' must be invoked from a + buffer that visits a Denote note. It prompts for a title and then for + keywords. These are the standard prompts we already use for note + creation, so the keywords’ prompt allows minibuffer completion and the + input of multiple entries, each separated by a comma ([Points of + entry]). + + The newly created front matter is added to the top of the file. + + This command does not rename the file (e.g. to update the keywords). + To rename a file by reading its front matter as input, the user can + rely on `denote-rename-file-using-front-matter' ([Renaming files]). + + Note that `denote-add-front-matter' is useful only for existing Denote + notes. If the user needs to convert a generic text file to a Denote + note, they can use one of the command which first rename the file to + make it comply with our file-naming scheme and then add the relevant + front matter. + + +[Points of entry] See section 3 + +[Renaming files] See section 4 + + +7 Linking notes +═══════════════ + + Denote offers several commands for linking between notes. + + All links target files which are Denote files. This means that they + have our file-naming scheme. Files need to be inside the + `denote-directory' or one of its subdirectories. No other file is + recognised. + + The following sections delve into the details. + + +7.1 Adding a single link +──────────────────────── + + The `denote-link' command inserts a link at point to a file specified + at the minibuffer prompt ([The `denote-org-store-link-to-heading' user + option]). 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]]'. The user + might prefer its simplicity. + + By default, the description of the link is taken from the signature of + the file, if present, and the target file’s front matter’s title or, + if that is not available, from the file name. If the region is + active, its text is used as the link’s description instead. If the + active region has no text, the inserted link uses just the identifier, + as with the `C-u' prefix mentioned above. + + Inserted links are automatically buttonized and remain active for as + long as the buffer is available. In Org this is handled by the major + mode: the `denote:' hyperlink type works exactly like the standard + `file:'. In Markdown and plain text, Denote performs the + buttonization of those links. To buttonize links in existing files + while visiting them, the user must add this snippet to their setup (it + already excludes Org): + + ┌──── + │ (add-hook 'find-file-hook #'denote-link-buttonize-buffer) + └──── + + The `denote-link-buttonize-buffer' is also an interactive function in + case the user needs it. + + Links are created only for files which qualify as a “note” for our + purposes ([Linking notes]). + + Links are styled with the `denote-faces-link' face, which looks + exactly like an ordinary link by default. This is just a convenience + for the user/theme in case they want `denote:' links to remain + distinct from other links. + + 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 aforementioned + buttonization. Interested users can refer to the function + `denote-link-markdown-follow' for the implementation details. + + +[The `denote-org-store-link-to-heading' user option] See section 7.2 + +[Linking notes] See section 7 + + +7.2 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 (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 versions prior to `2.3.0'. + + Note that the optional extension `denote-org-extras.el' defines the + command `denote-org-extras-link-to-heading', which always links to a + file+heading regardless of the aforementioned user option ([Insert + link to an Org file with a further pointer to a 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. ] + + +[Insert link to an Org file with a further pointer to a heading] See +section 7.3 + + +7.3 Insert link to an Org file with a further pointer to a heading +────────────────────────────────────────────────────────────────── + + As part of the optional `denote-org-extras.el' extension, 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 ([The file-naming scheme]). + + This feature is similar to the concept of the user option + `denote-org-store-link-to-heading' ([The + `denote-org-store-link-to-heading' user option]). 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. + + +[The file-naming scheme] See section 5 + +[The `denote-org-store-link-to-heading' user option] See section 7.2 + + +7.4 Insert links matching a regexp +────────────────────────────────── + + The command `denote-add-links' adds links at point matching a regular + expression or plain string. The links 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]). + + +[Linking notes] See section 7 + + +7.5 Insert link to file with signature +────────────────────────────────────── + + The command `denote-link-with-signature' prompts for a file among + those that contain a `==SIGNATURE' and inserts a link to it. The + description of the link includes the text of the signature and that of + the file’s title, if any. For example, a link to the following file: + + ┌──── + │ 20230925T144303==abc--my-first-signature-note__denote_testing.txt + └──── + + + will get this link: `[[denote:20230925T144303][abc My first signature + note]]'. + + For more advanced uses, refer to the doc string of the `denote-link' + function. + + +7.6 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]). + + +[Insert links matching a regexp] See section 7.4 + +[Adding a single link] See section 7.1 + +[Linking notes] See section 7 + + +7.7 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. + + `denote-link-or-create-with-command' + This 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. + + 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 7 + +[Standard note creation] See section 3.1 + +[Adding a single link] See section 7.1 + +[The `denote-save-buffer-after-creation' option] See section 3.1.5 + +[Points of entry] See section 3 + +[Points of entry] See section 3 + +[Open an existing note or create it if missing] See section 3.6 + + +7.8 The backlinks’ buffer +───────────────────────── + + The command `denote-backlinks' produces a bespoke buffer which + displays backlinks to the current note. A “backlink” is a link back + to the present entry. + + By default, the backlinks’ buffer is designed to display the file name + of the note linking to the current entry. Each file name is presented + on its own line, like this: + + ┌──── + │ Backlinks to "On being honest" (20220614T130812) + │ ------------------------------------------------ + │ + │ 20220614T145606--let-this-glance-become-a-stare__journal.txt + │ 20220616T182958--feeling-butterflies-in-your-stomach__journal.txt + └──── + + When the user option `denote-backlinks-show-context' is non-nil, the + backlinks’ buffer displays the line on which a link to the current + note occurs. It also shows multiple occurrences, if present. It + looks like this (and has 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. + + [Speed up backlinks’ buffer creation?] + + The backlinks’ buffer runs the major-mode `denote-backlinks-mode'. It + binds keys to move between links with `n' (next) and `p' (previous). + These are stored in the `denote-backlinks-mode-map' (use `M-x + describe-mode' (`C-h m') in an unfamiliar buffer to learn more about + it). When the user option `denote-backlinks-show-context' is non-nil, + all relevant Xref key bindings are fully functional: again, check + `describe-mode'. + + The backlinking facility uses Emacs’ built-in Xref infrastructure. 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?] + + 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]). + + The placement of the backlinks’ buffer is subject to the user option + `denote-link-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. The doc string of our user option + includes a sample configuration that places the buffer in a left side + window instead. Reproducing it here for the sake of convenience: + + ┌──── + │ (setq denote-link-backlinks-display-buffer-action + │ '((display-buffer-reuse-window + │ display-buffer-in-side-window) + │ (side . left) + │ (slot . 99) + │ (window-width . 0.3))) + └──── + + +[Speed up backlinks’ buffer creation?] See section 23.9 + +[Why do I get “Search failed with status 1” when I search for +backlinks?] See section 23.10 + +[Visiting linked files via the minibuffer] See section 7.10 + + +7.9 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 7.4 + +[Insert links from marked files in Dired] See section 7.6 + + +7.10 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 15 + +[The backlinks’ buffer] See section 7.8 + + +7.11 Convert `denote:' links to `file:' links +───────────────────────────────────────────── + + 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 + ([Insert link to an Org file with a further pointer to a + heading]). + + 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. + + +[Insert link to an Org file with a further pointer to a heading] See +section 7.3 + + +7.12 Miscellaneous information about links +────────────────────────────────────────── + +7.12.1 Aliases for the linking commands +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + For convenience, the `denote-link' command has an alias called + `denote-insert-link'. The `denote-backlinks' can also be used as + `denote-show-backlinks-buffer'. While `denote-add-links' is aliased + `denote-link-insert-links-matching-regexp'. The purpose of these + aliases is to offer alternative, more descriptive names of select + commands. + + +7.12.2 The `denote-link-description-function' to format links +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The user option `denote-link-description-function' takes as its value + the symbol of a function. This is used to format the text of the link. + The default function inserts the title. If the file has a signature, + it includes that as well, prepending it to the title. + + The function specified accepts a single `FILE' argument and returns + the description as a string. + + +8 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 3.6 + +[Link to a note or create it if missing] See section 7.7 + +[Points of entry] See section 3 + +[Write your own convenience commands] See section 3.1.4.1 + + +9 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 5 + + +10 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 3 + +[The file-naming scheme] See section 5 + +[The denote-rename-buffer-format] See section 10.1 + +[Renaming files] See section 4 + +10.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' of the file. + • The `%i' is the Denote `IDENTIFIER' of the file. + • 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 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: + + ┌──── + │ ;; 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") + └──── + + 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 5 + + +11 Use Org dynamic blocks +═════════════════════════ + + [ As part of version 2.3.0, all dynamic blocks are defined in the file + `denote-org-extras.el'. The file which was once called + `denote-org-dblock.el' contains 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. ] + + 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 ([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 11.1 + +[Org dynamic block to insert file contents] See section 11.2 + +[Writing metanotes] See section 7.9 + +11.1 Org dynamic blocks to insert links or backlinks +──────────────────────────────────────────────────── + + [ As part of version 2.3.0, all dynamic blocks are defined in the file + `denote-org-extras.el'. The file which was once called + `denote-org-dblock.el' contains 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. ] + + The `denote-links' block can be inserted at point with the command + `denote-org-extras-dblock-insert-links' or by manually including the + following in an Org file: + + ┌──── + │ #+BEGIN: denote-links :regexp "YOUR REGEXP HERE" :sort-by-component nil :reverse-sort nil :id-only nil + │ + │ #+END: + └──── + + + 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 `denote-add-links' command + ([Insert links matching a regexp]). 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 `: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-extras-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 ([Linking + notes]). + + • 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). + + The same as above except for the `:regexp' parameter are true for the + `denote-backlinks' block. The block can be inserted at point with the + command `denote-org-extras-dblock-insert-backlinks' or by manually + writing this in an Org file: + + ┌──── + │ #+BEGIN: denote-backlinks :sort-by-component nil :reverse-sort nil :id-only nil + │ + │ #+END: + └──── + + + Finally, the `denote-missing-links' block is available with the + command `denote-org-extras-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. 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. + + +[Insert links matching a regexp] See section 7.4 + +[Linking notes] See section 7 + + +11.2 Org dynamic block to insert file contents +────────────────────────────────────────────── + + [ As part of version 2.3.0, all dynamic blocks are defined in the file + `denote-org-extras.el'. The file which was once called + `denote-org-dblock.el' contains 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. ] + + 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 ([Insert links matching a + regexp]). + + 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-extras-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" + │ + │ #+END: + └──── + + + To fully control the output, include these additional optional + parameters, which are described further below: + + ┌──── + │ #+BEGIN: denote-files :regexp "YOUR REGEXP HERE" :sort-by-component nil :reverse-sort nil :no-front-matter nil :file-separator nil :add-links nil + │ + │ #+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 `: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-extras-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-extras-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 + ([Linking notes]). 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 11 + +[Insert links matching a regexp] See section 7.4 + +[Org dynamic blocks to insert links or backlinks] See section 11.1 + +[Linking notes] See section 7 + + +12 Sort files by component +══════════════════════════ + + The `denote-sort.el' file is an optional extension to the core + functionality of Denote, which empowers users to sort files by the + given file name component ([The file-naming scheme]). + + The command `denote-sort-dired' produces a Dired file listing with a + flat, filtered, and sorted set of files from the `denote-directory'. + It does so by means of three minibuffer prompts: + + 1. It first asks for a regular expression with which to match Denote + files. Remember that due to Denote’s efficient file-naming scheme, + you do not need to write some complex regular expression. Even + 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'. + 3. Finally, it asks a “yes or no” on whether to reverse the sort + order. + + The resulting Dired listing is a regular Dired buffer, unlike that of + `dired-virtual-mode' ([Use `dired-virtual-mode' for arbitrary file + listings]). + + The dynamic Org blocks that Denote defines to insert file contents + also use this feature ([Org dynamic block to insert file contents]). + + DEVELOPMENT NOTE as of 2023-11-30 07:24 +0200: The sort mechanism will + be incorporated in more functions on a case-by-case basis, subject to + user feedback. For the time being, I am not documenting the functions + that are only accessed from Lisp. Do not hesitate to contact me + (Protesilaos) in person or on one of the development sources (mailing + list or GitHub/GitLab mirror) if you have a use-case where sorting + seems useful. I am happy to help but do not want to roll this feature + everywhere before eliciting relevant feedback: once we add it, we are + not going back. + + +[The file-naming scheme] See section 5 + +[Use `dired-virtual-mode' for arbitrary file listings] See section 15.3 + +[Org dynamic block to insert file contents] See section 11.2 + + +13 Keep a journal or diary +══════════════════════════ + + Denote provides a general-purpose mechanism to create new files that + broadly count as “notes” ([Points of entry]). Such files can be daily + entries in a journal. While it is possible to use the generic + `denote' command to maintain a journal, we provide an optional set of + convenience options and commands as part of + `denote-journal-extras.el'. To use those, add the following the + Denote configuration: + + ┌──── + │ (require 'denote-journal-extras) + └──── + + The command `denote-journal-extras-new-entry' creates a new entry in + the journal. Such a file has the `denote-journal-extras-keyword', + which is `journal' by default ([The file-naming scheme]). The user + can set this keyword to an arbitrary string (single word is + preferred). New journal entries can be stored in the + `denote-directory' or subdirectory thereof. To make it easier for the + user, the new journal entry will be placed in + `denote-journal-extras-directory', which defaults to a subdirectory of + `denote-directory' called `journal'. + + If `denote-journal-extras-directory' is nil, the `denote-directory' is + used. Journal entries will thus be in a flat listing together with + all other notes. They can still be retrieved easily by searching for + the `denote-journal-extras-keyword' ([Features of the file-naming + scheme for searching or filtering]). + + Furthermore, the command `denote-journal-extras-new-entry' will use + the current date as the title of the new entry. The exact format is + controlled by the user option `denote-journal-extras-title-format'. + Acceptable values for `denote-journal-extras-title-format' and their + corresponding styles are: + + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + Symbol Style + ──────────────────────────────────────────────────────────── + day Monday + day-date-month-year Monday 19 September 2023 + day-date-month-year-24h Monday 19 September 2023 20:49 + day-date-month-year-12h Monday 19 September 2023 08:49 PM + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + For example: + + ┌──── + │ (setq denote-journal-extras-title-format 'day-date-month-year) + └──── + + If the value of this user option is `nil', then + `denote-journal-extras-new-entry' will prompt for a title. + + The `denote-journal-extras-new-entry' command also accepts an optional + `DATE' argument. When called internactively, this is a universal + prefix (e.g. `C-u' with the default key bindings). With `DATE', it + prompts for a date to create a new journal entry for. The date prompt + can optionally use the Org date+calendar selection interface ([The + `denote-date-prompt-use-org-read-date' option]). + + In terms of workflow, using the current date as the title is better + for maintaining a daily journal. A prompt for an arbitrary title is + more suitable for those who like to keep a record of something like a + thought or event (though this can also be achieved by the regular + `denote' command or maybe `denote-subdirectory'). + + The `denote-journal-extras-new-entry' command calls the normal hook + `denote-journal-extras-hook' after it is done. The user can leverage + this to produce consequences therefrom, such as to set a timer with + the `tmr' package from GNU ELPA ([Journaling with a timer]). + + The command `denote-journal-extras-new-or-existing-entry' locates an + existing journal entry or creates a new one. A journal entry is one + that has `denote-journal-extras-keyword' as part of its file name. If + there are multiple journal entries for the current date, it prompts + for one among them using minibuffer completion. If there is only one, + it visits it outright. If there is no journal entry, it creates one + by calling `denote-journal-extra-new-entry' (as described above). + + 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. + + +[Points of entry] See section 3 + +[The file-naming scheme] See section 5 + +[Features of the file-naming scheme for searching or filtering] See +section 5.3 + +[The `denote-date-prompt-use-org-read-date' option] See section 3.1.6 + +[Journaling with a timer] See section 13.1 + +13.1 Journaling with a timer +──────────────────────────── + + [ Revised as part of version 2.1.0 to conform with how we now tend to + the needs of users who use Denote for journaling purposes ([Keep a + journal or diary]). ] + + Sometimes journaling is done with the intent to hone one’s writing + skills. Perhaps you are learning a new language or wish to + communicate your ideas with greater clarity and precision. As with + everything that requires a degree of sophistication, you have to work + for it—write, write, write! + + One way to test your progress is to set a timer. It helps you gauge + your output and its quality. To use a timer with Emacs, consider the + `tmr' package. A new timer can be set with something like this: + + ┌──── + │ ;; Set 10 minute timer with the given description + │ (tmr "10" "Practice writing in my journal") + └──── + + To make this timer start as soon as a new journal entry is created + with the command `denote-journal-extras-new-entry', add a function to + the `denote-journal-extras-hook'. For example: + + ┌──── + │ ;; Add an anonymous function, which is more difficult to modify after + │ ;; the fact: + │ (add-hook 'denote-journal-extras-hook (lambda () + │ (tmr "10" "Practice writing in my journal"))) + │ + │ ;; Or write a small function that you can then modify without + │ ;; revaluating the hook: + │ (defun my-denote-tmr () + │ (tmr "10" "Practice writing in my journal")) + │ + │ (add-hook 'denote-journal-extras-hook 'my-denote-tmr) + │ + │ ;; Or to make it fully featured, define variables for the duration and + │ ;; the description and set it up so that you only need to modify + │ ;; those: + │ (defvar my-denote-tmr-duration "10") + │ + │ (defvar my-denote-tmr-description "Practice writing in my journal") + │ + │ (defun my-denote-tmr () + │ (tmr my-denote-tmr-duration my-denote-tmr-description)) + │ + │ (add-hook 'denote-journal-extras-hook 'my-denote-tmr) + └──── + + Once the timer elapses, stop writing and review your performance. + Practice makes perfect! + + Sources for `tmr': + + ⁃ Package name (GNU ELPA): `tmr' + ⁃ Official manual: + ⁃ Change log: + ⁃ Git repo on SourceHut: + • Mirrors: + ⁃ GitHub: + ⁃ GitLab: + ⁃ Mailing list: + + +[Keep a journal or diary] See section 13 + + +14 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) + └──── + + +15 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. This section covers + the details. + + +15.1 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))) + └──── + + +15.2 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 5.3 + + +15.3 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 +23.7 + +[The file-naming scheme] See section 5 + + +15.4 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 15.2 + + +15.5 Search file contents +───────────────────────── + + 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. + + +15.6 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. + + +15.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: + . + + +15.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: + . + + +15.9 Use the `consult-notes' package +──────────────────────────────────── + + 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]). + + +[Features of the file-naming scheme for searching or filtering] See +section 5.3 + + +15.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] + + + +15.11 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). + + +15.12 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))) + └──── + + +15.13 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 15.14 + +[Rename multiple files at once] See section 4.3 + + +15.14 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 4.3 + +[Rename files with Denote in the Image Dired thumbnails buffer] See +section 15.13 + + +15.15 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 3.1.4 + +15.15.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. + + Denote also provides commands to convert `denote:' links to their + `file:' equivalent, in case this is a required pre-processing step for + export purposes ([Convert `denote:' links to `file:' links]). + + +[Convert `denote:' links to `file:' links] See section 7.11 + +◊ 15.15.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 15.15 + + +◊ 15.15.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 3.8 + + +◊ 15.15.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 3.8 + + +15.15.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. + + +16 Installation +═══════════════ + + + + +16.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: + . + + +16.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://git.sr.ht/~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. + + +17 Sample configuration +═══════════════════════ + + ┌──── + │ (require 'denote) + │ + │ ;; Remember to check the doc strings of those variables. + │ (setq denote-directory (expand-file-name "~/Documents/notes/")) + │ (setq denote-save-buffer-after-creation nil) + │ (setq denote-known-keywords '("emacs" "philosophy" "politics" "economics")) + │ (setq denote-infer-keywords t) + │ (setq denote-sort-keywords t) + │ (setq denote-file-type nil) ; Org is the default, set others here + │ (setq denote-prompts '(title keywords)) + │ (setq denote-excluded-directories-regexp nil) + │ (setq denote-excluded-keywords-regexp nil) + │ (setq denote-rename-no-confirm nil) ; Set to t if you are familiar with `denote-rename-file' + │ + │ ;; Pick dates, where relevant, with Org's advanced interface: + │ (setq denote-date-prompt-use-org-read-date t) + │ + │ + │ ;; Read this manual for how to specify `denote-templates'. We do not + │ ;; include an example here to avoid potential confusion. + │ + │ + │ (setq denote-date-format nil) ; read doc string + │ + │ ;; By default, we do not show the context of links. We just display + │ ;; file names. This provides a more informative view. + │ (setq denote-backlinks-show-context t) + │ + │ ;; Also see `denote-link-backlinks-display-buffer-action' which is a bit + │ ;; advanced. + │ + │ ;; If you use Markdown or plain text files (Org renders links as buttons + │ ;; right away) + │ (add-hook 'find-file-hook #'denote-link-buttonize-buffer) + │ + │ ;; 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/books"))) + │ + │ ;; Generic (great if you rename files Denote-style in lots of places): + │ ;; (add-hook 'dired-mode-hook #'denote-dired-mode) + │ ;; + │ ;; OR if only want it in `denote-dired-directories': + │ (add-hook 'dired-mode-hook #'denote-dired-mode-in-directories) + │ + │ + │ ;; Automatically rename Denote buffers using the `denote-rename-buffer-format'. + │ (denote-rename-buffer-mode 1) + │ + │ ;; Denote DOES NOT define any key bindings. This is for the user to + │ ;; decide. For example: + │ (let ((map global-map)) + │ (define-key map (kbd "C-c n n") #'denote) + │ (define-key map (kbd "C-c n c") #'denote-region) ; "contents" mnemonic + │ (define-key map (kbd "C-c n N") #'denote-type) + │ (define-key map (kbd "C-c n d") #'denote-date) + │ (define-key map (kbd "C-c n z") #'denote-signature) ; "zettelkasten" mnemonic + │ (define-key map (kbd "C-c n s") #'denote-subdirectory) + │ (define-key map (kbd "C-c n t") #'denote-template) + │ ;; 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'. + │ (define-key map (kbd "C-c n i") #'denote-link) ; "insert" mnemonic + │ (define-key map (kbd "C-c n I") #'denote-add-links) + │ (define-key map (kbd "C-c n b") #'denote-backlinks) + │ (define-key map (kbd "C-c n f f") #'denote-find-link) + │ (define-key map (kbd "C-c n f b") #'denote-find-backlink) + │ ;; 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'. + │ (define-key map (kbd "C-c n r") #'denote-rename-file) + │ (define-key map (kbd "C-c n R") #'denote-rename-file-using-front-matter)) + │ + │ ;; Key bindings specifically for Dired. + │ (let ((map dired-mode-map)) + │ (define-key map (kbd "C-c C-d C-i") #'denote-link-dired-marked-notes) + │ (define-key map (kbd "C-c C-d C-r") #'denote-dired-rename-files) + │ (define-key map (kbd "C-c C-d C-k") #'denote-dired-rename-marked-files-with-keywords) + │ (define-key map (kbd "C-c C-d C-R") #'denote-dired-rename-marked-files-using-front-matter)) + │ + │ (with-eval-after-load 'org-capture + │ (setq denote-org-capture-specifiers "%l\n%i\n%?") + │ (add-to-list 'org-capture-templates + │ '("n" "New note (with denote.el)" plain + │ (file denote-last-path) + │ #'denote-org-capture + │ :no-save t + │ :immediate-finish nil + │ :kill-buffer t + │ :jump-to-captured t))) + │ + │ ;; Also check the commands `denote-link-after-creating', + │ ;; `denote-link-or-create'. You may want to bind them to keys as well. + │ + │ + │ ;; If you want to have Denote commands available via a right click + │ ;; context menu, use the following and then enable + │ ;; `context-menu-mode'. + │ (add-hook 'context-menu-functions #'denote-context-menu) + └──── + + +18 For developers or advanced users +═══════════════════════════════════ + + Denote is in a stable state and can be relied upon as the basis for + custom extensions. 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. + + 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]). + + Variable `denote-excluded-punctuation-regexp' + Punctionation that is removed from file names. We consider + those characters illegal for our purposes. + + Variable `denote-excluded-punctuation-extra-regexp' + Additional punctuation that is removed from file names. This + variable is for advanced users who need to extend the + `denote-excluded-punctuation-regexp'. Once we have a better + understanding of what we should be omitting, we will update + things accordingly. + + Function `denote-file-is-note-p' + Return non-nil if `FILE' is an actual Denote note. For our + purposes, a note must not 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 + `denote-file-type'. + + Function `denote-file-has-identifier-p' + Return non-nil if `FILE' has a Denote identifier. + + 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'. + + 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]). + + Function `denote-convert-file-name-keywords-to-crm' + Make `STRING' with keywords readable by + `completing-read-multiple'. `STRING' consists of + underscore-separated words, as those appear in the keywords + component of a Denote file name. `STRING' is the same as the + return value of `denote-retrieve-filename-keywords'. + + 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-parse-date' + Return `DATE' as an appropriate value for the `denote' + command. Pass `DATE' through `denote-valid-date-p' and use its + return value. If either that or `DATE' is nil, return + `current-time'. + + 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 only need to have an identifier. The + return value may thus include file types that are not implied by + `denote-file-type'. Remember that the variable + `denote-directory' accepts a dir-local value, as explained in + its doc string ([Maintain separate directories for notes]). 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'. + + 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]). + + 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-get-path-by-id' + Return absolute path of `ID' string in `denote-directory-files'. + + Function `denote-barf-duplicate-id' + Throw a `user-error' if `IDENTIFIER' already exists. + + Function `denote-sluggify' + Make `STR' an appropriate slug for file names and related + ([Sluggification of file name components]). + + 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-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. + + If no file type in `denote-file-types' has the file extension, + the file type is assumed to be the first one in + `denote-file-types'. + + 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 cannot + be nil or an empty string and must match `denote-id-regexp'. + + `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. + + 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, refer to the + `denote-create-unique-file-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'. Try to + find the value of the title in the front matter of FILE, + otherwise use its file name. 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'. `DATE' is parsed by + `denote-valid-date-p'. If `DATE' is nil, use the current time. + + Function `denote-create-unique-file-identifier' + Create a new unique `FILE' identifier. Test that the identifier + is unique among `USED-IDS'. The conditions are as follows: + + • If `DATE' is non-nil, invoke + `denote-prompt-for-date-return-id'. + + • If `DATE' is nil, use the file attributes to determine the + last modified date and format it as an identifier. + + • As a fallback, derive an identifier from the current time. + + With optional `USED-IDS' as nil, test that the identifier is + unique among all files and buffers in variable + `denote-directory'. + + To only return an existing identifier, refer to the function + `denote-retrieve-filename-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. To get a + combined string the way it would appear in a Denote file name, + use `denote-retrieve-front-matter-keywords-value-as-string'. + + Function `denote-retrieve-front-matter-keywords-value-as-string' + Return keywords value from `FILE' front matter per + `FILE-TYPE'. The return value is a string, with the underscrore + as a separator between individual keywords. To get a list of + strings instead, use + `denote-retrieve-front-matter-keywords-value' (the current + function uses that internally). + + Function `denote-retrieve-front-matter-keywords-line' + Return keywords line from `FILE' front matter per `FILE-TYPE'. + + 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 with identifier in variable + `denote-directory'. With optional `FILES-MATCHING-REGEXP', + filter the candidates per the given regular expression. With + optional `PROMPT-TEXT', use it instead of the default “Select + NOTE”. + + 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]). + + 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'). + + Function `denote-rename-file-prompt' + Prompt to rename file named `OLD-NAME' to `NEW-NAME'. + + Function `denote-rename-file-and-buffer' + Rename file named `OLD-NAME' to `NEW-NAME', updating buffer + name. + + 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. With optional + `NO-CONFIRM', do not prompt to confirm the rewriting of the + front matter. Otherwise produce a `y-or-n-p' prompt to that + effect. With optional `NO-CONFIRM', save the buffer after + performing the rewrite. Otherwise leave it unsaved for furthter + review by the user. + + 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]). + + 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'. + + 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]) + + 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-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. + + 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'. + + Variable `denote-link-signature-format' + Format of link description for `denote-link-with-signature'. + + 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. + + +[The file-naming scheme] See section 5 + +[Standard note creation] See section 3.1 + +[Maintain separate directories for notes] See section 3.7 + +[Sluggification of file name components] See section 5.1 + +[The `denote-history-completion-in-prompts' option] See section 3.1.2 + +[The denote-date-prompt-use-org-read-date option] See section 3.1.6 + +[Points of entry] See section 3 + +[Change the front matter format] See section 6.1 + + +19 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. + + +20 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 24 + +20.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-consult.el + This can be a separate package that enhances or replaces the + various prompts we have for files (and maybe more) by using the + `consult' package. Consult provides the preview mechanism and + can probably be used for more things, such as to define a source + for Denote-only buffers in the `consult-buffer' command. If we + need to tweak things in `denote.el', I am happy to do it. For + example, we could have a `denote-file-prompt-function' variable, + which would default to `denote-file-prompt' (what we currently + have) and would also such a hypothetical package to easily plug + into what we have. + + 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). + + Signatures before identifiers + This is probably going to increase the complixity of `denote.el' + and may not be worth pursuing. But just to explore the idea: we + could have an option to rearrange file names such that the + signature appears before the identifier. If we can do this in a + smart way, we can probably extend the principle for all file + name components. Again though, this may be too complex and not + worth doing. + + 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. + + +21 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 20 + + +22 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] + +22.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 22 + +[date2name] + +[filetags] + +[appendfilename] + +[move2archive] + +[blog post] + +[presentation at GLT18] + +[memacs] + + +23 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. + + +23.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. + + +23.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. + + +23.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. + + +23.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). + + +23.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. + + +23.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 20 + + +23.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 12 + + +23.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. + + +23.9 Speed up backlinks’ 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))) + └──── + + +23.10 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] + + +23.11 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. + + +24 Acknowledgements +═══════════════════ + + 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, Adam Růžička, Alan Schmitt, Ashton Wiersdorf, + Benjamin Kästner, Bruno Boal, Charanjit Singh, Clemens + Radermacher, Colin McLear, Damien Cassou, Eduardo Grajeda, Elias + Storms, Eshel Yaron, Florian, Glenna D., Graham Marlow, Hilde + Rhyne, Ivan Sokolov, Jack Baty, Jean-Charles Bagneris, + Jean-Philippe Gagné Guay, Joseph Turner, Jürgen Hötzel, Kaushal + Modi, Kai von Fintel, Kostas Andreadis, Kristoffer Balintona, + Kyle Meyer, Marc Fargas, Matthew Lemon, Noboru Ota (nobiot), + Norwid Behrnd, Peter Prevos, Philip Kaludercic, Quiliro Ordóñez, + Stephen R. Kifer, Stefan Monnier, Stefan Thesing, Thibaut + Benjamin, Tomasz Hołubowicz, Vedang Manerikar, Wesley Harvey, + Zhenxu Xu, arsaber101, ezchi, jarofromel, leinfink (Henrik), + l-o-l-h (Lincoln), mattyonweb, maxbrieiev, mentalisttraceur, + pmenair, relict007. + + Ideas and/or user feedback + Abin Simon, Aditya Yadav, Alan Schmitt, Aleksandr Vityazev, Alex + Hirschfeld, Alfredo Borrás, Ashton Wiersdorf, Benjamin Kästner, + Claudiu Tănăselia, Colin McLear, Damien Cassou, Elias Storms, + Federico Stilman, Florian, Frédéric Willem Frank Ehmsen, Glenna + D., Guo Yong, Hanspeter Gisler, Jack Baty, Jay Rajput, + Jean-Charles Bagneris, Jens Östlund, Jeremy Friesen, Jonathan + Sahar, Johan Bolmsjö, Jousimies, Juanjo Presa, Kai von Fintel, + Kaushal Modi, M. Hadi Timachi, Mark Olson, Mirko Hernandez, + Niall Dooley, Paul van Gelder, Peter Prevos, Peter Smith, Suhail + Singh, Shreyas Ragavan, Stefan Thesing, Summer Emacs, Sven + Seebeck, Taoufik, TJ Stankus, Vick (VicZz), Viktor Haag, Wade + Mealing, Yi Liu, Ypot, atanasj, babusri, doolio, drcxd, + fingerknight, hpgisler, mentalisttraceur, pRot0ta1p, rbenit68, + relict007, 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. + + +25 GNU Free Documentation License +═════════════════════════════════ + + +26 Indices +══════════ + +26.1 Function index +─────────────────── + + +26.2 Variable index +─────────────────── + + +26.3 Concept index +────────────────── blob - /dev/null blob + 08540f63c17e06cdba24c8ce6c447e6950d89c6e (mode 644) --- /dev/null +++ elpa/denote-2.3.5/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 + 1093db9c23bf95b3bd2b978705bcc473e957138f (mode 644) --- /dev/null +++ elpa/denote-2.3.5/README.org @@ -0,0 +1,5857 @@ +#+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 2.3.0 +#+macro: release-date 2024-03-24 +#+macro: development-version 3.0.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-2024 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: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 five 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. + +** 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. + +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). + +#+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 to + establish a sequential relationship between files (e.g. 1, 1a, 1b, + 1b1, 1b2, ...). Signatures have no strictly defined function and + are up to the user to apply as they see fit. One use-case is to + implement Niklas Luhmann's Zettelkasten system for a sequence of + notes (Folgezettel). Signatures are not included in a file's front + matter. They are reserved solely for creating a sequence in a file + listing, at least for the time being. To insert a link that + includes the signature, use the command ~denote-link-with-signature~ + ([[#h:066e5221-9844-474b-8858-398398646f86][Insert link to file with signature]]). + +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 . 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. Below we show some + concrete examples. + +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. + +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 equivalent to calling `denote' when `denote-prompts' is +set to '(subdirectory title keywords)." + (declare (interactive-only t)) + (interactive) + (let ((denote-prompts '(subdirectory title keywords))) + (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. + +Now let's say we want to have a command that (i) asks for a template and +(ii) for a subdirectory ([[#h:f635a490-d29e-4608-9372-7bd13b34d56c][The denote-templates option]]). 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 denote-subdirectory-with-template () + "Create note while also prompting for a template and subdirectory. + +This is equivalent to calling `denote' when `denote-prompts' is +set to '(template subdirectory title keywords)." + (declare (interactive-only t)) + (interactive) + (let ((denote-prompts '(template subdirectory title keywords))) + (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]]). + +*** The ~denote-save-buffer-after-creation~ option +:PROPERTIES: +:CUSTOM_ID: h:bf80f4cd-6f56-4f7c-a991-8573161e4511 +:END: + +#+vindex: denote-save-buffer-after-creation +The user option ~denote-save-buffer-after-creation~ controls whether +commands that creeate 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]]). + +If ~denote-save-buffer-after-creation~ is set to a non-nil value, such +buffers are saved automatically. + +*** 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 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-extras-extract-org-subtree +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. + +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-extras-extract-org-subtree~ prompts for +keywords. Else the new note has no keywords ([[#h:ad4dde4a-8e88-470a-97ae-e7b9d4b41fb4][Add or remove keywords interactively]]). + +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~. + +** 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 + +[ In the future, we might develop Denote in ways which do not require such + manual intervention. More user feedback is required to identify the + relevant workflows. ] + +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 ([[#h:6a92a8b5-d766-42cc-8e5b-8dc255466a23][Standard note creation]]). + +#+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~. + +** 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 a title and keywords. 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 + +** 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. + +** 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. They will not read the global value of ~denote-directory~. +The global value of ~denote-directory~ is read everywhere else except +the silos. + +[[#h:0f72e6ea-97f0-42e1-8fd4-0684af0422e0][Use custom commands to select a silo]]. + +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. + +*** Use custom commands to select a silo +:PROPERTIES: +:CUSTOM_ID: h:0f72e6ea-97f0-42e1-8fd4-0684af0422e0 +:END: + +[ As part of version 2.1.0, the contents of this section + are formally provided in the file =denote-silo-extras.el=. We keep + this here for existing users. Otherwise consult the new entry in + the manual ([[#h:e43baf95-f201-4fec-8620-c0eb5eaa1c85][The =denote-silo-extras.el=]]). ] + +We implement silos as directory-local values of the user option +~denote-directory~. This means that all Denote commands read from the +local value if they are invoked from that context. For example, if +=~/Videos/recordings= is a silo and =~/Documents/notes= is the +default/global value of ~denote-directory~ all Denote commands will +read the video's path when called from there (e.g. by using Emacs' +~dired~); any other context reads the global value. + +[[#h:15719799-a5ff-4e9a-9f10-4ca03ef8f6c5][Maintain separate directory silos for notes]]. + +There are cases where the user (i) wants to maintain multiple silos +and (ii) prefers an interactive way to switch between them without +going through Dired. Since this is specific to the user's workflow, +it is easier to have some custom code for it. The following should be +added to the user's Denote configuration: + +#+begin_src emacs-lisp +(defvar my-denote-silo-directories + `("/home/prot/Videos/recordings" + "/home/prot/Documents/books" + ;; You don't actually need to include the `denote-directory' here + ;; if you use the regular commands in their global context. I am + ;; including it for completeness. + ,denote-directory) + "List of file paths pointing to my Denote silos. + This is a list of strings.") + +(defvar my-denote-commands-for-silos + '(denote + denote-date + denote-subdirectory + denote-template + denote-type) + "List of Denote commands to call after selecting a silo. + This is a list of symbols that specify the note-creating + interactive functions that Denote provides.") + +(defun my-denote-pick-silo-then-command (silo command) + "Select SILO and run Denote COMMAND in it. + SILO is a file path from `my-denote-silo-directories', while + COMMAND is one among `my-denote-commands-for-silos'." + (interactive + (list (completing-read "Select a silo: " my-denote-silo-directories nil t) + (intern (completing-read + "Run command in silo: " + my-denote-commands-for-silos nil t)))) + (let ((denote-directory silo)) + (call-interactively command))) +#+end_src + +With this in place, =M-x my-denote-pick-silo-then-command= will use +minibuffer completion to select a silo among the predefined options +and then ask for the command to run in that context. + +Note that =let= binding ~denote-directory~ can be used in custom +commands and other wrapper functions to override the global default +value of ~denote-directory~ to select silos. + +To see another example of a wrapper function that =let= binds +~denote-directory~, see: + +[[#h:d0c7cb79-21e5-4176-a6af-f4f68578c8dd][Extending Denote: Split an Org subtree into its own note]]. + +*** The =denote-silo-extras.el= +:PROPERTIES: +:CUSTOM_ID: h:e43baf95-f201-4fec-8620-c0eb5eaa1c85 +:END: + +The =denote-silo-extras.el= provides optional convenience functions for +working with silos ([[#h:15719799-a5ff-4e9a-9f10-4ca03ef8f6c5][Maintain separate directory silos for notes]]). +Start by loading the relevant library: + +#+begin_src emacs-lisp +(require 'denote-silo-extras) +#+end_src + +#+vindex: denote-silo-extras-directories +The user option ~denote-silo-extras-directories~ specifies a list of +directories that the user has set up as ~denote-directory~ silos. + +#+findex: denote-silo-extras-create-note +The command ~denote-silo-extras-create-note~ prompts for a directory +among ~denote-silo-extras-directories~ and runs the ~denote~ command +from there. + +#+findex: denote-silo-extras-open-or-create +Similar to the above, the command ~denote-silo-extras-open-or-create~ +prompts for a directory among ~denote-silo-extras-directories~ and runs +the ~denote-open-or-create~ command from there. + +#+findex: denote-silo-extras-select-silo-then-command +The command ~denote-silo-extras-select-silo-then-command~ prompts with +minibuffer completion for a directory among ~denote-silo-extras-directories~. +Once the user selects a silo, a second prompt asks for a Denote +note-creation command to call from inside that silo ([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]). + +** 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~. + +** 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. + +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]]. + +** 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=. If a file +name component is present, but there is no entry for it in +~denote-prompts~, keep it 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, it asks for confirmation, showing the difference +between old and new file names. It does not ask for confirmation if +the user option ~denote-rename-no-confirm~ is set to a non-nil value +([[#h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7][The ~denote-rename-no-confirm~ option]]). + +If =FILE= has front matter for =TITLE= and =KEYWORDS=, +~denote-rename-file~ asks to rewrite their values in order to reflect +the new input, unless ~denote-rename-no-confirm~ is non-nil. When the +~denote-rename-no-confirm~ is nil (the default), the underlying buffer +is not saved, giving the user the change to invoking ~diff-buffer-with-file~ to +double-check the effect. 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~), the ~denote-rename-file~ adds front +matter to the top of it and leaves the buffer unsaved for further +inspection. It actually saves the buffer if ~denote-rename-no-confirm~ +is non-nil ([[#h:13218826-56a5-482a-9b91-5b6de4f14261][Front matter]]). + +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-no-confirm~ option +:PROPERTIES: +:CUSTOM_ID: h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7 +:END: + +#+vindex: denote-rename-no-confirm +The user option ~denote-rename-no-confirm~ makes all commands that +rename files not prompt for confirmation and save buffers outright ([[#h:532e8e2a-9b7d-41c0-8f4b-3c5cbb7d4dca][Renaming files]]). + +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. + +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. It also does not save the affected file's +buffer to let the user inspect and confirm the changes (such as by +invoking the command ~diff-buffer-with-file~). + +With this user option bound to a non-nil value, buffers are saved as +well. The assumption is that the user who opts in to this feature is +familiar with the ~denote-rename-file~ (or related) operation and +knows it is reliable. + +Specialised 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 + +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. + +If called interactively with a prefix argument (=C-u= by default) or +from Lisp with a non-nil =NO-CONFIRM= argument, this "yes or no" +prompt is skipped and the renaming is done outright. + +If called interactively with a double prefix argument (=C-u C-u= by +default) or from Lisp with a non-nil =SAVE-BUFFER= argument, the +buffer is saved after the front matter is updated and the file is +renamed. + +If the user option ~denote-rename-no-confirm~ is non-nil, it is +interpreted the same way as a combination of =NO-CONFIRM= and +=SAVE-BUFFER= ([[#h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7][The ~denote-rename-no-confirm~ 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. + +** 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. 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). ] + +** 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 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. + +** Rename a file by adding or removing keywords interactively +:PROPERTIES: +:CUSTOM_ID: h:ad4dde4a-8e88-470a-97ae-e7b9d4b41fb4 +:END: + +#+findex: denote-keywords-add +#+findex: denote-keywords-remove +The commands ~denote-keywords-add~ and ~denote-keywords-remove~ +streamline the process of interactively updating a file's keywords in +the front matter and renaming it accordingly. + +The ~denote-keywords-add~ 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]]). + +Similarly, the ~denote-keywords-remove~ removes one or more keywords +from the list of existing keywords and then renames the file +accordingly. + +Both commands accept an optional prefix argument to automatically save +the buffer. Similarly, they both interpret a non-nil value for the +user option ~denote-rename-no-confirm~ the same as the prefix argument +([[#h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7][The ~denote-rename-no-confirm~ option]]). + +Furthermore, both commands call the ~denote-after-rename-file-hook~ as +a final step after carrying out their task. + +#+findex: denote-rename-add-keywords +#+findex: denote-rename-remove-keywords +Aliases for these commands are: ~denote-rename-add-keywords~ and +~denote-rename-remove-keywords~. + +** Rename a file by adding or removing a signature interactively +:PROPERTIES: +:CUSTOM_ID: h:b08a350f-b269-47ed-8c2a-b8ecf1b63c7f +:END: + +#+findex: denote-rename-add-signature +#+findex: denote-rename-remove-signature +The commands ~denote-rename-add-signature~ and +~denote-rename-remove-signature~ streamline the process of +interactively adding or removing a signature from a given file ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). + +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. + +Both commands ask for confirmation before carrying out their action. +They do so unless the user option ~denote-rename-no-confirm~ is set to +a non-nil value ([[#h:a2ae9090-c49e-4b32-bcf5-eb8944241fd7][The ~denote-rename-no-confirm~ option]]). They also +both take care to reload any Dired buffers and run the +~denote-after-rename-file-hook~ as a final step. + +** 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 a string of alphanumeric characters in the +=SIGNATURE= field. Signatures have no clearly defined purpose and are up +to the user to define. One use-case is to use them to establish +sequential relations between files (e.g. 1, 1a, 1b, 1b1, 1b2, ...). + +Signatures are an optional extension to Denote's file-naming scheme. +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 =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. + +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. + +** 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. The constant + ~denote-excluded-punctuation-regexp~ holds the relevant value. + ++ 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 (denote--slug-no-punct 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 (denote--slug-no-punct 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. + +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. + +** 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: + +#+findex: denote-add-front-matter +Sometimes the user needs to produce new front matter for an existing +note. Perhaps because they accidentally deleted a line and could not +undo the operation. The command ~denote-add-front-matter~ can be used +for this very purpose. + +In interactive use, ~denote-add-front-matter~ must be invoked from a +buffer that visits a Denote note. It prompts for a title and then for +keywords. These are the standard prompts we already use for note +creation, so the keywords' prompt allows minibuffer completion and the +input of multiple entries, each separated by a comma ([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]). + +The newly created front matter is added to the top of the file. + +This command does not rename the file (e.g. to update the keywords). To +rename a file by reading its front matter as input, the user can rely on +~denote-rename-file-using-front-matter~ ([[#h:532e8e2a-9b7d-41c0-8f4b-3c5cbb7d4dca][Renaming files]]). + +Note that ~denote-add-front-matter~ is useful only for existing Denote +notes. If the user needs to convert a generic text file to a Denote +note, they can use one of the command which first rename the file to +make it comply with our file-naming scheme and then add the relevant +front matter. + +* Linking notes +:PROPERTIES: +:CUSTOM_ID: h:fc913d54-26c8-4c41-be86-999839e8ad31 +:END: + +Denote offers several commands for linking between notes. + +All links target files which are Denote files. This means that they +have our file-naming scheme. Files need to be inside the +~denote-directory~ or one of its subdirectories. No other file is +recognised. + +The following sections delve into the details. + +** Adding a single link +:PROPERTIES: +:CUSTOM_ID: h:5e5e3370-12ab-454f-ba09-88ff44214324 +:END: + +#+findex: denote-link +The ~denote-link~ command inserts a link at point to a file specified +at the minibuffer prompt ([[#h:d99de1fb-b1b7-4a74-8667-575636a4d6a4][The ~denote-org-store-link-to-heading~ user option]]). +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]]=. The user +might prefer its simplicity. + +By default, the description of the link is taken from the signature of +the file, if present, and the target file's front matter's title or, if +that is not available, from the file name. If the region is active, its +text is used as the link's description instead. If the active region +has no text, the inserted link uses just the identifier, as with the +=C-u= prefix mentioned above. + +Inserted links are automatically buttonized and remain active for as +long as the buffer is available. In Org this is handled by the major +mode: the =denote:= hyperlink type works exactly like the standard +=file:=. In Markdown and plain text, Denote performs the buttonization +of those links. To buttonize links in existing files while visiting +them, the user must add this snippet to their setup (it already excludes +Org): + +#+findex: denote-link-buttonize-buffer +#+begin_src emacs-lisp +(add-hook 'find-file-hook #'denote-link-buttonize-buffer) +#+end_src + +The ~denote-link-buttonize-buffer~ is also an interactive function in +case the user needs it. + +Links are created only for files which qualify as a "note" for our +purposes ([[#h:fc913d54-26c8-4c41-be86-999839e8ad31][Linking notes]]). + +#+vindex: denote-faces-link +Links are styled with the ~denote-faces-link~ face, which looks exactly +like an ordinary link by default. This is just a convenience for the +user/theme in case they want =denote:= links to remain distinct from +other links. + +#+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. This method works in addition to +the =RET= key, which is made available by the aforementioned +buttonization. Interested users can refer to the function +~denote-link-markdown-follow~ for the implementation details. + +** 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 (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 versions prior to =2.3.0=. + +Note that the optional extension =denote-org-extras.el= defines the command +~denote-org-extras-link-to-heading~, which always links to a file+heading +regardless of the aforementioned user option ([[#h:fc1ad245-ec08-41be-8d1e-7153d99daf02][Insert link to an Org file with a further pointer to a 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. ] + +** 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-extras-link-to-heading +As part of the optional =denote-org-extras.el= extension, 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 ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). + +This feature is similar to the concept of the user option ~denote-org-store-link-to-heading~ +([[#h:d99de1fb-b1b7-4a74-8667-575636a4d6a4][The ~denote-org-store-link-to-heading~ user option]]). 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. + +** Insert links matching a regexp +:PROPERTIES: +:CUSTOM_ID: h:9bec2c83-36ca-4951-aefc-7187c5463f90 +:END: + +#+findex: denote-add-links +The command ~denote-add-links~ adds links at point matching a +regular expression or plain string. The links 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 link to file with signature +:PROPERTIES: +:CUSTOM_ID: h:066e5221-9844-474b-8858-398398646f86 +:END: + +#+findex: denote-link-with-signature +The command ~denote-link-with-signature~ prompts for a file among +those that contain a ===SIGNATURE= and inserts a link to it. The +description of the link includes the text of the signature and that of +the file's title, if any. For example, a link to the following file: + +: 20230925T144303==abc--my-first-signature-note__denote_testing.txt + +will get this link: =[[denote:20230925T144303][abc My first signature note]]=. + +For more advanced uses, refer to the doc string of the ~denote-link~ +function. + +** 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]]). + +** 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. + +#+findex: denote-link-or-create-with-command ++ ~denote-link-or-create-with-command~ :: This 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. + +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: + +#+findex: denote-backlinks +The command ~denote-backlinks~ produces a bespoke buffer which +displays backlinks to the current note. A "backlink" is a link back +to the present entry. + +By default, the backlinks' buffer is designed to display the file name +of the note linking to the current entry. Each file name is presented +on its own line, like this: + +#+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 + +#+vindex: denote-backlinks-show-context +When the user option ~denote-backlinks-show-context~ is non-nil, the +backlinks' buffer displays the line on which a link to the current +note occurs. It also shows multiple occurrences, if present. It looks +like this (and has 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. + +[[#h:893eec49-d7be-4603-bcff-fcc247244011][Speed up backlinks' buffer creation?]] + +#+findex: denote-backlinks-mode +#+vindex: denote-backlinks-mode-map +The backlinks' buffer runs the major-mode ~denote-backlinks-mode~. It +binds keys to move between links with =n= (next) and =p= (previous). +These are stored in the ~denote-backlinks-mode-map~ (use =M-x +describe-mode= (=C-h m=) in an unfamiliar buffer to learn more about +it). When the user option ~denote-backlinks-show-context~ is non-nil, +all relevant Xref key bindings are fully functional: again, check +~describe-mode~. + +The backlinking facility uses Emacs' built-in Xref infrastructure. 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?]] + +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]]). + +#+vindex: denote-link-backlinks-display-buffer-action +The placement of the backlinks' buffer is subject to the user option +~denote-link-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. The doc string of our user option includes a sample +configuration that places the buffer in a left side window instead. +Reproducing it here for the sake of convenience: + +#+begin_src emacs-lisp +(setq denote-link-backlinks-display-buffer-action + '((display-buffer-reuse-window + display-buffer-in-side-window) + (side . left) + (slot . 99) + (window-width . 0.3))) +#+end_src + +** 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]]). + +** Convert =denote:= links to =file:= links +: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-extras.el= contains two commands +that are relevant for this use-case: + +#+findex: denote-org-extras-convert-links-to-file-type ++ 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 ([[#h:fc1ad245-ec08-41be-8d1e-7153d99daf02][Insert link to an Org file with a further pointer to a heading]]). + +#+findex: denote-org-extras-convert-links-to-denote-type ++ 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. + +** Miscellaneous information about links +:PROPERTIES: +:CUSTOM_ID: h:dd8f2231-8d77-49b9-acc4-af525c68b271 +:END: + +*** Aliases for the linking commands +:PROPERTIES: +:CUSTOM_ID: h:078856d9-f608-43a8-be84-f2cad4c27d0e +:END: + +#+findex: denote-insert-link +#+findex: denote-show-backlinks-buffer +#+findex: denote-link-insert-links-matching-regexp +For convenience, the ~denote-link~ command has an alias called +~denote-insert-link~. The ~denote-backlinks~ can also be used as +~denote-show-backlinks-buffer~. While ~denote-add-links~ is +aliased ~denote-link-insert-links-matching-regexp~. The purpose of +these aliases is to offer alternative, more descriptive names of +select commands. + +*** The ~denote-link-description-function~ to format links +:PROPERTIES: +:CUSTOM_ID: h:f634427c-b451-40e2-993e-e00ac627af68 +:END: + +#+vindex: denote-link-description-function +The user option ~denote-link-description-function~ takes as its value +the symbol of a function. This is used to format the text of the link. +The default function inserts the title. If the file has a signature, +it includes that as well, prepending it to the title. + +The function specified accepts a single =FILE= argument and returns +the description as a string. + +* 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]]): + +- The =%t= is the Denote =TITLE= of the file. +- The =%i= is the Denote =IDENTIFIER= of the file. +- 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 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 +;; 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~. + +* Use Org dynamic blocks +:PROPERTIES: +:CUSTOM_ID: h:8b542c50-dcc9-4bca-8037-a36599b22779 +:END: + +[ As part of version 2.3.0, all dynamic blocks are defined in the file + =denote-org-extras.el=. The file which was once called + =denote-org-dblock.el= contains 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. ] + +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 ([[#h:6060a7e6-f179-4d42-a9de-a9968aaebecc][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 or backlinks +:PROPERTIES: +:CUSTOM_ID: h:50160fae-6515-4d7d-9737-995ad925e64b +:END: + +[ As part of version 2.3.0, all dynamic blocks are defined in the file + =denote-org-extras.el=. The file which was once called + =denote-org-dblock.el= contains 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. ] + +#+findex: denote-org-extras-dblock-insert-links +The =denote-links= block can be inserted at point with the command +~denote-org-extras-dblock-insert-links~ or by manually including the +following in an Org file: + +: #+BEGIN: denote-links :regexp "YOUR REGEXP HERE" :sort-by-component nil :reverse-sort nil :id-only nil +: +: #+END: + +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 ~denote-add-links~ command + ([[#h:9bec2c83-36ca-4951-aefc-7187c5463f90][Insert links matching a regexp]]). 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 =: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-extras-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 ([[#h:fc913d54-26c8-4c41-be86-999839e8ad31][Linking notes]]). + +- 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). + +#+findex: denote-org-extras-dblock-insert-backlinks +The same as above except for the =:regexp= parameter are true for the +=denote-backlinks= block. The block can be inserted at point with the +command ~denote-org-extras-dblock-insert-backlinks~ or by manually writing +this in an Org file: + +: #+BEGIN: denote-backlinks :sort-by-component nil :reverse-sort nil :id-only nil +: +: #+END: + +#+findex: denote-org-extras-dblock-insert-missing-links +Finally, the =denote-missing-links= block is available with the +command ~denote-org-extras-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. 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. + +** Org dynamic block to insert file contents +:PROPERTIES: +:CUSTOM_ID: h:f15fa143-5036-416f-9bff-1bcabbb03456 +:END: + +[ As part of version 2.3.0, all dynamic blocks are defined in the file + =denote-org-extras.el=. The file which was once called + =denote-org-dblock.el= contains 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. ] + +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 ([[#h:9bec2c83-36ca-4951-aefc-7187c5463f90][Insert links matching a regexp]]). + +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-extras-dblock-insert-files +To produce such a block, call the command ~denote-org-extras-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" +: +: #+END: + +To fully control the output, include these additional optional +parameters, which are described further below: + +: #+BEGIN: denote-files :regexp "YOUR REGEXP HERE" :sort-by-component nil :reverse-sort nil :no-front-matter nil :file-separator nil :add-links nil +: +: #+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 =: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-extras-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-extras-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-extras-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 + ([[#h:fc913d54-26c8-4c41-be86-999839e8ad31][Linking notes]]). 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). + +* Sort files by component +:PROPERTIES: +:CUSTOM_ID: h:9fe01e63-f34f-4479-8713-f162a5ca865e +:END: + +The =denote-sort.el= file is an optional extension to the core +functionality of Denote, which empowers users to sort files by the +given file name component ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). + +#+findex: denote-sort-dired +The command ~denote-sort-dired~ produces a Dired file listing with a +flat, filtered, and sorted set of files from the ~denote-directory~. +It does so by means of three minibuffer prompts: + +1. It first asks for a regular expression with which to match Denote + files. Remember that due to Denote's efficient file-naming scheme, + you do not need to write some complex regular expression. Even + 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=. +3. Finally, it asks a "yes or no" on whether to reverse the sort + order. + +The resulting Dired 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]]). + +The dynamic Org blocks that Denote defines to insert file contents +also use this feature ([[#h:f15fa143-5036-416f-9bff-1bcabbb03456][Org dynamic block to insert file contents]]). + +DEVELOPMENT NOTE as of 2023-11-30 07:24 +0200: The sort mechanism will +be incorporated in more functions on a case-by-case basis, subject to +user feedback. For the time being, I am not documenting the functions +that are only accessed from Lisp. Do not hesitate to contact me +(Protesilaos) in person or on one of the development sources (mailing +list or GitHub/GitLab mirror) if you have a use-case where sorting +seems useful. I am happy to help but do not want to roll this feature +everywhere before eliciting relevant feedback: once we add it, we are +not going back. + +* Keep a journal or diary +:PROPERTIES: +:CUSTOM_ID: h:4a6d92dd-19eb-4fcc-a7b5-05ce04da3a92 +:END: + +Denote provides a general-purpose mechanism to create new files that +broadly count as "notes" ([[#h:17896c8c-d97a-4faa-abf6-31df99746ca6][Points of entry]]). Such files can be daily +entries in a journal. While it is possible to use the generic +~denote~ command to maintain a journal, we provide an optional set of +convenience options and commands as part of =denote-journal-extras.el=. +To use those, add the following the Denote configuration: + +#+begin_src emacs-lisp +(require 'denote-journal-extras) +#+end_src + +#+findex: denote-journal-extras-new-entry +#+vindex: denote-journal-extras-keyword +#+vindex: denote-journal-extras-directory +The command ~denote-journal-extras-new-entry~ creates a new entry in +the journal. Such a file has the ~denote-journal-extras-keyword~, +which is =journal= by default ([[#h:4e9c7512-84dc-4dfb-9fa9-e15d51178e5d][The file-naming scheme]]). The user can +set this keyword to an arbitrary string (single word is preferred). +New journal entries can be stored in the ~denote-directory~ or +subdirectory thereof. To make it easier for the user, the new journal +entry will be placed in ~denote-journal-extras-directory~, which +defaults to a subdirectory of ~denote-directory~ called =journal=. + +If ~denote-journal-extras-directory~ is nil, the ~denote-directory~ is +used. Journal entries will thus be in a flat listing together with +all other notes. They can still be retrieved easily by searching for +the ~denote-journal-extras-keyword~ ([[#h:1a953736-86c2-420b-b566-fb22c97df197][Features of the file-naming scheme for searching or filtering]]). + +#+vindex: denote-journal-extras-title-format +Furthermore, the command ~denote-journal-extras-new-entry~ will use +the current date as the title of the new entry. The exact format is +controlled by the user option ~denote-journal-extras-title-format~. +Acceptable values for ~denote-journal-extras-title-format~ and their +corresponding styles are: + +| Symbol | Style | +|-------------------------+-----------------------------------| +| day | Monday | +| day-date-month-year | Monday 19 September 2023 | +| day-date-month-year-24h | Monday 19 September 2023 20:49 | +| day-date-month-year-12h | Monday 19 September 2023 08:49 PM | + +For example: + +#+begin_src emacs-lisp +(setq denote-journal-extras-title-format 'day-date-month-year) +#+end_src + +If the value of this user option is ~nil~, then +~denote-journal-extras-new-entry~ will prompt for a title. + +The ~denote-journal-extras-new-entry~ command also accepts an optional +=DATE= argument. When called internactively, this is a universal +prefix (e.g. =C-u= with the default key bindings). With =DATE=, it +prompts for a date to create a new journal entry for. The date prompt +can optionally use the Org date+calendar selection interface +([[#h:e7ef08d6-af1b-4ab3-bb00-494a653e6d63][The ~denote-date-prompt-use-org-read-date~ option]]). + +In terms of workflow, using the current date as the title is better +for maintaining a daily journal. A prompt for an arbitrary title is +more suitable for those who like to keep a record of something like a +thought or event (though this can also be achieved by the regular +~denote~ command or maybe ~denote-subdirectory~). + +#+vindex: denote-journal-extras-hook +The ~denote-journal-extras-new-entry~ command calls the normal hook +~denote-journal-extras-hook~ after it is done. The user can leverage +this to produce consequences therefrom, such as to set a timer with +the ~tmr~ package from GNU ELPA ([[#h:4af1f81e-e93a-43cc-b344-960032a16d42][Journaling with a timer]]). + +#+findex: denote-journal-extras-new-or-existing-entry +The command ~denote-journal-extras-new-or-existing-entry~ locates an +existing journal entry or creates a new one. A journal entry is one +that has ~denote-journal-extras-keyword~ as part of its file name. If +there are multiple journal entries for the current date, it prompts +for one among them using minibuffer completion. If there is only one, +it visits it outright. If there is no journal entry, it creates one +by calling ~denote-journal-extra-new-entry~ (as described above). + +#+findex: denote-journal-extras-link-or-create-entry +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. + +** Journaling with a timer +:PROPERTIES: +:CUSTOM_ID: h:4af1f81e-e93a-43cc-b344-960032a16d42 +:END: + +[ Revised as part of version 2.1.0 to conform with how we + now tend to the needs of users who use Denote for journaling + purposes ([[#h:4a6d92dd-19eb-4fcc-a7b5-05ce04da3a92][Keep a journal or diary]]). ] + +Sometimes journaling is done with the intent to hone one's writing +skills. Perhaps you are learning a new language or wish to communicate +your ideas with greater clarity and precision. As with everything that +requires a degree of sophistication, you have to work for it---write, +write, write! + +One way to test your progress is to set a timer. It helps you gauge +your output and its quality. To use a timer with Emacs, consider the +~tmr~ package. A new timer can be set with something like this: + +#+begin_src emacs-lisp +;; Set 10 minute timer with the given description +(tmr "10" "Practice writing in my journal") +#+end_src + +To make this timer start as soon as a new journal entry is created +with the command ~denote-journal-extras-new-entry~, add a function to +the ~denote-journal-extras-hook~. For example: + +#+begin_src emacs-lisp +;; Add an anonymous function, which is more difficult to modify after +;; the fact: +(add-hook 'denote-journal-extras-hook (lambda () + (tmr "10" "Practice writing in my journal"))) + +;; Or write a small function that you can then modify without +;; revaluating the hook: +(defun my-denote-tmr () + (tmr "10" "Practice writing in my journal")) + +(add-hook 'denote-journal-extras-hook 'my-denote-tmr) + +;; Or to make it fully featured, define variables for the duration and +;; the description and set it up so that you only need to modify +;; those: +(defvar my-denote-tmr-duration "10") + +(defvar my-denote-tmr-description "Practice writing in my journal") + +(defun my-denote-tmr () + (tmr my-denote-tmr-duration my-denote-tmr-description)) + +(add-hook 'denote-journal-extras-hook 'my-denote-tmr) +#+end_src + +Once the timer elapses, stop writing and review your performance. +Practice makes perfect! + +Sources for ~tmr~: + ++ Package name (GNU ELPA): ~tmr~ ++ Official manual: ++ Change log: ++ Git repo on SourceHut: + - Mirrors: + + GitHub: + + GitLab: ++ Mailing list: + +* 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 + +* 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. This section covers the details. + +** 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 + +** 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: + +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. + +** 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: + +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. + +** 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. + +Denote also provides commands to convert =denote:= links to their +=file:= equivalent, in case this is a required pre-processing step for +export purposes ([[#h:ed220cac-7dcb-4bb7-9243-1bb85e452e5f][Convert =denote:= links to =file:= links]]). + +**** 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. + +* 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://git.sr.ht/~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 + +#+begin_src emacs-lisp +(require 'denote) + +;; Remember to check the doc strings of those variables. +(setq denote-directory (expand-file-name "~/Documents/notes/")) +(setq denote-save-buffer-after-creation nil) +(setq denote-known-keywords '("emacs" "philosophy" "politics" "economics")) +(setq denote-infer-keywords t) +(setq denote-sort-keywords t) +(setq denote-file-type nil) ; Org is the default, set others here +(setq denote-prompts '(title keywords)) +(setq denote-excluded-directories-regexp nil) +(setq denote-excluded-keywords-regexp nil) +(setq denote-rename-no-confirm nil) ; Set to t if you are familiar with `denote-rename-file' + +;; Pick dates, where relevant, with Org's advanced interface: +(setq denote-date-prompt-use-org-read-date t) + + +;; Read this manual for how to specify `denote-templates'. We do not +;; include an example here to avoid potential confusion. + + +(setq denote-date-format nil) ; read doc string + +;; By default, we do not show the context of links. We just display +;; file names. This provides a more informative view. +(setq denote-backlinks-show-context t) + +;; Also see `denote-link-backlinks-display-buffer-action' which is a bit +;; advanced. + +;; If you use Markdown or plain text files (Org renders links as buttons +;; right away) +(add-hook 'find-file-hook #'denote-link-buttonize-buffer) + +;; 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/books"))) + +;; Generic (great if you rename files Denote-style in lots of places): +;; (add-hook 'dired-mode-hook #'denote-dired-mode) +;; +;; OR if only want it in `denote-dired-directories': +(add-hook 'dired-mode-hook #'denote-dired-mode-in-directories) + + +;; Automatically rename Denote buffers using the `denote-rename-buffer-format'. +(denote-rename-buffer-mode 1) + +;; Denote DOES NOT define any key bindings. This is for the user to +;; decide. For example: +(let ((map global-map)) + (define-key map (kbd "C-c n n") #'denote) + (define-key map (kbd "C-c n c") #'denote-region) ; "contents" mnemonic + (define-key map (kbd "C-c n N") #'denote-type) + (define-key map (kbd "C-c n d") #'denote-date) + (define-key map (kbd "C-c n z") #'denote-signature) ; "zettelkasten" mnemonic + (define-key map (kbd "C-c n s") #'denote-subdirectory) + (define-key map (kbd "C-c n t") #'denote-template) + ;; 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'. + (define-key map (kbd "C-c n i") #'denote-link) ; "insert" mnemonic + (define-key map (kbd "C-c n I") #'denote-add-links) + (define-key map (kbd "C-c n b") #'denote-backlinks) + (define-key map (kbd "C-c n f f") #'denote-find-link) + (define-key map (kbd "C-c n f b") #'denote-find-backlink) + ;; 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'. + (define-key map (kbd "C-c n r") #'denote-rename-file) + (define-key map (kbd "C-c n R") #'denote-rename-file-using-front-matter)) + +;; Key bindings specifically for Dired. +(let ((map dired-mode-map)) + (define-key map (kbd "C-c C-d C-i") #'denote-link-dired-marked-notes) + (define-key map (kbd "C-c C-d C-r") #'denote-dired-rename-files) + (define-key map (kbd "C-c C-d C-k") #'denote-dired-rename-marked-files-with-keywords) + (define-key map (kbd "C-c C-d C-R") #'denote-dired-rename-marked-files-using-front-matter)) + +(with-eval-after-load 'org-capture + (setq denote-org-capture-specifiers "%l\n%i\n%?") + (add-to-list 'org-capture-templates + '("n" "New note (with denote.el)" plain + (file denote-last-path) + #'denote-org-capture + :no-save t + :immediate-finish nil + :kill-buffer t + :jump-to-captured t))) + +;; Also check the commands `denote-link-after-creating', +;; `denote-link-or-create'. You may want to bind them to keys as well. + + +;; If you want to have Denote commands available via a right click +;; context menu, use the following and then enable +;; `context-menu-mode'. +(add-hook 'context-menu-functions #'denote-context-menu) +#+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. 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. + +#+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]]). + +#+vindex: denote-excluded-punctuation-regexp ++ Variable ~denote-excluded-punctuation-regexp~ :: Punctionation that + is removed from file names. We consider those characters illegal + for our purposes. + +#+vindex: denote-excluded-punctuation-extra-regexp ++ Variable ~denote-excluded-punctuation-extra-regexp~ :: Additional + punctuation that is removed from file names. This variable is for + advanced users who need to extend the ~denote-excluded-punctuation-regexp~. + Once we have a better understanding of what we should be omitting, + we will update things accordingly. + +#+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 not 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 + ~denote-file-type~. + +#+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-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-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]]). + +#+findex: denote-convert-file-name-keywords-to-crm ++ Function ~denote-convert-file-name-keywords-to-crm~ :: Make =STRING= + with keywords readable by ~completing-read-multiple~. =STRING= + consists of underscore-separated words, as those appear in the + keywords component of a Denote file name. =STRING= is the same as + the return value of ~denote-retrieve-filename-keywords~. + +#+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-parse-date ++ Function ~denote-parse-date~ :: Return =DATE= as an appropriate + value for the ~denote~ command. Pass =DATE= through + ~denote-valid-date-p~ and use its return value. If either that or + =DATE= is nil, return ~current-time~. + +#+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 only need to have an + identifier. The return value may thus include file types that are + not implied by ~denote-file-type~. Remember that the variable + ~denote-directory~ accepts a dir-local value, as explained in its + doc string ([[#h:15719799-a5ff-4e9a-9f10-4ca03ef8f6c5][Maintain separate directories for notes]]). 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~. + +#+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]]). + +#+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-get-path-by-id ++ Function ~denote-get-path-by-id~ :: Return absolute path of =ID= + string in ~denote-directory-files~. + +#+findex: denote-barf-duplicate-id ++ Function ~denote-barf-duplicate-id~ :: Throw a ~user-error~ if + =IDENTIFIER= already exists. + +#+findex: denote-sluggify ++ Function ~denote-sluggify~ :: Make =STR= an appropriate slug for + file names and related ([[#h:ae8b19a1-7f67-4258-96b3-370a72c43f4e][Sluggification of file name components]]). + +#+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-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. + + If no file type in ~denote-file-types~ has the file extension, the + file type is assumed to be the first one in ~denote-file-types~. + +#+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 cannot be + nil or an empty string and must match ~denote-id-regexp~. + + =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. + +#+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, refer to the + ~denote-create-unique-file-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=. Try to find the value of the + title in the front matter of FILE, otherwise use its file name. 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~. =DATE= is parsed by + ~denote-valid-date-p~. If =DATE= is nil, use the current time. + +#+findex: denote-create-unique-file-identifier ++ Function ~denote-create-unique-file-identifier~ :: Create a new unique + =FILE= identifier. Test that the identifier is unique among + =USED-IDS=. The conditions are as follows: + + - If =DATE= is non-nil, invoke ~denote-prompt-for-date-return-id~. + + - If =DATE= is nil, use the file attributes to determine the last + modified date and format it as an identifier. + + - As a fallback, derive an identifier from the current time. + + With optional =USED-IDS= as nil, test that the identifier is unique + among all files and buffers in variable ~denote-directory~. + + To only return an existing identifier, refer to the function + ~denote-retrieve-filename-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. To get a combined string the way it would appear in a + Denote file name, use ~denote-retrieve-front-matter-keywords-value-as-string~. + +#+findex: denote-retrieve-front-matter-keywords-value-as-string ++ Function ~denote-retrieve-front-matter-keywords-value-as-string~ :: Return + keywords value from =FILE= front matter per =FILE-TYPE=. The return + value is a string, with the underscrore as a separator between + individual keywords. To get a list of strings instead, use + ~denote-retrieve-front-matter-keywords-value~ (the current function uses that + internally). + +#+findex: denote-retrieve-front-matter-keywords-line ++ Function ~denote-retrieve-front-matter-keywords-line~ :: Return keywords line + from =FILE= front matter per =FILE-TYPE=. + +#+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 with identifier in + variable ~denote-directory~. With optional =FILES-MATCHING-REGEXP=, + filter the candidates per the given regular expression. With + optional =PROMPT-TEXT=, use it instead of the default "Select NOTE". + +#+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]]). + +#+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~). + +#+findex: denote-rename-file-prompt ++ Function ~denote-rename-file-prompt~ :: Prompt to rename file named + =OLD-NAME= to =NEW-NAME=. + +#+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-rewrite-front-matter ++ 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. With optional =NO-CONFIRM=, do not prompt to confirm + the rewriting of the front matter. Otherwise produce a ~y-or-n-p~ + prompt to that effect. With optional =NO-CONFIRM=, save the buffer + after performing the rewrite. Otherwise leave it unsaved for + furthter review by the user. + +#+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]]). + +#+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~. + +#+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]]) + +#+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-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. + +#+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~. + +#+vindex: denote-link-signature-format ++ Variable ~denote-link-signature-format~ :: Format of link + description for ~denote-link-with-signature~. + +#+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. + +* 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-consult.el :: This can be a separate package that enhances or + replaces the various prompts we have for files (and maybe more) by + using the ~consult~ package. Consult provides the preview mechanism + and can probably be used for more things, such as to define a source + for Denote-only buffers in the ~consult-buffer~ command. If we need + to tweak things in =denote.el=, I am happy to do it. For example, we + could have a ~denote-file-prompt-function~ variable, which would + default to ~denote-file-prompt~ (what we currently have) and would + also such a hypothetical package to easily plug into what we have. + +- 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). + +- Signatures before identifiers :: This is probably going to increase + the complixity of =denote.el= and may not be worth pursuing. But + just to explore the idea: we could have an option to rearrange file + names such that the signature appears before the identifier. If we + can do this in a smart way, we can probably extend the principle for + all file name components. Again though, this may be too complex and + not worth doing. + +- 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. + +** Speed up backlinks' 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 :: Abin Simon, Adam Růžička, + Alan Schmitt, Ashton Wiersdorf, Benjamin Kästner, Bruno Boal, + Charanjit Singh, Clemens Radermacher, Colin McLear, Damien Cassou, + Eduardo Grajeda, Elias Storms, Eshel Yaron, Florian, Glenna D., + Graham Marlow, Hilde Rhyne, Ivan Sokolov, Jack Baty, Jean-Charles + Bagneris, Jean-Philippe Gagné Guay, Joseph Turner, Jürgen Hötzel, + Kaushal Modi, Kai von Fintel, Kostas Andreadis, Kristoffer + Balintona, Kyle Meyer, Marc Fargas, Matthew Lemon, Noboru Ota + (nobiot), Norwid Behrnd, Peter Prevos, Philip Kaludercic, Quiliro + Ordóñez, Stephen R. Kifer, Stefan Monnier, Stefan Thesing, Thibaut + Benjamin, Tomasz Hołubowicz, Vedang Manerikar, Wesley Harvey, Zhenxu + Xu, arsaber101, ezchi, jarofromel, leinfink (Henrik), l-o-l-h + (Lincoln), mattyonweb, maxbrieiev, mentalisttraceur, pmenair, + relict007. + ++ Ideas and/or user feedback :: Abin Simon, Aditya Yadav, Alan + Schmitt, Aleksandr Vityazev, Alex Hirschfeld, Alfredo Borrás, Ashton + Wiersdorf, Benjamin Kästner, Claudiu Tănăselia, Colin McLear, Damien + Cassou, Elias Storms, Federico Stilman, Florian, Frédéric Willem + Frank Ehmsen, Glenna D., Guo Yong, Hanspeter Gisler, Jack Baty, Jay + Rajput, Jean-Charles Bagneris, Jens Östlund, Jeremy Friesen, + Jonathan Sahar, Johan Bolmsjö, Jousimies, Juanjo Presa, Kai von + Fintel, Kaushal Modi, M. Hadi Timachi, Mark Olson, Mirko Hernandez, + Niall Dooley, Paul van Gelder, Peter Prevos, Peter Smith, Suhail + Singh, Shreyas Ragavan, Stefan Thesing, Summer Emacs, Sven Seebeck, + Taoufik, TJ Stankus, Vick (VicZz), Viktor Haag, Wade Mealing, Yi + Liu, Ypot, atanasj, babusri, doolio, drcxd, fingerknight, hpgisler, + mentalisttraceur, pRot0ta1p, rbenit68, relict007, 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. + +* 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 + 1ea03ba9e7341ee40a2ddd780d8e5c9d81576952 (mode 644) --- /dev/null +++ elpa/denote-2.3.5/denote-autoloads.el @@ -0,0 +1,994 @@ +;;; 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 "denote" "\ +Create a new note with the appropriate metadata and file name. + +Run the `denote-after-new-note-hook' after creating the new note. + +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 `denote-file-type'. + +- SUBDIRECTORY 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 SUBDIRECTORY 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 SUBDIRECTORY 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." 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. + +If TARGET file does not exist, add the user input that was used +to search for it to the minibuffer 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 +\\\\[previous-history-element]. + +(fn TARGET)" t) +(autoload 'denote-open-or-create-with-command "denote" "\ +Visit TARGET file in variable `denote-directory'. +If file does not exist, invoke `denote' to create a file. + +If TARGET file does not exist, add the user input that was used +to search for it to the minibuffer 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 +\\\\[previous-history-element]." 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 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. + +If a file name component is present, but there is no entry for it in +`denote-prompts', keep it 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, read its file type extension (like .org) and +preserve 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-no-confirm' is set to a non-nil +value. + +If FILE has front matter for TITLE and KEYWORDS, ask to rewrite +their values in order to reflect the new input, unless +`denote-rename-no-confirm' is non-nil. When the +`denote-rename-no-confirm' is nil (the default), do not save the +underlying buffer, thus giving the user the option to +double-check the result, such as by invokling 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. Save +the buffer if `denote-rename-no-confirm' is non-nil. + +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' + +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 &optional 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-no-confirm' to a non-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 `denote-file-type'), + such that it includes the new keywords. + +Run the `denote-after-rename-file-hook' after renaming is done. + +[ 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). ]" '(dired-mode)) +(function-put 'denote-dired-rename-marked-files-with-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 return value of the +function `buffer-file-name' 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 `denote-file-type'. + +Unless NO-CONFIRM is non-nil (such as with a prefix argument), +ask for confirmation, showing the difference between the old and +the new file names. + +Never modify the identifier of the FILE, if any, 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. + +If NO-CONFIRM is non-nil (such as with a prefix argument) do not +prompt for confirmation while renaming the file. Do it outright. + +If optional SAVE-BUFFER is non-nil (such as with a double prefix +argument), save the corresponding buffer. + +If the user option `denote-rename-no-confirm' is non-nil, +interpret it the same way as a combination of NO-CONFIRM and +SAVE-BUFFER. + +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. + +(fn FILE &optional NO-CONFIRM SAVE-BUFFER)" 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 `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-keywords-add "denote" "\ +Prompt for KEYWORDS to add to the current note's front matter. +When called from Lisp, KEYWORDS is a list of strings. + +Rename the file without further prompt so that its name reflects +the new front matter, per `denote-rename-file-using-front-matter'. + +With an optional SAVE-BUFFER (such as a prefix argument when +called interactively), save the buffer outright. Otherwise leave +the buffer unsaved for further review. + +If the user option `denote-rename-no-confirm' is non-nil, +interpret it the same way as SAVE-BUFFER, making SAVE-BUFFER +reduntant. + +Run `denote-after-rename-file-hook' as a final step. + +(fn KEYWORDS &optional SAVE-BUFFER)" t) +(autoload 'denote-keywords-remove "denote" "\ +Prompt for keywords in current note and remove them. +Keywords are retrieved from the file's front matter. + +Rename the file without further prompt so that its name reflects +the new front matter, per `denote-rename-file-using-front-matter'. + +With an optional SAVE-BUFFER as a prefix argument, save the +buffer outright. Otherwise leave the buffer unsaved for further +review. + +If the user option `denote-rename-no-confirm' is non-nil, +interpret it the same way as SAVE-BUFFER, making SAVE-BUFFER +reduntant. + +Run `denote-after-rename-file-hook' as a final step. + +(fn &optional SAVE-BUFFER)" t) +(function-put 'denote-keywords-remove 'interactive-only 't) +(autoload 'denote-rename-add-signature "denote" "\ +Add to FILE name the SIGNATURE. +In interactive use, prompt for FILE, defaulting either to the current +buffer's file or the one at point in a Dired buffer. Also prompt for +SIGNATURE, using the existing one, if any, as the initial value. + +When called from Lisp, FILE is a string pointing to a file system path +and SIGNATURE is a string. + +Ask for confirmation before renaming the file to include the new +signature. Do it unless the user option `denote-rename-no-confirm' is +set to a non-nil value. + +Once the operation is done, reload any Dired buffers and run the +`denote-after-rename-file-hook'. + +Also see `denote-rename-remove-signature'. + +(fn FILE SIGNATURE)" t) +(autoload 'denote-rename-remove-signature "denote" "\ +Remove the signature of FILE. +In interactive use, prompt for FILE, defaulting either to the current +buffer's file or the one at point in a Dired buffer. When called from +Lisp, FILE is a string pointing to a file system path. + +Ask for confirmation before renaming the file to remove its signature. +Do it unless the user option `denote-rename-no-confirm' is set to a +non-nil value. + +Once the operation is done, reload any Dired buffers and run the +`denote-after-rename-file-hook'. + +Also see `denote-rename-add-signature'. + +(fn FILE)" t) +(autoload 'denote-add-front-matter "denote" "\ +Insert front matter at the top of FILE. + +When called interactively, FILE is the return value of the +function `buffer-file-name'. FILE is checked to determine +whether it is a note for Denote's purposes. + +TITLE is a string. Interactively, it is the user input at the +minibuffer prompt. + +KEYWORDS is a list of strings. Interactively, it is the user +input at the minibuffer prompt. This one supports completion for +multiple entries, each separated by the `crm-separator' (normally +a comma). + +The purpose of this command is to help the user generate new +front matter for an existing note (perhaps because the user +deleted the previous one and could not undo the change). + +This command does not rename the file (e.g. to update the +keywords). To rename a file by reading its front matter as +input, use `denote-rename-file-using-front-matter'. + +Note that this command is useful only for existing Denote notes. +If the user needs to convert a generic text file to a Denote +note, they can use one of the command which first rename the file +to make it comply with our file-naming scheme and then add the +relevant front matter. + +[ 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-29 09:24 +0200. ] + +(fn FILE TITLE KEYWORDS)" t) +(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 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. + +(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 `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. + +The DESCRIPTION is returned by the function specified in variable +`denote-link-description-function'. If the region is active, its +content is deleted and can be used as the description of the +link. The default value of `denote-link-description-function' +returns the content of the active region, if any, else the title +of the linked file is used as the description. The title comes +either from the front matter or the file name. Note that if you +change the default value of `denote-link-description-function', +make sure to use the `region-text' parameter. Regardless of the +value of this user option, `denote-link' will always replace the +content of the active region. + +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, the link is also as if ID-ONLY were non-nil. The +default value of `denote-link-description-function' returns an +empty string when the region is empty. Thus, the link will have +no description in this case. + +When called from Lisp, FILE is a string representing a full file +system path. FILE-TYPE is a symbol as described in +`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-link-with-signature "denote" "\ +Insert link to file with signature. +Prompt for file using minibuffer completion, limiting the list of +candidates to files with a signature in their file name. + +By default, the description of the link includes the signature, +if present, followed by the file's title, if any. + +For more advanced uses with Lisp, refer to the `denote-link' +function." t) +(function-put 'denote-link-with-signature 'interactive-only 't) +(autoload 'denote-find-link "denote" "\ +Use minibuffer completion to visit linked file." t) +(function-put 'denote-find-link 'interactive-only 't) +(autoload 'denote-find-backlink "denote" "\ +Use minibuffer completion to visit backlink to current file. + +Like `denote-find-link', but select backlink to follow." t) +(function-put 'denote-find-backlink '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. + +If TARGET file does not exist, add the user input that was used +to search for it to the minibuffer 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 +\\\\[previous-history-element]. + +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-link-buttonize-buffer "denote" "\ +Make denote: links actionable buttons in the current buffer. + +Buttonization applies to the plain text and Markdown file types, +per the user option `denote-file-types'. It will not do anything +in `org-mode' buffers, as buttons already work there. If you do +not use Markdown or plain text, then you do not need this. + +Links work when they point to a file inside the variable +`denote-directory'. + +To buttonize links automatically add this function to the +`find-file-hook'. Or call it interactively for on-demand +buttonization. + +When called from Lisp, with optional BEG and END as buffer +positions, limit the process to the region in-between. + +(fn &optional BEG END)" t) +(autoload 'denote-backlinks "denote" "\ +Produce a buffer with backlinks to the current note. + +The backlinks' buffer shows the file name of the note linking to +the current note, as well as the context of each link. + +File names are fontified by Denote if the user option +`denote-link-fontify-backlinks' is non-nil. If this user option +is nil, the buffer is fontified by Xref. + +The placement of the backlinks' buffer is controlled by the user +option `denote-link-backlinks-display-buffer-action'. By +default, it will show up below the current window." t) +(autoload 'denote-add-links "denote" "\ +Insert links to all notes matching 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-dired-marked-notes "denote" "\ +Insert Dired marked FILES as links in BUFFER. + +FILES are Denote notes, meaning that they have our file-naming +scheme, are writable/regular files, and use the appropriate file +type extension (per `denote-file-type'). Furthermore, the marked +files need to be inside the variable `denote-directory' or one of +its subdirectories. No other file is recognised (the list of +marked files ignores whatever does not count as a note for our +purposes). + +The BUFFER is one which visits a Denote note file. If there are +multiple buffers, prompt with completion for one among them. If +there isn't one, 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 +search option akin to that of standard Org `file:' link types. +Read Info node `(org) Search Options'. + +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. +Also see the user option `denote-org-store-link-to-heading'.") +(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-faces-link :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)") +(register-definition-prefixes "denote" '("denote-")) + + +;;; Generated autoloads from denote-journal-extras.el + +(autoload 'denote-journal-extras-new-entry "denote-journal-extras" "\ +Create a new journal entry in variable `denote-journal-extras-directory'. +Use `denote-journal-extras-keyword' as a keyword for the newly +created file. Set the title of the new entry according to the +value of the user option `denote-journal-extras-title-format'. + +With optional DATE as a prefix argument, prompt for a date. If +`denote-date-prompt-use-org-read-date' is non-nil, use the Org +date selection module. + +When called from Lisp DATE is a string and has the same format as +that covered in the documentation of the `denote' function. It +is internally processed by `denote-parse-date'. + +(fn &optional DATE)" t) +(autoload 'denote-journal-extras-new-or-existing-entry "denote-journal-extras" "\ +Locate an existing journal entry or create a new one. +A journal entry is one that has `denote-journal-extras-keyword' as +part of its file name. + +If there are multiple journal entries for the current date, +prompt for one using minibuffer completion. If there is only +one, visit it outright. If there is no journal entry, create one +by calling `denote-journal-extra-new-entry'. + +With optional DATE as a prefix argument, prompt for a date. If +`denote-date-prompt-use-org-read-date' is non-nil, use the Org +date selection module. + +When called from Lisp, DATE is a string and has the same format +as that covered in the documentation of the `denote' function. +It is internally processed by `denote-parse-date'. + +(fn &optional DATE)" t) +(autoload 'denote-journal-extras-link-or-create-entry "denote-journal-extras" "\ +Use `denote-link' on journal entry, creating it if necessary. +A journal entry is one that has `denote-journal-extras-keyword' as +part of its file name. + +If there are multiple journal entries for the current date, +prompt for one using minibuffer completion. If there is only +one, link to it outright. If there is no journal entry, create one +by calling `denote-journal-extra-new-entry' and link to it. + +With optional DATE as a prefix argument, prompt for a date. If +`denote-date-prompt-use-org-read-date' is non-nil, use the Org +date selection module. + +When called from Lisp, DATE is a string and has the same format +as that covered in the documentation of the `denote' function. +It is internally processed by `denote-parse-date'. + +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 &optional DATE ID-ONLY)" t) +(register-definition-prefixes "denote-journal-extras" '("denote-journal-extras-")) + + +;;; Generated autoloads from denote-org-extras.el + +(autoload 'denote-org-extras-link-to-heading "denote-org-extras" "\ +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-extras-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." '(org-mode)) +(function-put 'denote-org-extras-link-to-heading 'interactive-only 't) +(autoload 'denote-org-extras-extract-org-subtree "denote-org-extras" "\ +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 +`denote-file-type'." '(org-mode)) +(autoload 'denote-org-extras-convert-links-to-file-type "denote-org-extras" "\ +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-extras-convert-links-to-denote-type "denote-org-extras" "\ +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-extras-dblock-insert-links "denote-org-extras" "\ +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-extras-dblock-insert-links))) +(autoload 'denote-org-extras-dblock-insert-missing-links "denote-org-extras" "\ +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-extras-dblock-insert-links))) +(autoload 'denote-org-extras-dblock-insert-backlinks "denote-org-extras" "\ +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-extras-dblock-insert-backlinks))) +(autoload 'denote-org-extras-dblock-insert-files "denote-org-extras" "\ +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-extras-dblock-insert-files))) +(register-definition-prefixes "denote-org-extras" '("denote-org-extras-" "org-dblock-write:denote-")) + + +;;; Generated autoloads from denote-rename-buffer.el + +(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-rename-buffer" nil) +(autoload 'denote-rename-buffer-mode "denote-rename-buffer" "\ +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-rename-buffer" '("denote-rename-buffer")) + + +;;; Generated autoloads from denote-silo-extras.el + +(autoload 'denote-silo-extras-create-note "denote-silo-extras" "\ +Select SILO and run `denote' in it. +SILO is a file path from `denote-silo-extras-directories'. + +When called from Lisp, SILO is a file system path to a directory. + +(fn SILO)" t) +(autoload 'denote-silo-extras-open-or-create "denote-silo-extras" "\ +Select SILO and run `denote-open-or-create' in it. +SILO is a file path from `denote-silo-extras-directories'. + +When called from Lisp, SILO is a file system path to a directory. + +(fn SILO)" t) +(autoload 'denote-silo-extras-select-silo-then-command "denote-silo-extras" "\ +Select SILO and run Denote COMMAND in it. +SILO is a file path from `denote-silo-extras-directories', while +COMMAND is one among `denote-silo-extras-commands'. + +When called from Lisp, SILO is a file system path to a directory. + +(fn SILO COMMAND)" t) +(register-definition-prefixes "denote-silo-extras" '("denote-silo-extras-")) + + +;;; Generated autoloads from denote-sort.el + +(autoload 'denote-sort-files "denote-sort" "\ +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 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-sort" "\ +Produce Dired dired-buffer with sorted files from variable `denote-directory'. +When called interactively, prompt for FILES-MATCHING-REGEXP, +SORT-BY-COMPONENT, and REVERSE. + +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'). + +3. REVERSE is a boolean to reverse the order when it is a non-nil value. + +When called from Lisp, the arguments are a string, a keyword, and +a non-nil value, respectively. + +(fn FILES-MATCHING-REGEXP SORT-BY-COMPONENT REVERSE)" t) +(register-definition-prefixes "denote-sort" '("denote-sort-")) + +;;; 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 + c017fab2996d7bc0f661798482dd37722c65901a (mode 644) --- /dev/null +++ elpa/denote-2.3.5/denote-journal-extras.el @@ -0,0 +1,249 @@ +;;; denote-journal-extras.el --- Convenience functions for daily journaling -*- lexical-binding: t; -*- + +;; Copyright (C) 2023-2024 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: + +;; This is a set of optional convenience functions that used to be +;; provided in the Denote manual. They facilitate the use of Denote +;; for daily journaling. + +;;; Code: + +(require 'denote) + +(defgroup denote-journal-extras nil + "Denote for daily journaling." + :group 'denote + :link '(info-link "(denote) Top") + :link '(url-link :tag "Homepage" "https://protesilaos.com/emacs/denote")) + +(defcustom denote-journal-extras-directory + (expand-file-name "journal" denote-directory) + "Directory for storing daily journal entries. +This can either be the same as the variable `denote-directory' or +a subdirectory of it. + +A value of nil means to use the variable `denote-directory'. +Journal entries will thus be in a flat listing together with all +other notes. They can still be retrieved easily by searching for +the `denote-journal-extras-keyword'." + :group 'denote-journal-extras + :type '(choice (directory :tag "Provide directory path (is created if missing)") + (const :tag "Use the `denote-directory'" nil))) + +(defcustom denote-journal-extras-keyword "journal" + "Single word keyword to tag journal entries. +It is used by `denote-journal-extras-new-entry' to add a keyword +to the newly created file." + :group 'denote-journal-extras + :type 'string) + +(defcustom denote-journal-extras-title-format 'day-date-month-year-24h + "Date format to construct the title with `denote-journal-extras-new-entry'. +The value is either a symbol or an arbitrary string that is +passed to `format-time-string' (consult its documentation for the +technicalities). + +Acceptable symbols and their corresponding styles are: + +| Symbol | Style | +|-------------------------+-----------------------------------| +| day | Monday | +| day-date-month-year | Monday 19 September 2023 | +| day-date-month-year-24h | Monday 19 September 2023 20:49 | +| day-date-month-year-12h | Monday 19 September 2023 08:49 PM | + +With a nil value, make `denote-journal-extras-new-entry' prompt +for a title." + :group 'denote-journal-extras + :type '(choice + (const :tag "Prompt for title with `denote-journal-extras-new-entry'" nil) + (const :tag "Monday" + :doc "The `format-time-string' is: %A" + day) + (const :tag "Monday 19 September 2023" + :doc "The `format-time-string' is: %A %e %B %Y" + day-date-month-year) + (const :tag "Monday 19 September 2023 20:49" + :doc "The `format-time-string' is: %A %e %B %Y %H:%M" + day-date-month-year-24h) + (const :tag "Monday 19 September 2023 08:49 PM" + :doc "The `format-time-string' is: %A %e %B %Y %I:%M %^p" + day-date-month-year-12h) + (string :tag "Custom string with `format-time-string' specifiers"))) + +(defcustom denote-journal-extras-hook nil + "Normal hook called after `denote-journal-extras-new-entry'. +Use this to, for example, set a timer after starting a new +journal entry (refer to the `tmr' package on GNU ELPA)." + :group 'denote-journal-extras + :type 'hook) + +(defun denote-journal-extras-directory () + "Make the variable `denote-journal-extras-directory' and its parents." + (if-let (((stringp denote-journal-extras-directory)) + (directory (file-name-as-directory (expand-file-name denote-journal-extras-directory)))) + (progn + (when (not (file-directory-p denote-journal-extras-directory)) + (make-directory directory :parents)) + directory) + (denote-directory))) + +(defun denote-journal-extras-daily--title-format (&optional date) + "Return present date in `denote-journal-extras-title-format' or prompt for title. +With optional DATE, use it instead of the present date. DATE has +the same format as that returned by `current-time'." + (format-time-string + (if (and denote-journal-extras-title-format + (stringp denote-journal-extras-title-format)) + denote-journal-extras-title-format + (pcase denote-journal-extras-title-format + ('day "%A") + ('day-date-month-year "%A %e %B %Y") + ('day-date-month-year-24h "%A %e %B %Y %H:%M") + ('day-date-month-year-12h "%A %e %B %Y %I:%M %^p") + (_ (denote-title-prompt (format-time-string "%F" date))))) + date)) + +(defun denote-journal-extras--get-template () + "Return template that has `journal' key in `denote-templates'. +If no template with `journal' key exists but `denote-templates' +is non-nil, prompt the user for a template among +`denote-templates'. Else return nil. + +Also see `denote-journal-extras-new-entry'." + (if-let ((template (alist-get 'journal denote-templates))) + template + (when denote-templates + (denote-template-prompt)))) + +;;;###autoload +(defun denote-journal-extras-new-entry (&optional date) + "Create a new journal entry in variable `denote-journal-extras-directory'. +Use `denote-journal-extras-keyword' as a keyword for the newly +created file. Set the title of the new entry according to the +value of the user option `denote-journal-extras-title-format'. + +With optional DATE as a prefix argument, prompt for a date. If +`denote-date-prompt-use-org-read-date' is non-nil, use the Org +date selection module. + +When called from Lisp DATE is a string and has the same format as +that covered in the documentation of the `denote' function. It +is internally processed by `denote-parse-date'." + (interactive (list (when current-prefix-arg (denote-date-prompt)))) + (let ((internal-date (denote-parse-date date)) + (denote-directory (denote-journal-extras-directory))) + (denote + (denote-journal-extras-daily--title-format internal-date) + `(,denote-journal-extras-keyword) + nil nil date + (denote-journal-extras--get-template)) + (run-hooks 'denote-journal-extras-hook))) + +(defun denote-journal-extras--entry-today (&optional date) + "Return list of files matching a journal for today or optional DATE. +DATE has the same format as that returned by `denote-parse-date'." + (denote-directory-files + (format "%sT[0-9]\\{6\\}.*_%s" + (format-time-string "%Y%m%d" date) + denote-journal-extras-keyword))) + +;;;###autoload +(defun denote-journal-extras-new-or-existing-entry (&optional date) + "Locate an existing journal entry or create a new one. +A journal entry is one that has `denote-journal-extras-keyword' as +part of its file name. + +If there are multiple journal entries for the current date, +prompt for one using minibuffer completion. If there is only +one, visit it outright. If there is no journal entry, create one +by calling `denote-journal-extra-new-entry'. + +With optional DATE as a prefix argument, prompt for a date. If +`denote-date-prompt-use-org-read-date' is non-nil, use the Org +date selection module. + +When called from Lisp, DATE is a string and has the same format +as that covered in the documentation of the `denote' function. +It is internally processed by `denote-parse-date'." + (interactive + (list + (when current-prefix-arg + (denote-date-prompt)))) + (let* ((internal-date (denote-parse-date date)) + (files (denote-journal-extras--entry-today internal-date))) + (cond + ((length> files 1) + (find-file (completing-read "Select journal entry: " files nil :require-match))) + (files + (find-file (car files))) + (t + (denote-journal-extras-new-entry date))))) + +;;;###autoload +(defun denote-journal-extras-link-or-create-entry (&optional date id-only) + "Use `denote-link' on journal entry, creating it if necessary. +A journal entry is one that has `denote-journal-extras-keyword' as +part of its file name. + +If there are multiple journal entries for the current date, +prompt for one using minibuffer completion. If there is only +one, link to it outright. If there is no journal entry, create one +by calling `denote-journal-extra-new-entry' and link to it. + +With optional DATE as a prefix argument, prompt for a date. If +`denote-date-prompt-use-org-read-date' is non-nil, use the Org +date selection module. + +When called from Lisp, DATE is a string and has the same format +as that covered in the documentation of the `denote' function. +It is internally processed by `denote-parse-date'. + +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 + (pcase current-prefix-arg + ('(16) (list (denote-date-prompt) :id-only)) + ('(4) (list (denote-date-prompt))))) + (let* ((internal-date (denote-parse-date date)) + (files (denote-journal-extras--entry-today internal-date)) + (path)) + (cond + ((length> files 1) + (setq path (completing-read "Select journal entry: " files nil :require-match))) + (files + (setq path (car files))) + (t + (save-window-excursion + (denote-journal-extras-new-entry date) + (save-buffer) + (setq path (buffer-file-name))))) + (denote-link path + (denote-filetype-heuristics (buffer-file-name)) + (denote--link-get-description path) + id-only))) + +(provide 'denote-journal-extras) +;;; denote-journal-extras.el ends here blob - /dev/null blob + 03a97dbce0e8ac1329591adbdf74df68cdde1487 (mode 644) --- /dev/null +++ elpa/denote-2.3.5/denote-org-dblock.el @@ -0,0 +1,70 @@ +;;; denote-org-dblock.el --- Compatibility alieases for Denote Org Dynamic blocks -*- lexical-binding: t -*- + +;; Copyright (C) 2022-2024 Free Software Foundation, Inc. + +;; Authors: Elias Storms , +;; Protesilaos Stavrou +;; Maintainer: Protesilaos Stavrou +;; Deprecated-since: 3.0.0 +;; 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: +;; +;; This file defines compatibility aliases for Org dynamic blocks set +;; up by Denote. The new source of these is the denote-org-extras.el. +;; +;; Below is the old commentary. +;; +;; * * * +;; +;; This file 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") + +;;; Code: + +(require 'denote-org-extras) + +(define-obsolete-function-alias + 'denote-org-dblock-insert-links + 'denote-org-extras-dblock-insert-links + "3.0.0") + +(define-obsolete-function-alias + 'denote-org-dblock-insert-backlinks + 'denote-org-extras-dblock-insert-backlinks + "3.0.0") + +(define-obsolete-function-alias + 'denote-org-dblock-insert-files + 'denote-org-extras-dblock-insert-files + "3.0.0") + +(display-warning + 'denote + "`denote-org-dblock.el' is obsolete; use `denote-org-extras.el'" + :warning) + +(provide 'denote-org-dblock) +;;; denote-org-dblock.el ends here blob - /dev/null blob + 38772bc9be3a3c9af33a71406f2e4ea147335585 (mode 644) --- /dev/null +++ elpa/denote-2.3.5/denote-org-extras.el @@ -0,0 +1,544 @@ +;;; denote-org-extras.el --- Denote extensions for Org mode -*- lexical-binding: t -*- + +;; Copyright (C) 2024 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: +;; +;; WORK-IN-PROGRESS + +;;; Code: + +(require 'denote) +(require 'denote-sort) +(require 'org) + +;;;; Link to file and heading + +(defun denote-org-extras--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) "[*\^L]+"))) + 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"))))) + +(defun denote-org-extras--outline-prompt (&optional file) + "Prompt for outline among headings retrieved by `denote-org-extras--get-outline'. +With optional FILE use the outline of it, otherwise use that of +the current file." + (completing-read + (format "Select heading inside `%s': " + (propertize (file-name-nondirectory file) 'face 'denote-faces-prompt-current-name)) + (denote--completion-table-no-sort 'imenu (denote-org-extras--get-outline (or file buffer-file-name))) + nil :require-match)) + +(defun denote-org-extras--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 line) + (cons (denote-link-ol-get-heading) (denote-link-ol-get-id))))) + +(defun denote-org-extras-format-link-with-heading (file heading-id description) + "Prepare link to FILE with HEADING-ID using DESCRIPTION." + (format "[[denote:%s::#%s][%s]]" + (denote-retrieve-filename-identifier file) + heading-id + description)) + +;;;###autoload +(defun denote-org-extras-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-extras-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." + (declare (interactive-only t)) + (interactive nil org-mode) + (unless (derived-mode-p 'org-mode) + (user-error "Links to headings only work between Org files")) + (when-let ((file (denote-file-prompt ".*\\.org")) + (file-text (denote--link-get-description file)) + (heading (denote-org-extras--outline-prompt file)) + (line (string-to-number (car (split-string heading "\t")))) + (heading-data (denote-org-extras--get-heading-and-id-from-line line file)) + (heading-text (car heading-data)) + (heading-id (cdr heading-data)) + (description (denote-link-format-heading-description file-text heading-text))) + (insert (denote-org-extras-format-link-with-heading file heading-id description)))) + +;;;; Extract subtree into its own note + +(defun denote-org-extras--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))) + +;;;###autoload +(defun denote-org-extras-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 +`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-extras--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 nil signature) + (insert text)) + (user-error "No subtree to extract; aborting"))) + +;;;; Convert links from `:denote' to `:file' and vice versa + +;; TODO 2024-02-28: Do we need to convert between other link types? I +;; think not, since the `denote:' type is modelled after the `file:' +;; one. +(defun denote-org-extras--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))) + +;;;###autoload +(defun denote-org-extras-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) + (progn + (goto-char (point-min)) + (while (re-search-forward (denote-org-extras--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-get-path-by-id id)))) + (when id + (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))))) + ;; TODO 2024-02-28: notify how many changed. + (message "Converted `denote:' links to `file:' links")) + (user-error "The current file is not using Org mode"))) + +;;;###autoload +(defun denote-org-extras-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) + (progn + (goto-char (point-min)) + (while (re-search-forward (denote-org-extras--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))))) + ;; TODO 2024-02-28: notify how many changed. + (message "Converted as `file:' links to `denote:' links")) + (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-extras-dblock--files (files-matching-regexp &optional sort-by-component reverse) + "Return list of FILES-MATCHING-REGEXP in variable `denote-directory'. +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-extras-dblock--files-missing-only'." + (cond + ((and sort-by-component reverse) + (denote-sort-get-directory-files files-matching-regexp sort-by-component reverse :omit-current)) + (sort-by-component + (denote-sort-get-directory-files files-matching-regexp sort-by-component nil :omit-current)) + (reverse + (denote-sort-get-directory-files files-matching-regexp :no-component-specified reverse :omit-current)) + (t + (denote-directory-files files-matching-regexp :omit-current)))) + +(defun denote-org-extras-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-extras-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-extras-dblock--files'." + (denote-sort-files + (denote-org-extras-dblock--get-missing-links files-matching-regexp) + sort-by-component + reverse)) + +;;;;; Dynamic block to insert links + +;;;###autoload +(defun denote-org-extras-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 + :sort-by-component nil + :reverse-sort nil + :id-only 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-extras-dblock-insert-links))) + +(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* ((regexp (plist-get params :regexp)) + (rx (if (listp regexp) (macroexpand `(rx ,regexp)) regexp)) + (sort (plist-get params :sort-by-component)) + (reverse (plist-get params :reverse-sort)) + (block-name (plist-get params :block-name)) + (files (denote-org-extras-dblock--files 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) + (join-line))) ; remove trailing empty line + +;;;;; Dynamic block to insert missing links + +;;;###autoload +(defun denote-org-extras-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 + :sort-by-component nil + :reverse-sort nil + :id-only 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-extras-dblock-insert-links))) + +(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* ((regexp (plist-get params :regexp)) + (rx (if (listp regexp) (macroexpand `(rx ,regexp)) regexp)) + (sort (plist-get params :sort-by-component)) + (reverse (plist-get params :reverse-sort)) + (block-name (plist-get params :block-name)) + (files (denote-org-extras-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) + (join-line))) ; remove trailing empty line + +;;;;; Dynamic block to insert backlinks + +(defun denote-org-extras-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))) + +;;;###autoload +(defun denote-org-extras-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" + :sort-by-component nil + :reverse-sort nil + :id-only 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-extras-dblock-insert-backlinks))) + +(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 (denote-link-return-backlinks))) + (let* ((sort (plist-get params :sort-by-component)) + (reverse (plist-get params :reverse-sort)) + (files (denote-org-extras-dblock--maybe-sort-backlinks files sort reverse))) + (denote-link--insert-links files 'org (plist-get params :id-only) :no-other-sorting) + (join-line)))) ; remove trailing empty line + +;;;;; Dynamic block to insert entire file contents + +(defun denote-org-extras-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--link-get-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-extras-dblock-file-contents-separator + (concat "\n\n" (make-string 50 ?-) "\n\n\n") + "Fallback separator used by `denote-org-extras-dblock-add-files'.") + +(defun denote-org-extras-dblock--separator (separator) + "Return appropriate value of SEPARATOR for `denote-org-extras-dblock-add-files'." + (cond + ((null separator) "") + ((stringp separator) separator) + (t denote-org-extras-dblock-file-contents-separator))) + +(defun denote-org-extras-dblock-add-files (regexp &optional separator no-front-matter add-links sort-by-component reverse) + "Insert files matching REGEXP. + +Seaprate them with the optional SEPARATOR. If SEPARATOR is nil, +use the `denote-org-extras-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." + (let* ((files (denote-org-extras-dblock--files regexp sort-by-component reverse)) + (files-contents (mapcar + (lambda (file) (denote-org-extras-dblock--get-file-contents file no-front-matter add-links)) + files))) + (insert (string-join files-contents (denote-org-extras-dblock--separator separator))))) + +;;;###autoload +(defun denote-org-extras-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 + :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-extras-dblock-insert-files))) + +(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* ((regexp (plist-get params :regexp)) + (rx (if (listp regexp) (macroexpand `(rx ,regexp)) 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))) + (when block-name (insert "#+name: " block-name "\n")) + (when rx (denote-org-extras-dblock-add-files rx separator no-f-m add-links sort reverse))) + (join-line)) ; remove trailing empty line + + +(provide 'denote-org-extras) +;;; denote-org-extras.el ends here blob - /dev/null blob + 3f77c277346522b8a454f5e529d3398f4d10d116 (mode 644) --- /dev/null +++ elpa/denote-2.3.5/denote-pkg.el @@ -0,0 +1,2 @@ +;; Generated package description from denote.el -*- no-byte-compile: t -*- +(define-package "denote" "2.3.5" "Simple notes with an efficient file-naming scheme" '((emacs "28.1")) :commit "914e5be6c1ad319cf924aaddc3515aaffe1d4bcc" :authors '(("Protesilaos Stavrou" . "info@protesilaos.com")) :maintainer '("Protesilaos Stavrou" . "info@protesilaos.com") :url "https://github.com/protesilaos/denote") blob - /dev/null blob + 1335d3a22c7e8cf1e62e3db855dcab15123bb3f0 (mode 644) --- /dev/null +++ elpa/denote-2.3.5/denote-rename-buffer.el @@ -0,0 +1,159 @@ +;;; denote-rename-buffer.el --- Rename Denote buffers to be shorter and easier to read -*- lexical-binding: t -*- + +;; Copyright (C) 2023-2024 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: +;; +;; Rename Denote buffers to be shorter and easier to read. Enable +;; `denote-rename-buffer-mode' to automatically rename the buffer of a +;; Denote file. The renaming function is specified in the user option +;; `denote-rename-buffer-function'. + +;;; Code: + +(require 'denote) + +(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")) + +(defcustom denote-rename-buffer-format "%t" + "The format of the buffer name `denote-rename-buffer' should use. +Thie value is a string that treats specially the following +specifiers: + +- The %t is the Denote TITLE of the file. +- The %i is the Denote IDENTIFIER of the file. +- 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 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 . "2.1.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)) + (type (denote-filetype-heuristics 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 ?i (or (denote-retrieve-filename-identifier file) "")) + (cons ?d (or (denote-retrieve-filename-identifier file) "")) + (cons ?s (or (denote-retrieve-filename-signature file) "")) + (cons ?k (if-let ((kws (denote-retrieve-front-matter-keywords-value file type))) + (denote-keywords-combine kws) + (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))) + +(make-obsolete + 'denote-rename-buffer-with-title + 'denote-rename-buffer + "2.1.0") + +(make-obsolete + 'denote-rename-buffer-with-identifier + 'denote-rename-buffer + "2.1.0") + +(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-rename-buffer) +;;; denote-rename-buffer.el ends here blob - /dev/null blob + 1e4cfd5155e4e1ada1ede5f2a2e09e5d76fd3d92 (mode 644) --- /dev/null +++ elpa/denote-2.3.5/denote-silo-extras.el @@ -0,0 +1,102 @@ +;;; denote-silo-extras.el --- Convenience functions for using Denote in multiple silos -*- lexical-binding: t; -*- + +;; Copyright (C) 2023-2024 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: + +;; This is a set of convenience functions that used to be provided in +;; the Denote manual. A "silo" is a `denote-directory' that is +;; self-contained. Users can maintain multiple silos. Consult the +;; manual for the details. With the Denote package installed, +;; evaluate the following to read the relevant node: +;; +;; (info "(denote) Maintain separate directory silos for notes") + +;;; Code: + +(require 'denote) + +(defgroup denote-silo-extras nil + "Make it easier to use Denote across Silos." + :group 'denote + :link '(info-link "(denote) Top") + :link '(url-link :tag "Homepage" "https://protesilaos.com/emacs/denote")) + +(defcustom denote-silo-extras-directories + `(,denote-directory) + "List of file paths pointing to Denote silos. +Each file path points to a directory, which takes the same value +as the variable `denote-directory'." + :group 'denote-silo-extras + :link '(info-link "(denote) Maintain separate directories for notes") + :type '(repeat directory)) + +(defvar denote-silo-extras-directory-history nil + "Minibuffer history for `denote-silo-extras--directory-prompt'.") + +(defalias 'denote-silo-extras--directory-history 'denote-silo-extras-directory-history + "Compatibility alias for `denote-silo-extras-directory-history'.") + +(defun denote-silo-extras--directory-prompt () + "Prompt for directory among `denote-silo-extras-directories'." + (let ((default (car denote-silo-extras-directory-history))) + (completing-read + (format-prompt "Select a silo" default) + denote-silo-extras-directories nil :require-match + nil 'denote-silo-extras-directory-history))) + +;;;###autoload +(defun denote-silo-extras-create-note (silo) + "Select SILO and run `denote' in it. +SILO is a file path from `denote-silo-extras-directories'. + +When called from Lisp, SILO is a file system path to a directory." + (interactive (list (denote-silo-extras--directory-prompt))) + (let ((denote-directory silo)) + (call-interactively #'denote))) + +;;;###autoload +(defun denote-silo-extras-open-or-create (silo) + "Select SILO and run `denote-open-or-create' in it. +SILO is a file path from `denote-silo-extras-directories'. + +When called from Lisp, SILO is a file system path to a directory." + (interactive (list (denote-silo-extras--directory-prompt))) + (let ((denote-directory silo)) + (call-interactively #'denote-open-or-create))) + +;;;###autoload +(defun denote-silo-extras-select-silo-then-command (silo command) + "Select SILO and run Denote COMMAND in it. +SILO is a file path from `denote-silo-extras-directories', while +COMMAND is one among `denote-silo-extras-commands'. + +When called from Lisp, SILO is a file system path to a directory." + (interactive + (list + (denote-silo-extras--directory-prompt) + (denote-command-prompt))) + (let ((denote-directory silo)) + (call-interactively command))) + +(provide 'denote-silo-extras) +;;; denote-silo-extras.el ends here blob - /dev/null blob + de443dc9bcecbcc118891fb70e71484189a24131 (mode 644) --- /dev/null +++ elpa/denote-2.3.5/denote-sort.el @@ -0,0 +1,201 @@ +;;; denote-sort.el --- Sort Denote files based on a file name component -*- lexical-binding: t -*- + +;; Copyright (C) 2023-2024 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: +;; +;; Sort Denote files based on their file name components, namely, the +;; signature, title, or keywords. + +;;; Code: + +(require 'denote) + +(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")) + +(defvar denote-sort-comparison-function #'string-collate-lessp + "String comparison function used by `denote-sort-files' subroutines.") + +(defvar denote-sort-components '(title keywords signature identifier) + "List of sorting keys applicable for `denote-sort-files' and related.") + +;; 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)))) + `(defun ,(intern (format "denote-sort-%s-lessp" component)) (file1 file2) + ,(format + "Return smallest between FILE1 and FILE2 based on their %s. +The comparison is done with `denote-sort-comparison-function' between the +two title values." + component) + (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 denote-sort-comparison-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 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 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 (when component + (pcase component + ('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) + "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." + (denote-sort-files + (denote-directory-files files-matching-regexp omit-current) + sort-by-component + reverse)) + +(defun denote-sort-get-links (files-matching-regexp sort-by-component current-file-type id-only &optional reverse) + "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 +`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." + (denote-link--prepare-links + (denote-sort-get-directory-files files-matching-regexp sort-by-component reverse) + 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 `denote-sort-files' 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-local denote-sort--dired-buffer nil + "Buffer object of current `denote-sort-dired'.") + +;;;###autoload +(defun denote-sort-dired (files-matching-regexp sort-by-component reverse) + "Produce Dired dired-buffer with sorted files from variable `denote-directory'. +When called interactively, prompt for FILES-MATCHING-REGEXP, +SORT-BY-COMPONENT, and REVERSE. + +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'). + +3. REVERSE is a boolean to reverse the order when it is a non-nil value. + +When called from Lisp, the arguments are a string, a keyword, and +a non-nil value, respectively." + (interactive + (list + (denote-files-matching-regexp-prompt) + (denote-sort-component-prompt) + (y-or-n-p "Reverse sort? "))) + (if-let ((default-directory (denote-directory)) + (files (denote-sort-get-directory-files files-matching-regexp sort-by-component reverse)) + ;; 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 sort-by-component)) + (buffer-name (format "Denote sort by `%s' at %s" sort-by-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 _) + (kill-buffer dired-buffer) + (denote-sort-dired files-matching-regexp sort-by-component reverse)))) + ;; 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))) + +(provide 'denote-sort) +;;; denote-sort.el ends here blob - /dev/null blob + 50c970a4e5128b26a114b24f282472a9be5bac2f (mode 644) --- /dev/null +++ elpa/denote-2.3.5/denote.el @@ -0,0 +1,4689 @@ +;;; denote.el --- Simple notes with an efficient file-naming scheme -*- lexical-binding: t -*- + +;; Copyright (C) 2022-2024 Free Software Foundation, Inc. + +;; Author: Protesilaos Stavrou +;; Maintainer: Protesilaos Stavrou +;; URL: https://github.com/protesilaos/denote +;; Version: 2.3.5 +;; 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") + +(define-obsolete-variable-alias + 'denote-user-enforced-denote-directory + 'denote-directory + "2.3.0") + +;;;###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) + +(defcustom denote-save-buffer-after-creation nil + "Control whether commands that creeate 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. + +If this user option is set to a non-nil value, such buffers are +saved automatically." + :group 'denote + :package-version '(denote . "2.3.0") + :type 'boolean) + +;;;###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 `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 + to establish a sequential relationship between files (e.g. 1, + 1a, 1b, 1b1, 1b2, ...). Signatures have no strictly defined + function and are up to the user to apply as they see fit. One + use-case is to implement Niklas Luhmann's Zettelkasten system + for a sequence of notes (Folgezettel). Signatures are not + included in a file's front matter. They are reserved solely + for creating a sequence in a file listing, at least for the + time being. To insert a link that includes the signature, use + the command `denote-link-with-signature'. + +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 (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'." + :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-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) + +(make-obsolete + 'denote-allow-multi-word-keywords + 'denote-file-name-letter-casing + "2.1.0") + +(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 `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 t + "Determine whether `org-store-link' links to the current Org heading. + +When non-nil store link to the current Org heading inside the +Denote file. If the heading does not have a CUSTOM_ID, create it +and include it in its PROPERTIES drawer. If a CUSTOM_ID exists, +take it as-is. + +Make 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][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 (though I deleted a few characters to not get +complaints from the byte compiler about long lines in the doc +string...). + +If this user option is set to nil, only store links to the Denote +file (using its identifier), but not to the given heading. This +is what Denote was doing in versions prior to 2.3.0. + +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. + +[ 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 . "2.3.0") + :type 'boolean) + +(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 . 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 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 string) + :package-version '(denote . "0.5.0") + :link '(info-link "(denote) The denote-templates option") + :group 'denote) + +(defcustom denote-backlinks-show-context nil + "When non-nil, show link context in the backlinks buffer. + +The context is the line a link to the current note is found in. +The context includes multiple links to the same note, if those +are present. + +When nil, only show a simple list of file names that link to the +current note." + :group 'denote + :package-version '(denote . "1.2.0") + :type 'boolean) + +(defcustom denote-rename-no-confirm nil + "Make renaming commands not prompt for confirmation and save buffers outright. + +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. + +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. It also does not +save the affected file's buffer to let the user inspect and +confirm the changes (such as by invoking the command +`diff-buffer-with-file'). + +With this user option bound to a non-nil value, buffers are saved +as well. 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. + +Specialised 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 + :package-version '(denote . "2.3.0") + :type 'boolean) + +(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-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) + "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)) + +(make-obsolete + 'denote-file-name-letter-casing + 'denote-file-name-slug-functions + "2.3.0") + +(defcustom denote-link-description-function #'denote-link-description-with-signature-and-title + "Function to create the description of links. + +The function specified takes a FILE argument and returns the description +as a string. + +By default, the title of the file is returned as the description. If +the file has a signature, it is prepended to the title." + :group 'denote + :type '(choice + (function :tag "Link to title and include signature, if present" denote-link-description-with-signature-and-title) + (function :tag "Custom function like `denote-link-description-with-signature-and-title'")) + :package-version '(denote . "2.3.0")) + +;;;; 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.") + +(defconst denote-excluded-punctuation-regexp "[][{}!@#$%^&*()=+'\"?,.\|;:~`‘’“”/]*" + "Punctionation that is removed from file names. +We consider those characters illegal for our purposes.") + +(defvar denote-excluded-punctuation-extra-regexp nil + "Additional punctuation that is removed from file names. +This variable is for advanced users who need to extend the +`denote-excluded-punctuation-regexp'. Once we have a better +understanding of what we should be omitting, we will update +things accordingly.") + +;;;; 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." + ;; NOTE 2024-02-09: We may want to remove this condition eventually. + ;; The reason is that we want to stop supporting the dir-local + ;; values of `default-directory' or `local' in favour of just + ;; specifying a string. I don't think we can delete this altogether + ;; though, as it will break existing configurations. + (if-let (((or (eq denote-directory 'default-directory) (eq denote-directory 'local))) + (silo-dir (denote--default-directory-is-silo-p))) + silo-dir + (let ((denote-directory (file-name-as-directory (expand-file-name denote-directory)))) + (denote--make-denote-directory) + denote-directory))) + +(defun denote--slug-no-punct (str &optional extra-characters) + "Remove punctuation from STR. +Concretely, replace with an empty string anything that matches +the `denote-excluded-punctuation-regexp' and +`denote-excluded-punctuation-extra-regexp'. + +EXTRA-CHARACTERS is an optional string that has the same meaning +as the aforementioned variables." + (dolist (regexp (list denote-excluded-punctuation-regexp + denote-excluded-punctuation-extra-regexp + extra-characters)) + (when (stringp regexp) + (setq str (replace-regexp-in-string regexp "" str)))) + str) + +(defun denote--slug-no-punct-for-signature (str &optional extra-characters) + "Remove punctuation (except = signs) from STR. + +This works the same way as `denote--slug-no-punct', except that = +signs are not removed from STR. + +EXTRA-CHARACTERS is an optional string. See +`denote--slug-no-punct' for its documentation." + (dolist (regexp (list denote-excluded-punctuation-regexp + denote-excluded-punctuation-extra-regexp + extra-characters)) + (when (stringp regexp) + (setq str (replace-regexp-in-string (string-replace "=" "" regexp) "" str)))) + str) + +(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--remove-dot-characters (str) + "Remove the dot character from STR." + (replace-regexp-in-string "\\." "" str)) + +(defun denote--trim-right-token-characters (str) + "Remove =, - and _ from the end of STR." + (string-trim-right str "[=_-]+")) + +(defun denote--replace-consecutive-token-characters (str) + "Replace consecutive characters with a single one in STR. +Spaces, underscores and equal signs are replaced with a single +one in str." + (replace-regexp-in-string + "-\\{2,\\}" "-" + (replace-regexp-in-string + "_\\{2,\\}" "_" + (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 +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))))) + +(make-obsolete + 'denote-letter-case + 'denote-sluggify + "2.3.0") + +(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-sluggify-title (str) + "Make STR an appropriate slug for title." + (downcase (denote--slug-hyphenate (denote--slug-no-punct str)))) + +(defun denote-sluggify-signature (str) + "Make STR an appropriate slug for signature." + (downcase (denote--slug-put-equals (denote--slug-no-punct-for-signature str "-+")))) + +(defun denote-sluggify-keyword (str) + "Sluggify STR while joining separate words." + (downcase + (replace-regexp-in-string + "-" "" + (denote--slug-hyphenate (denote--slug-no-punct str))))) + +(make-obsolete + 'denote-sluggify-and-join + 'denote-sluggify-keyword + "2.3.0") + +(defun denote-sluggify-keywords (keywords) + "Sluggify KEYWORDS, which is a list of strings." + (mapcar (lambda (keyword) + (denote-sluggify 'keyword keyword)) + keywords)) + +(defun denote--file-empty-p (file) + "Return non-nil if FILE is empty." + (zerop (or (file-attribute-size (file-attributes file)) 0))) + +(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 `denote-file-type'." + (seq-some (lambda (e) + (string-suffix-p e file)) + (denote-file-type-extensions-with-encryption))) + +(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 `denote-file-type'." + (and (string-prefix-p (denote-directory) (expand-file-name 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 not be a directory, must satisfy +`file-regular-p' and `denote-filename-is-note-p'." + (and (not (file-directory-p file)) + (file-regular-p file) + (denote-filename-is-note-p file))) + +(defun denote-file-has-signature-p (file) + "Return non-nil if FILE has a Denote identifier." + (denote-retrieve-filename-signature file)) + +(make-obsolete 'denote-file-directory-p nil "2.0.0") + +(defun denote--file-regular-writable-p (file) + "Return non-nil if FILE is regular and writable." + (and (file-regular-p file) + (file-writable-p file))) + +(defun denote-file-is-writable-and-supported-p (file) + "Return non-nil if FILE is writable and has supported extension." + (and (denote--file-regular-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))) + +(define-obsolete-function-alias + 'denote--default-dir-has-denote-prefix + 'denote--dir-in-denote-directory-p + "2.1.0") + +(defun denote--exclude-directory-regexp-p (file) + "Return non-nil if FILE matches `denote-excluded-directories-regexp'." + (and 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--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 (denote-file-has-identifier-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) + "Return list of absolute file paths in variable `denote-directory'. + +Files only need to have an identifier. The return value may thus +include file types that are not implied by `denote-file-type'. + +Remember that the variable `denote-directory' accepts a dir-local +value, as explained in its doc string. + +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'." + (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-is-note-p files))) + files)) + +;; NOTE 2023-11-30: We are declaring `denote-directory-text-only-files' +;; obsolete, though we keep it around for the foreseeable future. It +;; WILL BE REMOVED ahead of version 3.0.0 of Denote, whenever that +;; happens. +(make-obsolete 'denote-directory-text-only-files 'denote-directory-files "2.2.0") + +(defun denote-directory-text-only-files () + "Return list of text files in variable `denote-directory'. +Filter `denote-directory-files' using `denote-file-is-note-p'." + (denote-directory-files nil nil :text-only)) + +(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))) + +(define-obsolete-variable-alias + 'denote--encryption-file-extensions + 'denote-encryption-file-extensions + "2.0.0") + +;; 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.") + +(define-obsolete-function-alias + 'denote--extensions-with-encryption + 'denote-file-type-extensions-with-encryption + "2.0.0") + +(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-prefix-p id (file-name-nondirectory file))) + (denote-directory-files)))) + (if (length< files 2) + (car files) + (seq-find + (lambda (file) + (let ((file-extension (denote-get-file-extension file))) + (and (denote-file-is-note-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’)." + (file-relative-name (denote-get-path-by-id id) directory)) + +;; NOTE 2023-11-30: We are declaring `denote-directory-files-matching-regexp' +;; obsolete, though we keep it around for the foreseeable future. It +;; WILL BE REMOVED ahead of version 3.0.0 of Denote, whenever that +;; happens. +(make-obsolete 'denote-directory-files-matching-regexp 'denote-directory-files "2.2.0") + +(defun denote-directory-files-matching-regexp (regexp) + "Return list of files matching REGEXP in `denote-directory-files'." + (denote-directory-files regexp)) + +;; NOTE 2023-11-30: We are declaring `denote-all-files' obsolete, +;; though we keep it around for the foreseeable future. It WILL BE +;; REMOVED ahead of version 3.0.0 of Denote, whenever that happens. +(make-obsolete 'denote-all-files 'denote-directory-files "2.2.0") + +(defun denote-all-files (&optional omit-current) + "Return the list of Denote files in variable `denote-directory'. +With optional OMIT-CURRENT, do not include the current Denote +file in the returned list." + (denote-directory-files nil omit-current nil)) + +(defvar denote-file-history nil + "Minibuffer history of `denote-file-prompt'.") + +(defalias 'denote--file-history 'denote-file-history + "Compatibility alias for `denote-file-history'.") + +;; NOTE 2024-02-29: Based on `project--read-file-cpd-relative' from +;; the built-in project.el +(defun denote-file-prompt (&optional files-matching-regexp prompt-text) + "Prompt for file with identifier in variable `denote-directory'. +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 NOTE\"." + (when-let ((all-files (denote-directory-files files-matching-regexp :omit-current))) + (let* ((common-parent-directory + (let ((common-prefix (try-completion "" all-files))) + (if (> (length common-prefix) 0) + (file-name-directory common-prefix)))) + (cpd-length (length common-parent-directory)) + (prompt-prefix (or prompt-text "Select FILE")) + (prompt (if (zerop cpd-length) + (format "%s: " prompt-prefix) + (format "%s in %s: " prompt-prefix common-parent-directory))) + (included-cpd (when (member common-parent-directory all-files) + (setq all-files + (delete common-parent-directory all-files)) + t)) + (substrings (mapcar (lambda (s) (substring s cpd-length)) all-files)) + (_ (when included-cpd + (setq substrings (cons "./" substrings)))) + (new-collection (denote--completion-table 'file substrings)) + (relname (completing-read prompt new-collection nil nil nil 'denote-file-history)) + (absname (expand-file-name relname common-parent-directory))) + ;; NOTE 2024-02-29: This delete and add feels awkward. I wish + ;; we could tell `completing-read' to just leave this up to us. + (setq denote-file-history (delete relname denote-file-history)) + (add-to-history 'denote-file-history absname) + absname))) + +;;;; 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." + (when-let ((kws (denote-retrieve-filename-keywords path))) + (split-string kws "_"))) + +(defun denote--inferred-keywords () + "Extract keywords from `denote-directory-files'. +This function returns duplicates. The `denote-keywords' is the +one that doesn't." + (let ((kw (mapcan #'denote-extract-keywords-from-path (denote-directory-files)))) + (if-let ((regexp denote-excluded-keywords-regexp)) + (seq-remove (apply-partially #'string-match-p regexp) kw) + kw))) + +(defun 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. + +Inferred keywords are filtered by the user option +`denote-excluded-keywords-regexp'." + (delete-dups + (if denote-infer-keywords + (append (denote--inferred-keywords) denote-known-keywords) + denote-known-keywords))) + +(defun denote-convert-file-name-keywords-to-crm (string) + "Make STRING with keywords readable by `completing-read-multiple'. +STRING consists of underscore-separated words, as those appear in +the keywords component of a Denote file name. STRING is the same +as the return value of `denote-retrieve-filename-keywords'." + (string-join (split-string string "_" :omit-nulls "_") ",")) + +(defvar denote-keyword-history nil + "Minibuffer history of inputted keywords.") + +(defalias 'denote--keyword-history 'denote-keyword-history + "Compatibility alias for `denote-keyword-history'.") + +(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) + "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. + +Return an empty list if the minibuffer input is empty." + (denote--keywords-crm (denote-keywords) 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)) + +(define-obsolete-function-alias + 'denote--keywords-combine + 'denote-keywords-combine + "2.1.0") + +(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 +\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 +---\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 ++++\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 +---------------------------\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'.") + +(define-obsolete-function-alias + 'denote-surround-with-quotes + 'denote-format-string-for-md-front-matter + "2.3.0") + +(defun denote-format-string-for-md-front-matter (s) + "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." + (if (stringp s) + (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. +If S is not a string, return an empty string." + (if (stringp s) 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 "[][ \"']+")) + +(defvar denote-file-types + '((org + :extension ".org" + :date-function denote-date-org-timestamp + :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 + :link denote-org-link-format + :link-in-context-regexp denote-org-link-in-context-regexp) + (markdown-yaml + :extension ".md" + :date-function denote-date-rfc3339 + :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 + :link denote-md-link-format + :link-in-context-regexp denote-md-link-in-context-regexp) + (markdown-toml + :extension ".md" + :date-function denote-date-rfc3339 + :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 + :link denote-md-link-format + :link-in-context-regexp denote-md-link-in-context-regexp) + (text + :extension ".txt" + :date-function denote-date-iso-8601 + :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 + :link denote-org-link-format + :link-in-context-regexp denote-org-link-in-context-regexp)) + "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: + +- `: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 `denote-file-type' is nil, use the first element of this list +for new note creation. The default is `org'.") + +(defun denote--date-format-function (file-type) + "Return date format function of FILE-TYPE." + (plist-get + (alist-get file-type denote-file-types) + :date-function)) + +(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." + (plist-get + (alist-get file-type denote-file-types) + :title-key-regexp)) + +(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." + (plist-get + (alist-get file-type denote-file-types) + :keywords-key-regexp)) + +(defun denote--keywords-value-function (file-type) + "Convert keywords' string 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--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))) + +(define-obsolete-function-alias + 'denote--extensions + 'denote-file-type-extensions + "2.0.0") + +(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--format-front-matter (title date keywords id filetype) + "Front matter for new notes. + +TITLE, DATE, and ID are all strings or functions that return a +string. KEYWORDS is a list of strings. FILETYPE is one of the +values of `denote-file-type'." + (let* ((fm (denote--front-matter filetype)) + (title (denote--format-front-matter-title title filetype)) + (kws (denote--format-front-matter-keywords keywords filetype))) + (if fm (format fm title date kws id) ""))) + +(defun denote--get-title-line-from-front-matter (title file-type) + "Retrieve title line from front matter based on FILE-TYPE. +Format TITLE in the title line. The returned line does not +contain the newline." + (let ((front-matter (denote--format-front-matter title "" nil "" file-type)) + (key-regexp (denote--title-key-regexp file-type))) + (with-temp-buffer + (insert front-matter) + (goto-char (point-min)) + (when (re-search-forward key-regexp nil t 1) + (buffer-substring-no-properties (line-beginning-position) (line-end-position)))))) + +(defun denote--get-keywords-line-from-front-matter (keywords file-type) + "Retrieve keywords line from front matter based on FILE-TYPE. +Format KEYWORDS in the keywords line. The returned line does not +contain the newline." + (let ((front-matter (denote--format-front-matter "" "" keywords "" file-type)) + (key-regexp (denote--keywords-key-regexp file-type))) + (with-temp-buffer + (insert front-matter) + (goto-char (point-min)) + (when (re-search-forward key-regexp nil t 1) + (buffer-substring-no-properties (line-beginning-position) (line-end-position)))))) + +;;;; 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, refer to the function +`denote-create-unique-file-identifier'." + (let ((filename (file-name-nondirectory file))) + (if (string-match (concat "\\`" denote-id-regexp) filename) + (match-string-no-properties 0 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 (&optional date) + "Convert DATE into a Denote identifier using `denote-id-format'. +DATE is parsed by `denote-valid-date-p'. If DATE is nil, use the +current time." + (format-time-string + denote-id-format + (when date (denote-valid-date-p date)))) + +(defun denote-create-unique-file-identifier (file used-ids &optional date) + "Generate a unique identifier for FILE not in USED-IDS hash-table. + +The conditions are as follows: + +- If optional DATE is non-nil pass it to `denote-get-identifier'. + DATE will have to conform with `denote-valid-date-p'. If it + does not, return an error. + +- If optional DATE is nil, use the file attributes to determine + the last modified date and format it as an identifier. + +- As a fallback, derive an identifier from the current time. + +To only return an existing identifier, refer to the function +`denote-retrieve-filename-identifier'." + (let ((id (cond + (date (denote-get-identifier date)) + ((denote--file-attributes-time file)) + (t (denote-get-identifier))))) + (denote--find-first-unused-id id used-ids))) + +(define-obsolete-function-alias + 'denote-retrieve-or-create-file-identifier + 'denote-retrieve-filename-identifier + "2.1.0") + +(defun denote-retrieve-filename-keywords (file) + "Extract keywords from FILE name, if present, else return nil. +Return matched keywords as a single string." + (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))) + +(defun denote-retrieve-front-matter-title-value (file file-type) + "Return title value from FILE front matter per FILE-TYPE." + (denote--file-with-temp-buffer file + (when (re-search-forward (denote--title-key-regexp file-type) nil t 1) + (funcall (denote--title-value-reverse-function file-type) + (buffer-substring-no-properties (point) (line-end-position)))))) + +(defun denote-retrieve-front-matter-title-line (file file-type) + "Return title line from FILE front matter per FILE-TYPE." + (denote--file-with-temp-buffer file + (when (re-search-forward (denote--title-key-regexp file-type) nil t 1) + (buffer-substring-no-properties (line-beginning-position) (line-end-position))))) + +(defun denote-retrieve-front-matter-keywords-value (file file-type) + "Return keywords value from FILE front matter per FILE-TYPE. +The return value is a list of strings. To get a combined string +the way it would appear in a Denote file name, use +`denote-retrieve-front-matter-keywords-value-as-string'." + (denote--file-with-temp-buffer file + (when (re-search-forward (denote--keywords-key-regexp file-type) nil t 1) + (funcall (denote--keywords-value-reverse-function file-type) + (buffer-substring-no-properties (point) (line-end-position)))))) + +(defun denote-retrieve-front-matter-keywords-value-as-string (file file-type) + "Return keywords value from FILE front matter per FILE-TYPE. +The return value is a string, with the underscrore as a separator +between individual keywords. To get a list of strings instead, +use `denote-retrieve-front-matter-keywords-value' (the current function uses +that internally)." + (denote-keywords-combine (denote-retrieve-front-matter-keywords-value file file-type))) + +(defun denote-retrieve-front-matter-keywords-line (file file-type) + "Return keywords line from FILE front matter per FILE-TYPE." + (denote--file-with-temp-buffer file + (when (re-search-forward (denote--keywords-key-regexp file-type) nil t 1) + (buffer-substring-no-properties (line-beginning-position) (line-end-position))))) + +(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'.") + +(defalias 'denote-retrieve-keywords-value-as-string 'denote-retrieve-front-matter-keywords-value-as-string + "Alias for `denote-retrieve-front-matter-keywords-value-as-string'.") + +(define-obsolete-function-alias + 'denote--retrieve-title-or-filename + 'denote-retrieve-title-or-filename + "2.3.0") + +(defun denote-retrieve-title-or-filename (file type) + "Return appropriate title for FILE given its TYPE. +Try to find the value of the title in the front matter of FILE, +otherwise use its file name. + +This is a wrapper for `denote-retrieve-front-matter-title-value' and +`denote-retrieve-filename-title'." + (if-let (((denote-file-is-note-p file)) + (title (denote-retrieve-front-matter-title-value file type)) + ((not (string-blank-p title)))) + title + (or (denote-retrieve-filename-title file) + (file-name-base file)))) + +(defun denote--retrieve-location-in-xrefs (identifier) + "Return list of xrefs for IDENTIFIER with their respective location. +Limit the search to text files, per `denote-directory-files' with +non-nil `text-only' parameter." + (mapcar #'xref-match-item-location + (xref-matches-in-files identifier + (denote-directory-files nil nil :text-only)))) + +(defun denote--retrieve-group-in-xrefs (identifier) + "Access location of xrefs for IDENTIFIER and group them per file. +See `denote--retrieve-locations-in-xrefs'." + (mapcar #'xref-location-group + (denote--retrieve-location-in-xrefs identifier))) + +(defun denote--retrieve-files-in-xrefs (identifier) + "Return sorted, deduplicated file names with IDENTIFIER in their contents." + (sort + (delete-dups + (denote--retrieve-group-in-xrefs identifier)) + #'string-collate-lessp)) + +;;;; 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 cannot be +nil or an empty string and must match `denote-id-regexp'. + +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")) + ((null id) + (error "ID must not be nil")) + ((string-empty-p id) + (error "ID must not be an empty string")) + ((not (string-match-p denote-id-regexp id)) + (error "ID `%s' does not match `denote-id-regexp'" id))) + (let ((file-name (concat dir-path id))) + (when (and signature (not (string-empty-p signature))) + (setq file-name (concat file-name "==" (denote-sluggify 'signature signature)))) + (when (and title (not (string-empty-p title))) + (setq file-name (concat file-name "--" (denote-sluggify 'title title)))) + (when keywords + (setq file-name (concat file-name "__" (denote-keywords-combine (denote-sluggify-keywords keywords))))) + (concat file-name extension))) + +(defun denote--format-front-matter-title (title file-type) + "Format TITLE according to FILE-TYPE for the file's front matter." + (funcall (denote--title-value-function file-type) title)) + +(defun denote--format-front-matter-keywords (keywords file-type) + "Format KEYWORDS according to FILE-TYPE for the file's front matter. +Apply `denote-sluggify' to KEYWORDS." + (let ((kws (denote-sluggify-keywords keywords))) + (funcall (denote--keywords-value-function file-type) kws))) + +(defun denote--path (title keywords dir id file-type signature) + "Return path to new file. +Use ID, TITLE, KEYWORDS, FILE-TYPE and SIGNATURE to construct +path to DIR." + (denote-format-file-name + dir id keywords title (denote--file-extension file-type) signature)) + +;; 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." + (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." + (format-time-string "[%F %a %R]" date)) + +(defun denote-date-iso-8601 (date) + "Format DATE according to ISO 8601 standard." + (format-time-string "%F" date)) + +(defun denote--date (date file-type) + "Expand DATE in an appropriate format for FILE-TYPE." + (let ((format denote-date-format)) + (cond + (format + (format-time-string format date)) + ((when-let ((fn (denote--date-format-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. + +Arguments TITLE, KEYWORDS, DATE, ID, DIRECTORY, FILE-TYPE, +TEMPLATE, and SIGNATURE should be valid for note creation." + (let* ((path (denote--path title keywords directory id file-type signature)) + (buffer (find-file path)) + (header (denote--format-front-matter + title (denote--date date file-type) keywords + id + file-type))) + (with-current-buffer buffer + (insert header) + (insert template)))) + +(defun denote--dir-in-denote-directory-p (directory) + "Return non-nil if DIRECTORY is in variable `denote-directory'." + (and 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))))) + (if (memq type (denote--file-type-keys)) + type + (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--valid-date + 'denote-valid-date-p + "2.3.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, return nil." + (if (and (or (numberp date) (listp date)) + (decode-time date)) + date + (date-to-time (denote--date-add-current-time date)))) + +(defun denote-parse-date (date) + "Return DATE as an appropriate value for the `denote' command. +Pass DATE through `denote-valid-date-p' and use its return value. +If either that or DATE is nil, return `current-time'." + (or (denote-valid-date-p date) (current-time))) + +(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--id-exists-p (identifier) + "Return non-nil if IDENTIFIER already exists." + (seq-some + (lambda (file) + (string-prefix-p identifier (file-name-nondirectory file))) + (append (denote-directory-files) (denote--buffer-file-names)))) + +(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) + (let ((id (denote-retrieve-filename-identifier name))) + (puthash id t ids))) + ids)) + +(defun denote--find-first-unused-id (id used-ids) + "Return the first unused id starting at ID from USED-IDS. +USED-IDS is a hash-table of all used IDs. If ID is already used, +increment it 1 second at a time until an available id is found." + (let ((current-id id)) + (while (gethash current-id used-ids) + (setq current-id (denote-get-identifier (time-add (date-to-time current-id) 1)))) + current-id)) + +(make-obsolete 'denote-barf-duplicate-id nil "2.1.0") + +(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-buffer-after-creation' +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-buffer-after-creation + (or force-save denote-save-buffer-after-creation)) + (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 + (when denote-file-history + (file-name-nondirectory (pop denote-file-history))) + denote-title-prompt-current-default)) + (path)) + (if in-background + (save-window-excursion + (call-interactively command) + (setq path (buffer-file-name))) + (call-interactively command) + (setq path (buffer-file-name))) + path)) + +;;;###autoload +(defun denote (&optional title keywords file-type subdirectory 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. + +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 `denote-file-type'. + +- SUBDIRECTORY 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 SUBDIRECTORY 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 + (let ((args (make-vector 7 nil))) + (dolist (prompt denote-prompts) + (pcase prompt + ('title (aset args 0 (denote-title-prompt + (when (and (not denote-ignore-region-in-denote-command) + (use-region-p)) + (buffer-substring-no-properties + (region-beginning) + (region-end)))))) + ('keywords (aset args 1 (denote-keywords-prompt))) + ('file-type (aset args 2 (denote-file-type-prompt))) + ('subdirectory (aset args 3 (denote-subdirectory-prompt))) + ('date (aset args 4 (denote-date-prompt))) + ('template (aset args 5 (denote-template-prompt))) + ('signature (aset args 6 (denote-signature-prompt))))) + (append args nil))) + (let* ((title (or title "")) + (file-type (denote--valid-file-type (or file-type denote-file-type))) + (kws (denote-keywords-sort keywords)) + (date (denote-parse-date date)) + (id (denote--find-first-unused-id + (denote-get-identifier date) + (denote--get-all-used-ids))) + (directory (if (denote--dir-in-denote-directory-p subdirectory) + (file-name-as-directory subdirectory) + (denote-directory))) + (template (if (stringp template) + template + (or (alist-get template denote-templates) ""))) + (signature (or signature ""))) + (denote--prepare-note title kws date id directory file-type template signature) + (when denote-save-buffer-after-creation (save-buffer)) + (denote--keywords-add-to-history keywords) + (run-hooks 'denote-after-new-note-hook))) + +(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 + initial-title + 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 `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-prompt () + "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." + (if (and denote-date-prompt-use-org-read-date + (require 'org nil :no-error)) + (let* ((time (org-read-date nil t)) + (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 + "DATE and TIME for note (e.g. 2022-06-16 14:30): " + nil 'denote-date-history))) + +(defun denote-prompt-for-date-return-id () + "Use `denote-date-prompt' and return it as `denote-id-format'." + (denote-get-identifier (denote-date-prompt))) + +(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 (if def + (format "Select SUBDIRECTORY [%s]: " def) + "Select SUBDIRECTORY: "))) + (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.") + +(defun denote--add-prompts (additional-prompts) + "Add all the elements in the ADDITIONAL-PROMPTS list to `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." + (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)) + (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. + +If TARGET file does not exist, add the user input that was used +to search for it to the minibuffer 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 +\\\\[previous-history-element]." + (interactive (list (denote-file-prompt))) + (if (and target (file-exists-p target)) + (find-file target) + (denote--command-with-features #'denote :use-file-prompt-as-def-title nil nil nil))) + +;;;###autoload +(defun denote-open-or-create-with-command () + "Visit TARGET file in variable `denote-directory'. +If file does not exist, invoke `denote' to create a file. + +If TARGET file does not exist, add the user input that was used +to search for it to the minibuffer 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 +\\\\[previous-history-element]." + (declare (interactive-only t)) + (interactive) + (let ((target (denote-file-prompt))) + (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-capture-p () + "Return non-nil if this is an `org-capture' buffer." + (and (bound-and-true-p org-capture-mode) + (derived-mode-p 'org-mode) + (string-match-p "\\`CAPTURE.*\\.org" (buffer-name)))) + +(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 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. + +If no file type in `denote-file-types' has the file extension, +the file type is assumed to be the first one in `denote-file-types'." + (cond + ((denote--file-type-org-capture-p) 'org) + (file + (let* ((extension (denote-get-file-extension-sans-encryption file)) + (types (denote--file-types-with-extension extension))) + (cond ((null types) + (caar denote-file-types)) + ((= (length types) 1) + (caar types)) + (t + (or (car (seq-find + (lambda (type) + (denote--regexp-in-file-p (plist-get (cdr type) :title-key-regexp) file)) + types)) + (caar types)))))))) + +(defun denote--file-attributes-time (file) + "Return `file-attribute-modification-time' of FILE as identifier." + (denote-get-identifier (file-attribute-modification-time (file-attributes 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 (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." + (unless (string= (expand-file-name old-name) (expand-file-name 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-buffer-after-creation (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))))) + +(defun denote--add-front-matter (file title keywords id file-type &optional save-buffer) + "Prepend front matter to FILE if `denote-file-is-note-p'. +The TITLE, KEYWORDS ID, and FILE-TYPE are passed from the +renaming command and are used to construct a new front matter +block if appropriate. + +With optional SAVE-BUFFER, save the buffer corresponding to FILE." + (when-let ((date (denote--date (date-to-time id) file-type)) + (new-front-matter (denote--format-front-matter title date keywords id file-type))) + (with-current-buffer (find-file-noselect file) + (goto-char (point-min)) + (insert new-front-matter) + (when save-buffer (save-buffer))))) + +(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--edit-front-matter-p (file file-type) + "Test if FILE should be subject to front matter rewrite. +Use FILE-TYPE to look for the front matter lines. This is +relevant for operations that insert or rewrite the front matter +in a Denote note. + +For the purposes of this test, FILE is a Denote note when it +contains a title line, a keywords line or both." + (and (denote--front-matter file-type) + (or (denote--regexp-in-file-p (denote--title-key-regexp file-type) file) + (denote--regexp-in-file-p (denote--keywords-key-regexp file-type) file)))) + +(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." + (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 (denote--get-keywords-line-from-front-matter keywords file-type)) + (delete-region (point) (line-end-position)) + (when save-buffer (save-buffer))))))) + +(define-obsolete-function-alias + 'denote--rewrite-keywords + 'denote-rewrite-keywords + "2.0.0") + +(defun denote-rewrite-front-matter (file title keywords file-type &optional no-confirm) + "Rewrite front matter of note after `denote-rename-file'. +The FILE, TITLE, KEYWORDS, and FILE-TYPE are given by the +renaming command and are used to construct new front matter +values if appropriate. + +With optional NO-CONFIRM, do not prompt to confirm the rewriting +of the front matter. Otherwise produce a `y-or-n-p' prompt to +that effect. + +With optional NO-CONFIRM, save the buffer after performing the +rewrite. Otherwise leave it unsaved for furthter review by the +user." + (when-let ((old-title-line (denote-retrieve-front-matter-title-line file file-type)) + (old-keywords-line (denote-retrieve-front-matter-keywords-line file file-type)) + (new-title-line (denote--get-title-line-from-front-matter title file-type)) + (new-keywords-line (denote--get-keywords-line-from-front-matter keywords file-type))) + (with-current-buffer (find-file-noselect file) + (when (or no-confirm + (y-or-n-p (format + "Replace front matter?\n-%s\n+%s\n\n-%s\n+%s?" + (propertize old-title-line 'face 'denote-faces-prompt-old-name) + (propertize new-title-line 'face 'denote-faces-prompt-new-name) + (propertize old-keywords-line 'face 'denote-faces-prompt-old-name) + (propertize new-keywords-line 'face 'denote-faces-prompt-new-name)))) + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (re-search-forward (denote--title-key-regexp file-type) nil t 1) + (goto-char (line-beginning-position)) + (insert new-title-line) + (delete-region (point) (line-end-position)) + (goto-char (point-min)) + (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 no-confirm (save-buffer)))))))) + +(define-obsolete-function-alias + 'denote--rewrite-front-matter + 'denote-rewrite-front-matter + "2.0.0") + +;;;;; The renaming commands and their prompts + +(defun denote--rename-dired-file-or-prompt () + "Return Dired file at point, else prompt for one. +Throw error if FILE is not regular, else return FILE." + (or (dired-get-filename nil t) + (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." + (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))))) + +;; 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.") + +;;;###autoload +(defun denote-rename-file (file &optional 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 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. + +If a file name component is present, but there is no entry for it in +`denote-prompts', keep it 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, read its file type extension (like .org) and +preserve 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-no-confirm' is set to a non-nil +value. + +If FILE has front matter for TITLE and KEYWORDS, ask to rewrite +their values in order to reflect the new input, unless +`denote-rename-no-confirm' is non-nil. When the +`denote-rename-no-confirm' is nil (the default), do not save the +underlying buffer, thus giving the user the option to +double-check the result, such as by invokling 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. Save +the buffer if `denote-rename-no-confirm' is non-nil. + +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' + +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-prompt)) + (file-type (denote-filetype-heuristics file)) + (file-in-prompt (propertize (file-relative-name file) 'face 'denote-faces-prompt-current-name)) + (date nil) + (title (denote-retrieve-title-or-filename file file-type)) + (keywords (denote-convert-file-name-keywords-to-crm (or (denote-retrieve-filename-keywords 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) + keywords))) + ('signature + (setq signature (denote-signature-prompt + signature + (format "Rename `%s' with SIGNATURE (empty to remove)" file-in-prompt)))) + ('date + (unless (denote-file-has-identifier-p file) + (setq date (denote-date-prompt)))))) + (list file title keywords signature date))) + (setq keywords (denote-keywords-sort + (if (stringp keywords) + (split-string keywords "," :omit-nulls) + keywords))) + (let* ((dir (file-name-directory file)) + (id (or (denote-retrieve-filename-identifier file) + (denote-create-unique-file-identifier file (denote--get-all-used-ids) date))) + ;; TODO 2024-02-13: Should we derive the extension from the + ;; `denote-file-type-prompt' if we are conforming with the + ;; `denote-prompts'? + (extension (denote-get-file-extension file)) + (file-type (denote-filetype-heuristics file)) + (new-name (denote-format-file-name dir id keywords title extension signature)) + (max-mini-window-height denote-rename-max-mini-window-height)) + (when (or denote-rename-no-confirm (denote-rename-file-prompt file new-name)) + (denote-rename-file-and-buffer file new-name) + (denote-update-dired-buffers) + (when (denote-file-is-writable-and-supported-p new-name) + (if (denote--edit-front-matter-p new-name file-type) + (denote-rewrite-front-matter new-name title keywords file-type denote-rename-no-confirm) + (denote--add-front-matter new-name title keywords id file-type denote-rename-no-confirm))) + (run-hooks 'denote-after-rename-file-hook)) + new-name)) + +;;;###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-no-confirm' to a non-nil value)." + (declare (interactive-only t)) + (interactive nil dired-mode) + (if-let ((marks (dired-get-marked-files))) + (let ((used-ids (unless (seq-every-p #'denote-file-has-identifier-p marks) + (denote--get-all-used-ids)))) + (dolist (file marks) + (let* ((file-type (denote-filetype-heuristics file)) + (file-in-prompt (propertize (file-relative-name file) 'face 'denote-faces-prompt-current-name)) + (dir (file-name-directory file)) + (id (or (denote-retrieve-filename-identifier file) + (denote-create-unique-file-identifier file used-ids))) + (title (denote-retrieve-title-or-filename file file-type)) + (keywords (denote-convert-file-name-keywords-to-crm (or (denote-retrieve-filename-keywords file) ""))) + (signature (or (denote-retrieve-filename-signature file) "")) + (extension (denote-get-file-extension 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) + keywords))) + ('signature + (setq signature (denote-signature-prompt + signature + (format "Rename `%s' with SIGNATURE (empty to remove)" file-in-prompt)))) + ('date + (setq id (denote-prompt-for-date-return-id))))) + (setq keywords (denote-keywords-sort + (if (stringp keywords) + (split-string keywords "," :omit-nulls) + keywords))) + (let ((new-name (denote-format-file-name dir id keywords title extension signature))) + (denote-rename-file-and-buffer file new-name) + (when (denote-file-is-writable-and-supported-p new-name) + (if (denote--edit-front-matter-p new-name file-type) + (denote-rewrite-front-matter new-name title keywords file-type :no-confirm) + (denote--add-front-matter new-name title keywords id file-type :save-buffer))) + (run-hooks 'denote-after-rename-file-hook) + (when used-ids + (puthash id t used-ids))))) + (denote-update-dired-buffers)) + (user-error "No marked files; aborting"))) + +(make-obsolete + 'denote-dired-rename-marked-files + 'denote-dired-rename-marked-files-with-keywords + "2.1.0") + +(defalias 'denote-dired-rename-marked-files 'denote-dired-rename-files + "Alias for `denote-dired-rename-files'.") + +;;;###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 `denote-file-type'), + such that it includes the new keywords. + +Run the `denote-after-rename-file-hook' after renaming is done. + +[ 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). ]" + (declare (interactive-only t)) + (interactive nil dired-mode) + (if-let ((marks (dired-get-marked-files))) + (let ((keywords (denote-keywords-sort + (denote-keywords-prompt "Rename marked files with KEYWORDS, overwriting existing (empty to ignore/remove)"))) + (used-ids (unless (seq-every-p #'denote-file-has-identifier-p marks) + (denote--get-all-used-ids)))) + (dolist (file marks) + (let* ((dir (file-name-directory file)) + (id (or (denote-retrieve-filename-identifier file) + (denote-create-unique-file-identifier file used-ids))) + (signature (or (denote-retrieve-filename-signature file) "")) + (file-type (denote-filetype-heuristics file)) + (title (denote-retrieve-title-or-filename file file-type)) + (extension (denote-get-file-extension file)) + (new-name (denote-format-file-name dir id keywords title extension signature))) + (denote-rename-file-and-buffer file new-name) + (when (denote-file-is-writable-and-supported-p new-name) + (if (denote--edit-front-matter-p new-name file-type) + (denote-rewrite-keywords new-name keywords file-type denote-rename-no-confirm) + (denote--add-front-matter new-name title keywords id file-type denote-rename-no-confirm))) + (run-hooks 'denote-after-rename-file-hook) + (when used-ids + (puthash id t used-ids)))) + (denote-update-dired-buffers)) + (user-error "No marked files; aborting"))) + +;;;###autoload +(defun denote-rename-file-using-front-matter (file &optional no-confirm save-buffer) + "Rename FILE using its front matter as input. +When called interactively, FILE is the return value of the +function `buffer-file-name' 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 `denote-file-type'. + +Unless NO-CONFIRM is non-nil (such as with a prefix argument), +ask for confirmation, showing the difference between the old and +the new file names. + +Never modify the identifier of the FILE, if any, 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. + +If NO-CONFIRM is non-nil (such as with a prefix argument) do not +prompt for confirmation while renaming the file. Do it outright. + +If optional SAVE-BUFFER is non-nil (such as with a double prefix +argument), save the corresponding buffer. + +If the user option `denote-rename-no-confirm' is non-nil, +interpret it the same way as a combination of NO-CONFIRM and +SAVE-BUFFER. + +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." + (interactive + (let (no-confirm save-buffer) + (cond + ((and current-prefix-arg (> (prefix-numeric-value current-prefix-arg) 4)) + (setq no-confirm t + save-buffer t)) + (current-prefix-arg + (setq no-confirm t))) + (list buffer-file-name no-confirm save-buffer))) + (unless (denote-file-is-writable-and-supported-p file) + (user-error "The file is not writable or does not have a supported file extension")) + (if-let ((file-type (denote-filetype-heuristics file)) + (title (denote-retrieve-front-matter-title-value file file-type)) + (id (denote-retrieve-filename-identifier file))) + (let* ((keywords (denote-retrieve-front-matter-keywords-value file file-type)) + (signature (or (denote-retrieve-filename-signature file) "")) + (extension (denote-get-file-extension file)) + (dir (file-name-directory file)) + (new-name (denote-format-file-name dir id keywords title extension signature))) + (when (or denote-rename-no-confirm + no-confirm + (denote-rename-file-prompt file new-name)) + (denote-rename-file-and-buffer file new-name) + (denote-update-dired-buffers) + (when (or denote-rename-no-confirm save-buffer) + (save-buffer)) + (run-hooks 'denote-after-rename-file-hook))) + (user-error "No identifier or front matter for title"))) + +;;;###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 `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 (denote-file-is-writable-and-supported-p m) + (denote-file-has-identifier-p m))) + (dired-get-marked-files)))) + (progn + (dolist (file marks) + (denote-rename-file-using-front-matter file :no-confirm denote-rename-no-confirm)) + (denote-update-dired-buffers)) + (user-error "No marked Denote files; aborting"))) + +;;;;;; Interactively modify keywords and rename accordingly + +;;;###autoload +(defun denote-keywords-add (keywords &optional save-buffer) + "Prompt for KEYWORDS to add to the current note's front matter. +When called from Lisp, KEYWORDS is a list of strings. + +Rename the file without further prompt so that its name reflects +the new front matter, per `denote-rename-file-using-front-matter'. + +With an optional SAVE-BUFFER (such as a prefix argument when +called interactively), save the buffer outright. Otherwise leave +the buffer unsaved for further review. + +If the user option `denote-rename-no-confirm' is non-nil, +interpret it the same way as SAVE-BUFFER, making SAVE-BUFFER +reduntant. + +Run `denote-after-rename-file-hook' as a final step." + (interactive (list (denote-keywords-prompt "Add KEYWORDS") current-prefix-arg)) + ;; A combination of if-let and let, as we need to take into account + ;; the scenario in which there are no keywords yet. + (if-let ((file (buffer-file-name)) + ((denote-file-is-note-p file)) + (file-type (denote-filetype-heuristics file))) + (let* ((cur-keywords (denote-retrieve-front-matter-keywords-value file file-type)) + (new-keywords (denote-keywords-sort + (seq-uniq (append keywords cur-keywords))))) + (denote-rewrite-keywords file new-keywords file-type) + (denote-rename-file-using-front-matter file :no-confirm (or denote-rename-no-confirm save-buffer)) + (run-hooks 'denote-after-rename-file-hook)) + (user-error "Buffer not visiting a Denote file"))) + +(defalias 'denote-rename-add-keywords 'denote-keywords-add + "Alias for `denote-keywords-add'.") + +(defun denote--keywords-delete-prompt (keywords) + "Prompt for one or more KEYWORDS. +In the case of multiple entries, those are separated by the +`crm-separator', which typically is a comma. In such a case, the +output is sorted with `string-collate-lessp'." + (let ((choice (denote--keywords-crm keywords "Keywords to remove"))) + (if denote-sort-keywords + (sort choice #'string-collate-lessp) + choice))) + +;;;###autoload +(defun denote-keywords-remove (&optional save-buffer) + "Prompt for keywords in current note and remove them. +Keywords are retrieved from the file's front matter. + +Rename the file without further prompt so that its name reflects +the new front matter, per `denote-rename-file-using-front-matter'. + +With an optional SAVE-BUFFER as a prefix argument, save the +buffer outright. Otherwise leave the buffer unsaved for further +review. + +If the user option `denote-rename-no-confirm' is non-nil, +interpret it the same way as SAVE-BUFFER, making SAVE-BUFFER +reduntant. + +Run `denote-after-rename-file-hook' as a final step." + (declare (interactive-only t)) + (interactive "P") + (if-let ((file (buffer-file-name)) + ((denote-file-is-note-p file)) + (file-type (denote-filetype-heuristics file))) + (when-let ((cur-keywords (denote-retrieve-front-matter-keywords-value file file-type)) + (del-keyword (denote--keywords-delete-prompt cur-keywords))) + (denote-rewrite-keywords + file + (seq-difference cur-keywords del-keyword) + file-type) + (denote-rename-file-using-front-matter file :no-confirm (or denote-rename-no-confirm save-buffer)) + (run-hooks 'denote-after-rename-file-hook)) + (user-error "Buffer not visiting a Denote file"))) + +(defalias 'denote-rename-remove-keywords 'denote-keywords-remove + "Alias for `denote-keywords-remove'.") + +;;;;;; Interactively add or remove file name signature + +;;;###autoload +(defun denote-rename-add-signature (file signature) + "Add to FILE name the SIGNATURE. +In interactive use, prompt for FILE, defaulting either to the current +buffer's file or the one at point in a Dired buffer. Also prompt for +SIGNATURE, using the existing one, if any, as the initial value. + +When called from Lisp, FILE is a string pointing to a file system path +and SIGNATURE is a string. + +Ask for confirmation before renaming the file to include the new +signature. Do it unless the user option `denote-rename-no-confirm' is +set to a non-nil value. + +Once the operation is done, reload any Dired buffers and run the +`denote-after-rename-file-hook'. + +Also see `denote-rename-remove-signature'." + (interactive + (let* ((file (denote--rename-dired-file-or-prompt)) + (file-in-prompt (propertize (file-relative-name file) 'face 'denote-faces-prompt-current-name))) + (list + file + (denote-signature-prompt + (or (denote-retrieve-filename-signature file) "") + (format "Rename `%s' with SIGNATURE (empty to remove)" file-in-prompt))))) + (let* ((type (denote-filetype-heuristics file)) + (title (denote-retrieve-title-or-filename file type)) + (keywords-string (denote-retrieve-filename-keywords file)) + (keywords (when keywords-string (split-string keywords-string "_" :omit-nulls "_"))) + (dir (file-name-directory file)) + (id (or (denote-retrieve-filename-identifier file) + (denote-create-unique-file-identifier file (denote--get-all-used-ids)))) + (extension (denote-get-file-extension file)) + (new-name (denote-format-file-name dir id keywords title extension signature))) + (when (or denote-rename-no-confirm (denote-rename-file-prompt file new-name)) + (denote-rename-file-and-buffer file new-name) + (denote-update-dired-buffers) + (run-hooks 'denote-after-rename-file-hook)))) + +;;;###autoload +(defun denote-rename-remove-signature (file) + "Remove the signature of FILE. +In interactive use, prompt for FILE, defaulting either to the current +buffer's file or the one at point in a Dired buffer. When called from +Lisp, FILE is a string pointing to a file system path. + +Ask for confirmation before renaming the file to remove its signature. +Do it unless the user option `denote-rename-no-confirm' is set to a +non-nil value. + +Once the operation is done, reload any Dired buffers and run the +`denote-after-rename-file-hook'. + +Also see `denote-rename-add-signature'." + (interactive (list (denote--rename-dired-file-or-prompt))) + (when (denote-retrieve-filename-signature file) + (let* ((type (denote-filetype-heuristics file)) + (title (denote-retrieve-title-or-filename file type)) + (keywords-string (denote-retrieve-filename-keywords file)) + (keywords (when keywords-string (split-string keywords-string "_" :omit-nulls "_"))) + (dir (file-name-directory file)) + (id (or (denote-retrieve-filename-identifier file) + (denote-create-unique-file-identifier file (denote--get-all-used-ids)))) + (extension (denote-get-file-extension file)) + (new-name (denote-format-file-name dir id keywords title extension nil))) + (when (or denote-rename-no-confirm (denote-rename-file-prompt file new-name)) + (denote-rename-file-and-buffer file new-name) + (denote-update-dired-buffers) + (run-hooks 'denote-after-rename-file-hook))))) + +;;;;; Creation of front matter + +;;;###autoload +(defun denote-add-front-matter (file title keywords) + "Insert front matter at the top of FILE. + +When called interactively, FILE is the return value of the +function `buffer-file-name'. FILE is checked to determine +whether it is a note for Denote's purposes. + +TITLE is a string. Interactively, it is the user input at the +minibuffer prompt. + +KEYWORDS is a list of strings. Interactively, it is the user +input at the minibuffer prompt. This one supports completion for +multiple entries, each separated by the `crm-separator' (normally +a comma). + +The purpose of this command is to help the user generate new +front matter for an existing note (perhaps because the user +deleted the previous one and could not undo the change). + +This command does not rename the file (e.g. to update the +keywords). To rename a file by reading its front matter as +input, use `denote-rename-file-using-front-matter'. + +Note that this command is useful only for existing Denote notes. +If the user needs to convert a generic text file to a Denote +note, they can use one of the command which first rename the file +to make it comply with our file-naming scheme and then add the +relevant front matter. + +[ 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-29 09:24 +0200. ]" + (interactive + (list + (buffer-file-name) + (denote-title-prompt nil "Add TITLE (empty to ignore)") + (denote-keywords-sort (denote-keywords-prompt "Add KEYWORDS (empty to ignore)")))) + (when-let ((denote-file-is-writable-and-supported-p file) + (id (denote-retrieve-filename-identifier file)) + (file-type (denote-filetype-heuristics file))) + (denote--add-front-matter file title keywords id file-type))) + +(define-obsolete-function-alias + 'denote-change-file-type + 'denote-change-file-type-and-front-matter + "2.1.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 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." + (interactive + (list + (denote--rename-dired-file-or-prompt) + (denote--valid-file-type (or (denote-file-type-prompt) denote-file-type)))) + (let* ((dir (file-name-directory file)) + (old-file-type (denote-filetype-heuristics file)) + (id (or (denote-retrieve-filename-identifier file) "")) + (title (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) "")) + (old-extension (denote-get-file-extension 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 (and (not (eq old-extension new-extension)) + (denote-rename-file-prompt file new-name)) + (denote-rename-file-and-buffer file new-name) + (denote-update-dired-buffers) + (when (denote-file-is-writable-and-supported-p new-name) + (denote--add-front-matter new-name title keywords id new-file-type))))) + +;;;; 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-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")) + +(defvar denote-faces--file-name-regexp + (concat "\\(?11:[\t\s]+\\|.*/\\)?" + "\\(?1:[0-9]\\{4\\}\\)\\(?12:[0-9]\\{2\\}\\)\\(?13:[0-9]\\{2\\}\\)" + "\\(?10:T\\)" + "\\(?2:[0-9]\\{2\\}\\)\\(?14:[0-9]\\{2\\}\\)\\(?15:[0-9]\\{2\\}\\)" + "\\(?:\\(?3:==\\)\\(?4:[^.]*?\\)\\)?" + "\\(?:\\(?5:--\\)\\(?6:[^.]*?\\)\\)?" + "\\(?:\\(?7:__\\)\\(?8:[^.]*?\\)\\)?" + "\\(?9:\\..*\\)?$") + "Regexp of file names for fontification.") + +(defconst denote-faces-file-name-keywords + `((,denote-faces--file-name-regexp + (11 'denote-faces-subdirectory nil t) + (1 'denote-faces-year nil t) + (12 'denote-faces-month nil t) + (13 'denote-faces-day nil t) + (10 'denote-faces-time-delimiter nil t) + (2 'denote-faces-hour nil t) + (14 'denote-faces-minute nil t) + (15 'denote-faces-second nil t) + (3 'denote-faces-delimiter nil t) + (4 'denote-faces-signature nil t) + (5 'denote-faces-delimiter nil t) + (6 'denote-faces-title nil t) + (7 'denote-faces-delimiter nil t) + (8 'denote-faces-keywords nil t) + (9 'denote-faces-extension nil t ))) + "Keywords for fontification of file names.") + +(make-obsolete-variable 'denote-faces-file-name-keywords-for-backlinks nil "2.2.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 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))) + +(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 + +(defgroup denote-link () + "Link facility for Denote." + :group 'denote) + +;;;;; User options + +(defcustom denote-link-backlinks-display-buffer-action + '((display-buffer-reuse-window display-buffer-below-selected) + (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-link-backlinks-display-buffer-action + (quote ((display-buffer-reuse-window + display-buffer-in-side-window) + (side . left) + (slot . 99) + (window-width . 0.3)))) + +See Info node `(elisp) Displaying Buffers' for more details +and/or the documentation string of `display-buffer'." + :type '(cons (choice (function :tag "Display Function") + (repeat :tag "Display Functions" function)) + alist) + :package-version '(denote . "0.1.0") + :group 'denote-link) + +;;;;; 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:" denote-id-regexp "\\)" "]" "\\[.*?]]") + "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 "\\[.*?]" "(denote:" "\\(?1:" denote-id-regexp "\\)" ")") + "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:" denote-id-regexp "\\)" "]]") + "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) + "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'." + (format + (if (or id-only (null description) (string-empty-p description)) + denote-id-only-link-format + (denote--link-format file-type)) + (denote-retrieve-filename-identifier file) + description)) + +(make-obsolete 'denote-link--format-link 'denote-format-link "2.1.0") +(make-obsolete 'denote-link-signature-format nil "2.3.0") + +(defun denote--link-get-description (file) + "Return link description for FILE." + (funcall + (or denote-link-description-function #'denote-link-description-with-signature-and-title) + file)) + +(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 as 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)) + (t title)))) + +(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))) + +;;;###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. + +The DESCRIPTION is returned by the function specified in variable +`denote-link-description-function'. If the region is active, its +content is deleted and can be used as the description of the +link. The default value of `denote-link-description-function' +returns the content of the active region, if any, else the title +of the linked file is used as the description. The title comes +either from the front matter or the file name. Note that if you +change the default value of `denote-link-description-function', +make sure to use the `region-text' parameter. Regardless of the +value of this user option, `denote-link' will always replace the +content of the active region. + +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, the link is also as if ID-ONLY were non-nil. The +default value of `denote-link-description-function' returns an +empty string when the region is empty. Thus, the link will have +no description in this case. + +When called from Lisp, FILE is a string representing a full file +system path. FILE-TYPE is a symbol as described in +`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--link-get-description file)))) + (list file file-type description current-prefix-arg))) + (unless (or (denote--file-type-org-capture-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")) + (let* ((beg (point))) + (denote--delete-active-region-content) + (insert (denote-format-link file description file-type id-only)) + (unless (derived-mode-p 'org-mode) + (make-button beg (point) 'type 'denote-link-button)))) + +(define-obsolete-function-alias + 'denote-link-insert-link + 'denote-insert-link + "2.0.0") + +(defalias 'denote-insert-link 'denote-link + "Alias for `denote-link' command.") + +;;;###autoload +(defun denote-link-with-signature () + "Insert link to file with signature. +Prompt for file using minibuffer completion, limiting the list of +candidates to files with a signature in their file name. + +By default, the description of the link includes the signature, +if present, followed by the file's title, if any. + +For more advanced uses with Lisp, refer to the `denote-link' +function." + (declare (interactive-only t)) + (interactive) + (unless (or (denote--file-type-org-capture-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 (denote-file-prompt "=")) + (type (denote-filetype-heuristics (buffer-file-name))) + (description (denote--link-get-description file))) + (denote-link file type description))) + +(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-prefix-p i (file-name-nondirectory 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-link--find-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))) + +(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))) + (with-temp-buffer + (insert-file-contents current-file) + (denote-link--expand-identifiers regexp)))) + +(defalias 'denote-link-return-forelinks 'denote-link-return-links + "Alias for `denote-link-return-links'.") + +(define-obsolete-function-alias + 'denote-link-find-file + 'denote-find-link + "2.0.0") + +;;;###autoload +(defun denote-find-link () + "Use minibuffer completion to visit linked file." + (declare (interactive-only t)) + (interactive) + (find-file + (concat + (denote-directory) + (denote-link--find-file-prompt + (or (denote-link-return-links) + (user-error "No links found")))))) + +(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-in-xrefs id)))) + +(define-obsolete-function-alias + 'denote-link-find-backlink + 'denote-find-backlink + "2.0.0") + +;;;###autoload +(defun denote-find-backlink () + "Use minibuffer completion to visit backlink to current file. + +Like `denote-find-link', but select backlink to follow." + (declare (interactive-only t)) + (interactive) + (find-file + (denote-get-path-by-id + (denote-extract-id-from-string + (denote-link--find-file-prompt + (or (denote-link-return-backlinks) + (user-error "No backlinks 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-capture-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--link-get-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-capture-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--link-get-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. + +If TARGET file does not exist, add the user input that was used +to search for it to the minibuffer 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 +\\\\[previous-history-element]. + +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))) + (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-capture-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--link-get-description target) + id-only)) + +(defalias 'denote-link-to-existing-or-new-note 'denote-link-or-create + "Alias for `denote-link-or-create' command.") + +;;;;; Link buttons + +;; Evaluate: (info "(elisp) Button Properties") +;; +;; Button can provide a help-echo function as well, but I think we might +;; not need it. +(define-button-type 'denote-link-button + 'follow-link t + 'face 'denote-faces-link + 'action #'denote-link--find-file-at-button) + +(autoload 'thing-at-point-looking-at "thingatpt") + +(defun denote-link--link-at-point-string () + "Return identifier at point." + (when (or (thing-at-point-looking-at denote-id-only-link-in-context-regexp) + (thing-at-point-looking-at denote-md-link-in-context-regexp) + (thing-at-point-looking-at denote-org-link-in-context-regexp) + ;; Meant to handle the case where a link is broken by + ;; `fill-paragraph' into two lines, in which case it + ;; buttonizes only the "denote:ID" part. Example: + ;; + ;; [[denote:20220619T175212][This is a + ;; test]] + ;; + ;; Maybe there is a better way? + (thing-at-point-looking-at "\\[\\(denote:.*\\)]")) + (match-string-no-properties 0))) + +;; NOTE 2022-06-15: I add this as a variable for advanced users who may +;; prefer something else. If there is demand for it, we can make it a +;; defcustom, but I think it would be premature at this stage. +(defvar denote-link-button-action #'find-file-other-window + "Display buffer action for Denote buttons.") + +(defun denote-link--find-file-at-button (button) + "Visit file referenced by BUTTON." + (let* ((id (denote-extract-id-from-string + (buffer-substring-no-properties + (button-start button) + (button-end button)))) + (file (denote-get-path-by-id id))) + (funcall denote-link-button-action file))) + +;;;###autoload +(defun denote-link-buttonize-buffer (&optional beg end) + "Make denote: links actionable buttons in the current buffer. + +Buttonization applies to the plain text and Markdown file types, +per the user option `denote-file-types'. It will not do anything +in `org-mode' buffers, as buttons already work there. If you do +not use Markdown or plain text, then you do not need this. + +Links work when they point to a file inside the variable +`denote-directory'. + +To buttonize links automatically add this function to the +`find-file-hook'. Or call it interactively for on-demand +buttonization. + +When called from Lisp, with optional BEG and END as buffer +positions, limit the process to the region in-between." + (interactive) + (when (and (not (derived-mode-p 'org-mode)) + buffer-file-name + (denote-file-has-identifier-p buffer-file-name)) + (save-excursion + (goto-char (or beg (point-min))) + (while (re-search-forward denote-id-regexp end t) + (when-let ((string (denote-link--link-at-point-string)) + (beg (match-beginning 0)) + (end (match-end 0))) + (make-button beg end 'type 'denote-link-button)))))) + +(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-link-button-action + (denote-get-path-by-id (match-string 0 link))))) + +(eval-after-load 'markdown-mode + '(add-hook 'markdown-follow-link-functions #'denote-link-markdown-follow)) + +;;;;; Backlinks' buffer + +(define-button-type 'denote-link-backlink-button + 'follow-link t + 'action #'denote-link--backlink-find-file + 'face nil) ; we use this face though we style it later + +(defun denote-link--backlink-find-file (button) + "Action for BUTTON to `find-file'." + (funcall denote-link-button-action (buffer-substring (button-start button) (button-end button)))) + +(defun denote-link--display-buffer (buf) + "Run `display-buffer' on BUF. +Expand `denote-link-backlinks-display-buffer-action'." + (display-buffer + buf + `(,@denote-link-backlinks-display-buffer-action))) + +(define-obsolete-function-alias + 'denote-backlinks-next + 'denote-backlinks-mode-next + "2.3.0") + +(defun denote-backlinks-mode-next (n) + "Use appropriate command for forward motion in backlinks buffer. +With N as a numeric argument, move to the Nth button from point. +A nil value of N is understood as 1. + +When `denote-backlinks-show-context' is nil, move between files +in the backlinks buffer. + +When `denote-backlinks-show-context' is non-nil move between +matching identifiers." + (interactive "p" denote-backlinks-mode) + (unless (derived-mode-p 'denote-backlinks-mode) + (user-error "Only use this in a Denote backlinks buffer")) + (if denote-backlinks-show-context + (xref-next-line) + (forward-button n))) + +(define-obsolete-function-alias + 'denote-backlinks-prev + 'denote-backlinks-mode-previous + "2.3.0") + +(defun denote-backlinks-mode-previous (n) + "Use appropriate command for backward motion in backlinks buffer. +With N as a numeric argument, move to the Nth button from point. +A nil value of N is understood as 1. + +When `denote-backlinks-show-context' is nil, move between files +in the backlinks buffer. + +When `denote-backlinks-show-context' is non-nil move between +matching identifiers." + (interactive "p" denote-backlinks-mode) + (unless (derived-mode-p 'denote-backlinks-mode) + (user-error "Only use this in a Denote backlinks buffer")) + (if denote-backlinks-show-context + (xref-prev-line) + (backward-button n))) + +(defvar denote-backlinks-mode-map + (let ((m (make-sparse-keymap))) + (define-key m "n" #'denote-backlinks-mode-next) + (define-key m "p" #'denote-backlinks-mode-previous) + (define-key m "g" #'revert-buffer) + m) + "Keymap for `denote-backlinks-mode'.") + +(define-derived-mode denote-backlinks-mode xref--xref-buffer-mode "Backlinks" + :interactive nil + "Major mode for backlinks buffers." + (unless denote-backlinks-show-context + (font-lock-add-keywords nil denote-faces-file-name-keywords t))) + +(defun denote-link--prepare-backlinks (fetcher _alist) + "Create backlinks' buffer for the current note. +FETCHER is a function that fetches a list of xrefs. It is called +with `funcall' with no argument like `xref--fetcher'. + +In the case of `denote', `apply-partially' is used to create a +function that has already applied another function to multiple +arguments. + +ALIST is not used in favour of using +`denote-link-backlinks-display-buffer-action'." + (let* ((inhibit-read-only t) + (file (buffer-file-name)) + (file-type (denote-filetype-heuristics file)) + (id (denote-retrieve-filename-identifier-with-error file)) + (buf (format "*denote-backlinks to %s*" id)) + ;; We retrieve results in absolute form and change the absolute + ;; path to a relative path a few lines below. We could add a + ;; suitable function to project-find-functions and the results + ;; would be automatically in relative form, but eventually + ;; notes may not be all under a common directory (or project). + (xref-file-name-display 'abs) + (xref-alist (xref--analyze (funcall fetcher))) + (dir (denote-directory))) + ;; Change the GROUP of each item in xref-alist to a relative path + (mapc (lambda (x) + (setf (car x) (denote-get-file-name-relative-to-denote-directory (car x)))) + xref-alist) + (with-current-buffer (get-buffer-create buf) + (setq-local default-directory dir) + (erase-buffer) + (setq overlay-arrow-position nil) + (denote-backlinks-mode) + (goto-char (point-min)) + (when-let ((title (denote-retrieve-front-matter-title-value file file-type)) + (heading (format "Backlinks to %S (%s)" title id)) + (l (length heading))) + (insert (format "%s\n%s\n\n" heading (make-string l ?-)))) + (if denote-backlinks-show-context + (xref--insert-xrefs xref-alist) + (mapc (lambda (x) + (insert (car x)) + (make-button (line-beginning-position) (line-end-position) :type 'denote-link-backlink-button) + (newline)) + xref-alist)) + (goto-char (point-min)) + (setq-local revert-buffer-function + (lambda (_ignore-auto _noconfirm) + (when-let ((buffer-file-name file)) + (denote-link--prepare-backlinks + (apply-partially #'xref-matches-in-files id + (denote-directory-files nil :omit-current :text-only)) + nil))))) + (denote-link--display-buffer buf))) + +(define-obsolete-function-alias + 'denote-link-backlinks + 'denote-backlinks + "2.0.0") + +;;;###autoload +(defun denote-backlinks () + "Produce a buffer with backlinks to the current note. + +The backlinks' buffer shows the file name of the note linking to +the current note, as well as the context of each link. + +File names are fontified by Denote if the user option +`denote-link-fontify-backlinks' is non-nil. If this user option +is nil, the buffer is fontified by Xref. + +The placement of the backlinks' buffer is controlled by the user +option `denote-link-backlinks-display-buffer-action'. By +default, it will show up below the current window." + (interactive) + (let ((file (buffer-file-name))) + (when (denote-file-is-writable-and-supported-p file) + (let* ((id (denote-retrieve-filename-identifier-with-error file)) + (xref-show-xrefs-function #'denote-link--prepare-backlinks)) + (xref--show-xrefs + (apply-partially #'xref-matches-in-files id + (denote-directory-files nil :omit-current :text-only)) + nil))))) + +(define-obsolete-function-alias + 'denote-link-show-backlinks-buffer + 'denote-show-backlinks-buffer + "2.0.0") + +(defalias 'denote-show-backlinks-buffer 'denote-backlinks + "Alias for `denote-backlinks' command.") + +;;;;; Add links matching regexp + +(defvar denote-link--prepare-links-format "- %s\n" + "Format specifiers for `denote-link-add-links'.") + +;; NOTE 2022-06-16: There is no need to overwhelm the user with options, +;; though I expect someone to want to change the sort order. +(defvar denote-link-add-links-sort nil + "When t, add REVERSE to `sort-lines' of `denote-link-add-links'.") + +(defun denote-link--prepare-links (files current-file-type id-only &optional no-sort) + "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'." + (with-temp-buffer + (mapc + (lambda (file) + (let ((description (denote--link-get-description file))) + (insert + (format + denote-link--prepare-links-format + (denote-format-link file description current-file-type id-only))))) + files) + (unless no-sort + (sort-lines denote-link-add-links-sort (point-min) (point-max))) + (buffer-string))) + +(define-obsolete-function-alias + 'denote-link-add-links + 'denote-add-links + "2.0.0") + +(defun denote-link--insert-links (files current-file-type &optional id-only no-sort) + "Insert at point a typographic list of links matching FILES. + +With CURRENT-FILE-TYPE as a symbol among those specified in +`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'." + (insert (denote-link--prepare-links files current-file-type id-only no-sort))) + +;;;###autoload +(defun denote-add-links (regexp &optional id-only) + "Insert links to all notes matching 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 matching REGEXP") + current-prefix-arg)) + (unless (or (denote--file-type-org-capture-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)) + (beg (point))) + (progn + (denote-link--insert-links files file-type id-only) + (denote-link-buttonize-buffer beg (point))) + (message "No links matching `%s'" regexp)))) + +(defalias 'denote-link-insert-links-matching-regexp 'denote-add-links + "Alias for `denote-add-links' command.") + +(define-obsolete-function-alias + 'denote-link-add-missing-links + 'denote-add-missing-links + "2.0.0") + +(make-obsolete 'denote-add-missing-links nil "2.2.0") + +;;;;; Links from Dired marks + +;; NOTE 2022-07-21: I don't think we need a history for this one. +(defun denote-link--buffer-prompt (buffers) + "Select buffer from BUFFERS visiting Denote notes." + (let ((buffer-file-names (mapcar #'file-name-nondirectory + buffers))) + (completing-read + "Select note buffer: " + (denote--completion-table 'buffer buffer-file-names) + nil t))) + +(defun denote-link--map-over-notes () + "Return list of `denote-file-is-note-p' from Dired marked items." + (when (denote--dir-in-denote-directory-p default-directory) + (seq-filter #'denote-file-is-note-p (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 are Denote notes, meaning that they have our file-naming +scheme, are writable/regular files, and use the appropriate file +type extension (per `denote-file-type'). Furthermore, the marked +files need to be inside the variable `denote-directory' or one of +its subdirectories. No other file is recognised (the list of +marked files ignores whatever does not count as a note for our +purposes). + +The BUFFER is one which visits a Denote note file. If there are +multiple buffers, prompt with completion for one among them. If +there isn't one, 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 + (list + (denote-link--map-over-notes) + (let ((file-names (denote--buffer-file-names))) + (find-file + (cond + ((null file-names) + (user-error "No buffers visiting Denote notes")) + ((eq (length file-names) 1) + (car file-names)) + (t + (denote-link--buffer-prompt file-names))))) + current-prefix-arg) + dired-mode) + (if (null files) + (user-error "No note files to link to") + (when (y-or-n-p (format "Create links at point in %s?" buffer)) + (with-current-buffer buffer + (insert (denote-link--prepare-links + files + (denote-filetype-heuristics (buffer-file-name)) + id-only)) + (denote-link-buttonize-buffer))))) + +;;;;; Define menu + +(defvar denote--menu-contents + '("Denote" + ["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 link" denote-link + :help "Insert link to a file in the `denote-directory'" + :enable (derived-mode-p 'text-mode)] + ["Insert links with regexp" denote-add-links + :help "Insert links to files matching regexp in the `denote-directory'" + :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)] + ["Link to existing note or newly created one with the chosen command" denote-link-or-create-with-command + :help "Insert a link to an existing file, else create it with the given command 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)] + "---" + ["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 search terms. +With optional FULL-DATA return a list in the form of (path id search)." + (let* ((search (and (string-match "::\\(.*\\)\\'" link) + (match-string 1 link))) + (id (if (and search (not (string-empty-p search))) + (substring link 0 (match-beginning 0)) + link)) + (path (denote-get-path-by-id id))) + (cond + (full-data + (list path id search)) + ((and search (not (string-empty-p search))) + (concat path "::" search)) + (path)))) + +;;;###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 +search option akin to that of standard Org `file:' link types. +Read Info node `(org) Search Options'. + +Uses the function `denote-directory' to establish the path to the +file." + (org-link-open-as-file + (denote-link--ol-resolve-link-to-target link) + nil)) + +;;;###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 id (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 () + "Handler for `org-store-link' adding support for denote: links. +Also see the user option `denote-org-store-link-to-heading'." + (when-let ((file (buffer-file-name)) + ((denote-file-is-note-p file)) + (file-id (denote-retrieve-filename-identifier file)) + (description (denote--link-get-description file))) + (let ((heading-links (and denote-org-store-link-to-heading (derived-mode-p 'org-mode)))) + (org-link-store-props + :type "denote" + :description (if heading-links + (denote-link-format-heading-description + description + (denote-link-ol-get-heading)) + description) + :link (if heading-links + (format "denote:%s::#%s" file-id (denote-link-ol-get-id)) + (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." + (let* ((path-id (denote-link--ol-resolve-link-to-target link :full-data)) + (path (file-relative-name (nth 0 path-id))) + (id (nth 1 path-id)) + (search (nth 2 path-id)) + (anchor (file-name-sans-extension path)) + (desc (cond + (description) + (search (format "denote:%s::%s" id search)) + (t (concat "denote:" id))))) + (cond + ((eq format 'html) + (if search + (format "%s" anchor search desc) + (format "%s" anchor desc))) + ((eq format 'latex) (format "\\href{%s}{%s}" (replace-regexp-in-string "[\\{}$%&_#~^]" "\\\\\\&" path) desc)) + ((eq format 'texinfo) (format "@uref{%s,%s}" path desc)) + ((eq format 'ascii) (format "[%s] " desc path)) + ((eq format 'md) (format "[%s](%s)" desc path)) + (t 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-faces-link + :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) + +(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." + (let (title keywords subdirectory date template signature) + (dolist (prompt denote-prompts) + (pcase prompt + ('title (setq title (denote-title-prompt + (when (use-region-p) + (buffer-substring-no-properties + (region-beginning) + (region-end)))))) + ('keywords (setq keywords (denote-keywords-prompt))) + ('subdirectory (setq subdirectory (denote-subdirectory-prompt))) + ('date (setq date (denote-date-prompt))) + ('template (setq template (denote-template-prompt))) + ('signature (setq signature (denote-signature-prompt))))) + (let* ((title (or title "")) + (date (if (or (null date) (string-empty-p date)) + (current-time) + (denote-valid-date-p date))) + (id (denote--find-first-unused-id + (denote-get-identifier date) + (denote--get-all-used-ids))) + (keywords (denote-keywords-sort keywords)) + (directory (if (denote--dir-in-denote-directory-p subdirectory) + (file-name-as-directory subdirectory) + (denote-directory))) + (template (if (stringp template) + template + (or (alist-get template denote-templates) ""))) + (signature (or signature "")) + (front-matter (denote--format-front-matter + title (denote--date nil 'org) keywords + (denote-get-identifier) 'org))) + (setq denote-last-path + (denote--path title keywords directory id 'org signature)) + (denote--keywords-add-to-history keywords) + (concat front-matter template 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) + +(provide 'denote) +;;; denote.el ends here blob - /dev/null blob + f1e10ac8bc86bae9ea53691ea6bfdfc6d7af692e (mode 644) Binary files /dev/null and elpa/denote-2.3.5/denote.info differ blob - /dev/null blob + f87165c99de687bfbadf03e7d99cf25a1a5a2530 (mode 644) --- /dev/null +++ elpa/denote-2.3.5/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 + df1af93ade6eb525703582ca79f9792cecce80bf (mode 644) --- /dev/null +++ elpa/denote-2.3.5/tests/denote-test.el @@ -0,0 +1,456 @@ +;;; denote-test.el --- Unit tests for Denote -*- lexical-binding: t -*- + +;; Copyright (C) 2023 Free Software Foundation, Inc. + +;; Author: Protesilaos Stavrou +;; Maintainer: Denote Development <~protesilaos/denote@lists.sr.ht> +;; URL: https://git.sr.ht/~protesilaos/denote +;; Mailing-List: https://lists.sr.ht/~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: + +;; WORK-IN-PROGRESS + +;;; Code: + +(require 'ert) +(require 'denote) + +(ert-deftest denote-test--denote--make-denote-directory () + "Test that `denote--make-denote-directory' creates the directory." + (should (null (denote--make-denote-directory)))) + +(ert-deftest denote-test--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 denote-test--denote--slug-no-punct () + "Test that `denote--slug-no-punct' removes punctuation from the string. +Concretely, replace with spaces anything that matches the +`denote-excluded-punctuation-regexp' and +`denote-excluded-punctuation-extra-regexp'." + (should (equal (denote--slug-no-punct "This is !@# test") + "This is test"))) + +(ert-deftest denote-test--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 denote-test--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 denote-test--denote--slug-put-equals () + "Test that `denote--slug-put-equals' replaces spaces/underscores with =. +Otherwise do the same as what is described in +`denote-test--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 denote-test--denote-sluggify-signature () + "Test that `denote-sluggify-signature' sluggifies the string for file signatures. +This is like `denote-test--denote-sluggify', except that it also +accounts for what we describe in `denote-test--denote--slug-put-equals'." + (should (equal (denote-sluggify-signature "--- ___ !~!!$%^ This -iS- a tEsT ++ ?? ") + "this=is=a=test"))) + +(ert-deftest denote-test--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 `denote-test--denote-sluggify'." + (should (equal (denote-sluggify-keyword "--- ___ !~!!$%^ This iS a - tEsT ++ ?? ") + "thisisatest"))) + +(ert-deftest denote-test--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 denote-test--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 denote-test--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 `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 denote-test--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 denote-test--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 denote-test--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 denote-test--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 denote-test--denote-file-type-extensions-with-encryption () + "Test that `denote-file-type-extensions-with-encryption' covers encryption. +Extend what we do in `denote-test--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 denote-test--denote-surround-with-quotes () + "Test that `denote-surround-with-quotes' returns a string in quotes." + (should (and (equal (denote-surround-with-quotes "test") "\"test\"") + (equal (denote-surround-with-quotes "") "\"\"") + (equal (denote-surround-with-quotes nil) "\"\"") + (equal (denote-surround-with-quotes 'wrong) "\"\"") + (equal (denote-surround-with-quotes '(wrong)) "\"\"")))) + +(ert-deftest denote-test--denote--format-front-matter () + "Test that `denote--format-front-matter' formats front matter correctly." + (should (and (equal (denote--format-front-matter "" "" '("") "" 'text) + (mapconcat #'identity + '("title: " + "date: " + "tags: " + "identifier: " + "---------------------------\n\n") + "\n")) + + (equal + (denote--format-front-matter + "Some test" "2023-06-05" '("one" "two") + "20230605T102234" 'text) + (mapconcat #'identity + '("title: Some test" + "date: 2023-06-05" + "tags: one two" + "identifier: 20230605T102234" + "---------------------------\n\n") + "\n")))) + + (should (and (equal (denote--format-front-matter "" "" nil "" 'org) + (mapconcat #'identity + '("#+title: " + "#+date: " + "#+filetags: " + "#+identifier: " + "\n") + "\n")) + + (equal + (denote--format-front-matter + "Some test" "2023-06-05" '("one" "two") + "20230605T102234" 'org) + (mapconcat #'identity + '("#+title: Some test" + "#+date: 2023-06-05" + "#+filetags: :one:two:" + "#+identifier: 20230605T102234" + "\n") + "\n")))) + + (should (and (equal (denote--format-front-matter "" "" nil "" 'markdown-yaml) + (mapconcat #'identity + '("---" + "title: \"\"" + "date: " + "tags: []" + "identifier: \"\"" + "---" + "\n") + "\n")) + + (equal + (denote--format-front-matter + "Some test" "2023-06-05" '("one" "two") + "20230605T102234" 'markdown-yaml) + (mapconcat #'identity + '("---" + "title: \"Some test\"" + "date: 2023-06-05" + "tags: [\"one\", \"two\"]" + "identifier: \"20230605T102234\"" + "---" + "\n") + "\n")))) + +(should (and (equal (denote--format-front-matter "" "" nil "" 'markdown-toml) + (mapconcat #'identity + '("+++" + "title = \"\"" + "date = " + "tags = []" + "identifier = \"\"" + "+++" + "\n") + "\n")) + + (equal + (denote--format-front-matter + "Some test" "2023-06-05" '("one" "two") + "20230605T102234" 'markdown-toml) + (mapconcat #'identity + '("+++" + "title = \"Some test\"" + "date = 2023-06-05" + "tags = [\"one\", \"two\"]" + "identifier = \"20230605T102234\"" + "+++" + "\n") + "\n"))))) + +(ert-deftest denote-test--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 + kws + title + (denote--file-extension 'org) + "")) + + (should-error (denote-format-file-name + (denote-directory) + "" + kws + title + (denote--file-extension 'org) + "")) + + (should-error (denote-format-file-name + (denote-directory) + "0123456" + kws + title + (denote--file-extension '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 denote-test--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 denote-test--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 denote-test--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") (caar denote-file-types)) + (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") '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 denote-test--denote-convert-file-name-keywords-to-crm () + "Ensure that `denote-convert-file-name-keywords-to-crm' returns words as comma-separated string." + (should + (and (equal (denote-convert-file-name-keywords-to-crm "_denote_keywords_testing") "denote,keywords,testing") + (equal (denote-convert-file-name-keywords-to-crm "_denote") "denote") + (equal (denote-convert-file-name-keywords-to-crm "") "")))) + +(ert-deftest denote-test--denote-get-identifier () + "Test that `denote-get-identifier' returns an identifier." + (should (and (equal (denote-get-identifier) (format-time-string denote-id-format (current-time))) + (equal (denote-get-identifier "2024-02-01 10:34") "20240201T103400") + (equal (denote-get-identifier 1705644188) "20240119T080308") + (equal (denote-get-identifier '(26026 4251)) "20240119T080307"))) + (should-error (denote-get-identifier "Invalid date"))) + +;;;; denote-journal-extras.el + +(require 'denote-journal-extras) + +(ert-deftest denote-test--denote-journal-extras-daily--title-format () + "Make sure that `denote-journal-extras-daily--title-format' yields the desired format." + (should (and + ;; These three should prompt, but I am here treating the + ;; prompt as if already returned a string. The test for + ;; the `denote-title-prompt' can be separate. + (stringp + (cl-letf (((symbol-function 'denote-title-prompt) #'identity) + (denote-journal-extras-title-format nil)) + (denote-journal-extras-daily--title-format))) + + (stringp + (cl-letf (((symbol-function 'denote-title-prompt) #'identity) + (denote-journal-extras-title-format t)) + (denote-journal-extras-daily--title-format))) + + (stringp + (cl-letf (((symbol-function 'denote-title-prompt) #'identity) + (denote-journal-extras-title-format :some-arbitrary-keyword)) + (denote-journal-extras-daily--title-format))) + + ;; And these return the following values + (string-match-p + "\\<.*?\\>" + (let ((denote-journal-extras-title-format 'day)) + (denote-journal-extras-daily--title-format))) + + (string-match-p + "\\<.*?\\> [0-9]\\{,2\\} \\<.*?\\> [0-9]\\{,4\\}" + (let ((denote-journal-extras-title-format 'day-date-month-year)) + (denote-journal-extras-daily--title-format))) + + (string-match-p + "\\<.*?\\> [0-9]\\{,2\\} \\<.*?\\> [0-9]\\{,4\\} [0-9]\\{,2\\}:[0-9]\\{,2\\} \\<.*?\\>" + (let ((denote-journal-extras-title-format 'day-date-month-year-12h)) + (denote-journal-extras-daily--title-format))) + + (string-match-p + "\\<.*?\\> [0-9]\\{,2\\} \\<.*?\\> [0-9]\\{,4\\} [0-9]\\{,2\\}:[0-9]\\{,2\\}" + (let ((denote-journal-extras-title-format 'day-date-month-year-24h)) + (denote-journal-extras-daily--title-format)))))) + +(provide 'denote-test) +;;; denote-test.el ends here blob - c25baf3cb7a9cc3df7455ec177b22216e6b53cb1 (mode 644) blob + /dev/null --- elpa/denote-2.3.3.signed +++ /dev/null @@ -1,2 +0,0 @@ -Good signature from 066DAFCB81E42C40 GNU ELPA Signing Agent (2019) (trust undefined) created at 2024-03-28T10:05:03+0100 using RSA -Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) (trust undefined) created at 2024-03-28T10:05:03+0100 using EDDSA \ No newline at end of file blob - /dev/null blob + e8902a607b4dd36ee8197f7ce1a56da7b3c78568 (mode 644) --- /dev/null +++ elpa/denote-2.3.5.signed @@ -0,0 +1 @@ +Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) (trust undefined) created at 2024-03-31T11:20:01+0200 using EDDSA \ No newline at end of file blob - fcc3bdace4c95ae545da3757dc9db9d2f47955b3 (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/.editorconfig +++ /dev/null @@ -1,30 +0,0 @@ -root = true - -[*] -end_of_line = lf - -trim_trailing_whitespace = true -insert_final_newline = true - - -[*.el] -indent_style = space -max_line_length = 80 - - -[Makefile] -indent_style = tab -tab_width = 4 - - -[.gitmodules] -indent_style = tab -tab_width = 4 - - -[*.yml] -indent_style = space -indent_size = 2 - -[ert-tests/**/*.ini] -file_type_ext = editorconfig blob - 8793fee6c817bff60108be8a57e01813817423f5 (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/.github/workflows/build.yaml +++ /dev/null @@ -1,63 +0,0 @@ -name: build - -on: - push: - branches: - - master - pull_request: - types: [opened, synchronize] - branches: - - 'master' - release: - types: [published] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - test: - runs-on: ${{ matrix.os }} - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - emacs_version: - - "26.3" - - "27.2" - - "28.2" - experimental: [false] - include: - - os: ubuntu-latest - emacs_version: snapshot - experimental: true - - os: macos-latest - emacs_version: snapshot - experimental: true - - os: windows-latest - emacs_version: snapshot - experimental: true - continue-on-error: ${{ matrix.experimental }} - - steps: - - name: Checkout - uses: actions/checkout@v3 - with: - submodules: recursive - - - name: Setup Emacs - uses: jcs090218/setup-emacs@master - with: - version: ${{ matrix.emacs_version }} - - - uses: emacs-eask/setup-eask@master - with: - version: 'snapshot' - - - name: Run tests (Unix) - if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' - run: make check-unix - - - name: Run tests (Windows) - if: matrix.os == 'windows-latest' - run: make check-dos blob - c7a684c0916a04ccd44682d99658acbbce933023 (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/CHANGELOG.md +++ /dev/null @@ -1,367 +0,0 @@ -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -### Added - -### Changed - -### Deprecated - -### Removed - -### Fixed - -### Security - - -## [0.10.0] - 2023-05-07 - -### Added - -- Enable indentation for tree-sitter based typescript mode ([#282]) -- Add support for json-ts-mode ([#283]) -- Add support for some treesit modes ([#287]) -- Add indent variable associations for numerous tree-sitter modes ([#290]) -- Add js-ts-mode' spec to editorconfig-indentation-alist' ([#293]) -- Add bash-ts-mode to editorconfig-indentation-alist ([#296]) -- Add support for gdscript-mode ([#300]) - -### Changed - -- Drop Emacs 24.x and 25.x ([#286]) - -### Deprecated - -### Removed - -### Fixed - -- Fix write-file-functions default value ([#295]) -- Check mode-class property for special modes ([#301]) -- Load subr-x when compiling ([#302]) - -### Security - - -## [0.9.1] - 2022-11-07 - -### Fixed - -- Check filename rather than buffer-file-name for consistency ([#280]) - - -## [0.9.0] - 2022-10-23 - -### Changed - -- Use new implementation by default ([#263]) - - Set `(setq editorconfig--legacy-version t)` to use previous one - - -## [0.8.2] - 2021-08-13 - -### Added - -- Add rustic-mode to editorconfig-indentation-alist ([#208]) -- Add conf-mode abbrev-table definitions ([#220]) -- Add meson-mode indentation rule ([#253]) -- Add support for rjsx-mode ([#254]) -- Update README for NonGNU ELPA repository ([#259]) -- Add new implementation of editorconfig-mode ([#248], [#250], [#251], [#255], [#258], [#260]) - - By default this is disabled: set `(setq editorconfig--enable-20210221-testing t)` to use this - -### Fixed - -- Fix so that "?" does not match "/" ([#211]) -- Fix document typo ([#213]) -- Don't make unchanged vars buffer-local ([#222]) -- Silence byte-compiler warnings ([#235]) -- Use revert-buffer-with-coding-system to set coding system ([#236]) -- Do not run editorconfig-apply on recentf-save-file ([#241]) -- Skip special-mode buffers when applying ([#247]) -- Stop excluding remote files by default ([#234], [#245]) -- Fix editorconfig execution for remote hosts via tramp ([#249]) -- Add minor fixes to tests ([#252]) -- Fix excluding the recentf-save-file when in a symlinked directory ([#256]) - -### Changed - -- Define -mode-apply as an interactive command ([#216]) -- Use elisp core by default ([#209]) -- User functions in the hooks `editorconfig-hack-properties-functions` and - `editorconfig-after-apply-functions` can no longer distinguish explicitly - unset properties from ones that were never set in the first place. ([#222]) - - -## [0.8.1] - 2019-10-10 - -### Added - -- Add indentation support - - [#196] - - enh-ruby-mode - - haxor-mode - - mips-mode - - nasm-mode - - terra-mode - - kotlin-mode - - bpftrace-mode ([#199]) - - f90-mode ([#200]) -- Add explicit support for rpm-spec-mode ([efc1ff4], see [#197] ) -- Add whitelist for file_type_emacs value ([#204]) - - -## [0.8.0] - 2019-03-26 - -### Fixed - -- Allow library forget properties order ([#187]) -- Use API to get version info ([#193]) - - `editorconfig-version()` was added and `editorconfig-core-version` removed -- Update docs and metadata to follow MELPA guidelines ([#189]) -- Refactor ([#188], [#191]) - - -## [0.7.14] - 2018-12-25 - -### Added - -- Add feature to decide major-mode from file_type_ext [EXPERIMENTAL] [#175] ([#178]) ([#179]) ([#180]) -- Add feature to hack properties before applying [#182] -- Add variable editorconfig-trim-whitespaces-mode [#183] - - Useful when you want to use non-default mode like `ws-butler` to trim spaces - -### Fixed - -- Make conf-mode used when a file has .editorconfig extension [01a0640] -- Fix tests -- Fix docs - -### Changed - -- Change hook name -custom-hooks -> -after-apply-functions [bb4bc44] - - -## [0.7.13] - 2018-08-23 - -### Fixed - -- Check editorconfig configs when read only state changes ([#168]) -- use CURDIR instead of PWD in Makefile ([#170]) -- Refactor fnmatch-p ([#171]) -- Update tests - - -## [0.7.12] - 2018-06-20 - -### Added - -- Add /Fix major-mode support - - pug-mode [#149] - - csharp-mode [#154] -- Add variable to disable lisp-indent-offset sometimes [#155] -- Add texinfo doc [#159] - -### Fixed - -- Avoid passing a non-absolute file path to editorconfig(1) [#151] -- Use "-with-signature" coding systems for all UTF-16 charsets [#158] -- Allow normal whitespace when reading EditorConfig settings file [#162] -- Add some fixes to tests - - -## [0.7.11] - 2017-11-07 - -### Added - -- Add /Fix major-mode support - - apache-mode - - groovy-mode - - web-mode -- Add support for a custom lighter -- Add editorconfig-format-buffer function -- Add experimental file_type_emacs support - -### Changed - -- Change hook editorconfig is applied on - - -## [0.7.10] - 2017-06-07 - -*Undocumented* - -## [0.7.9] - 2017-02-22 - -*Undocumented* - -## [0.7.8] - 2016-08-09 - -*Undocumented* - -## [0.7.7] - 2016-07-19 - -*Undocumented* - -## [0.7.6] - 2016-05-05 - -*Undocumented* - -## [0.7.5] - 2016-04-22 - -*Undocumented* - -## [0.7.4] - 2016-03-31 - -*Undocumented* - -## [0.7.3] - 2016-02-12 - -*Undocumented* - -## [0.7.2] - 2016-01-27 - -*Undocumented* - -## [0.7.1] - 2016-01-24 - -*Undocumented* - -## [0.7.0] - 2016-01-17 - -*Undocumented* - -## [0.6.2] - 2016-01-15 - -*Undocumented* - -## [0.6.1] - 2015-12-09 - -*Undocumented* - -## [0.6] - 2015-12-04 - -*Undocumented* - -## [0.5] - 2015-11-04 - -*Undocumented* - -## [0.4] - 2014-12-20 - -*Undocumented* - -## [0.3] - 2014-05-07 - -*Undocumented* - -## [0.2] - 2013-06-06 - -*Undocumented* - -## [0.1] - 2012-02-07 - -*Undocumented* - - -[Unreleased]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.10.0...HEAD -[0.10.0]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.9.1...v0.10.0 -[0.9.1]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.9.0...v0.9.1 -[0.9.0]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.8.2...v0.9.0 -[0.8.2]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.8.1...v0.8.2 -[0.8.1]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.8.0...v0.8.1 -[0.8.0]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.14...v0.8.0 -[0.7.14]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.13...v0.7.14 -[0.7.13]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.12...v0.7.13 -[0.7.12]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.11...v0.7.12 -[0.7.11]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.10...v0.7.11 -[0.7.10]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.9...v0.7.10 -[0.7.9]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.8...v0.7.9 -[0.7.8]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.7...v0.7.8 -[0.7.7]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.6...v0.7.7 -[0.7.6]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.5...v0.7.6 -[0.7.5]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.4...v0.7.5 -[0.7.4]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.3...v0.7.4 -[0.7.3]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.2...v0.7.3 -[0.7.2]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.1...v0.7.2 -[0.7.1]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.0...v0.7.1 -[0.7.0]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.6.2...v0.7.0 -[0.6.2]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.6.1...v0.6.2 -[0.6.1]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.6...v0.6.1 -[0.6]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.5...v0.6 -[0.5]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.4...v0.5 -[0.4]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.3...v0.4 -[0.3]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.2...v0.3 -[0.2]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.1...v0.2 -[0.1]: https://github.com/editorconfig/editorconfig-emacs/releases/tag/v0.1 -[#302]: https://github.com/editorconfig/editorconfig-emacs/issues/302 -[#301]: https://github.com/editorconfig/editorconfig-emacs/issues/301 -[#300]: https://github.com/editorconfig/editorconfig-emacs/issues/300 -[#296]: https://github.com/editorconfig/editorconfig-emacs/issues/296 -[#295]: https://github.com/editorconfig/editorconfig-emacs/issues/295 -[#293]: https://github.com/editorconfig/editorconfig-emacs/issues/293 -[#290]: https://github.com/editorconfig/editorconfig-emacs/issues/290 -[#287]: https://github.com/editorconfig/editorconfig-emacs/issues/287 -[#286]: https://github.com/editorconfig/editorconfig-emacs/issues/286 -[#283]: https://github.com/editorconfig/editorconfig-emacs/issues/283 -[#282]: https://github.com/editorconfig/editorconfig-emacs/issues/282 -[#280]: https://github.com/editorconfig/editorconfig-emacs/issues/280 -[#263]: https://github.com/editorconfig/editorconfig-emacs/issues/263 -[#260]: https://github.com/editorconfig/editorconfig-emacs/issues/260 -[#258]: https://github.com/editorconfig/editorconfig-emacs/issues/258 -[#255]: https://github.com/editorconfig/editorconfig-emacs/issues/255 -[#251]: https://github.com/editorconfig/editorconfig-emacs/issues/251 -[#250]: https://github.com/editorconfig/editorconfig-emacs/issues/250 -[#248]: https://github.com/editorconfig/editorconfig-emacs/issues/248 -[#259]: https://github.com/editorconfig/editorconfig-emacs/issues/259 -[#256]: https://github.com/editorconfig/editorconfig-emacs/issues/256 -[#252]: https://github.com/editorconfig/editorconfig-emacs/issues/252 -[#249]: https://github.com/editorconfig/editorconfig-emacs/issues/249 -[#245]: https://github.com/editorconfig/editorconfig-emacs/issues/245 -[#234]: https://github.com/editorconfig/editorconfig-emacs/issues/234 -[#241]: https://github.com/editorconfig/editorconfig-emacs/issues/241 -[#236]: https://github.com/editorconfig/editorconfig-emacs/issues/236 -[#235]: https://github.com/editorconfig/editorconfig-emacs/issues/235 -[#222]: https://github.com/editorconfig/editorconfig-emacs/issues/222 -[#222]: https://github.com/editorconfig/editorconfig-emacs/issues/222 -[#220]: https://github.com/editorconfig/editorconfig-emacs/issues/220 -[#216]: https://github.com/editorconfig/editorconfig-emacs/issues/216 -[#213]: https://github.com/editorconfig/editorconfig-emacs/issues/213 -[#211]: https://github.com/editorconfig/editorconfig-emacs/issues/211 -[#209]: https://github.com/editorconfig/editorconfig-emacs/issues/209 -[#208]: https://github.com/editorconfig/editorconfig-emacs/issues/208 -[#204]: https://github.com/editorconfig/editorconfig-emacs/issues/204 -[#200]: https://github.com/editorconfig/editorconfig-emacs/issues/200 -[#199]: https://github.com/editorconfig/editorconfig-emacs/issues/199 -[#197]: https://github.com/editorconfig/editorconfig-emacs/issues/197 -[#196]: https://github.com/editorconfig/editorconfig-emacs/issues/196 -[#193]: https://github.com/editorconfig/editorconfig-emacs/issues/193 -[#191]: https://github.com/editorconfig/editorconfig-emacs/issues/191 -[#189]: https://github.com/editorconfig/editorconfig-emacs/issues/189 -[#188]: https://github.com/editorconfig/editorconfig-emacs/issues/188 -[#187]: https://github.com/editorconfig/editorconfig-emacs/issues/187 -[#183]: https://github.com/editorconfig/editorconfig-emacs/issues/183 -[#182]: https://github.com/editorconfig/editorconfig-emacs/issues/182 -[#180]: https://github.com/editorconfig/editorconfig-emacs/issues/180 -[#179]: https://github.com/editorconfig/editorconfig-emacs/issues/179 -[#178]: https://github.com/editorconfig/editorconfig-emacs/issues/178 -[#175]: https://github.com/editorconfig/editorconfig-emacs/issues/175 -[#171]: https://github.com/editorconfig/editorconfig-emacs/issues/171 -[#170]: https://github.com/editorconfig/editorconfig-emacs/issues/170 -[#168]: https://github.com/editorconfig/editorconfig-emacs/issues/168 -[#162]: https://github.com/editorconfig/editorconfig-emacs/issues/162 -[#159]: https://github.com/editorconfig/editorconfig-emacs/issues/159 -[#158]: https://github.com/editorconfig/editorconfig-emacs/issues/158 -[#155]: https://github.com/editorconfig/editorconfig-emacs/issues/155 -[#154]: https://github.com/editorconfig/editorconfig-emacs/issues/154 -[#151]: https://github.com/editorconfig/editorconfig-emacs/issues/151 -[#149]: https://github.com/editorconfig/editorconfig-emacs/issues/149 -[01a0640]: https://github.com/editorconfig/editorconfig-emacs/commit/01a064015ed8d00f2853f966f07d2be5b97bfe5e -[efc1ff4]: https://github.com/editorconfig/editorconfig-emacs/commit/efc1ff4b1c3422d6e231b1c01138becab4b9eded -[bb4bc44]: https://github.com/editorconfig/editorconfig-emacs/commit/bb4bc4497783e6607480cd0b761f974136784fdd blob - 492a943f829f92a53ea9b6c3404ca14b5cbadbab (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/CONTRIBUTORS +++ /dev/null @@ -1,11 +0,0 @@ -Contributors to EditorConfig Emacs plugin (chronological order): - -Trey Hunner -Jonas Bernoulli -Johan Sundström -Desmond O. Chang -Steve Jordan -Hong Xu -10sr -Usami Kenta -Izaak "Zaak" Beekman blob - 54b22e07f136ca517baed1e92d20bcfd76cc7d2f (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/Eask +++ /dev/null @@ -1,20 +0,0 @@ -(package "editorconfig" - "0.10.1" - "EditorConfig Emacs Plugin") - -(website-url "https://github.com/editorconfig/editorconfig-emacs#readme") -(keywords "convenience" "editorconfig") - -(package-file "editorconfig.el") - -(files "editorconfig-*.el") - -(script "test" "echo \"Error: no test specified\" && exit 1") - -(source "gnu") -(source "melpa") - -(depends-on "emacs" "26.1") -(depends-on "nadvice") - -(setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 blob - 963f6de2ede09db7ebfd92aa4d443960ee878692 (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/Makefile +++ /dev/null @@ -1,81 +0,0 @@ -# -*- Makefile -*- - -TEXI_CHAPTER := EditorConfig Emacs Plugin - -EMACS = emacs -EASK = eask -PANDOC = pandoc -AWK = awk - -PROJECT_ROOT_DIR = $(CURDIR) -ERT_TESTS = $(wildcard $(PROJECT_ROOT_DIR)/ert-tests/*.el) - -# Compile with noninteractive and relatively clean environment. -BATCHFLAGS = -batch -q --no-site-file -L $(PROJECT_ROOT_DIR) - -MAIN_SRC = editorconfig.el -SRCS = $(wildcard $(PROJECT_ROOT_DIR)/*.el) -OBJS = $(SRCS:.el=.elc) - -.PHONY: check-unix check-dos \ - compile clean \ - test test-ert test-core \ - sandbox doc - -# CI entry -check-unix: package install compile test -check-dos: package install compile test-ert - -package: - $(EASK) package - -install: - $(EASK) install - -compile: - $(EASK) compile - -clean: - $(EASK) clean elc - - -doc: doc/editorconfig.texi - -doc/editorconfig.texi: README.md doc/header.txt - mkdir -p doc - tail -n +4 $< | $(PANDOC) -s -f markdown -t texinfo -o $@.body - $(AWK) 'f{print} /^@chapter $(TEXI_CHAPTER)/{f=1;print}' $@.body >$@.body2 - cat doc/header.txt $@.body2 >$@ - rm -f $@.body $@.body2 - -test: test-ert test-core - $(EMACS) $(BATCHFLAGS) -l editorconfig.el - - -# ert test -test-ert: $(ERT_TESTS) $(OBJS) - $(EMACS) $(BATCHFLAGS) \ - --eval "(setq debug-on-error t)" \ - --eval "(require 'ert)" \ - --eval "(setq metadata-el-files '($(MAIN_SRC:%=\"%\")))" \ - $(ERT_TESTS:%=-l "%") \ - -f ert-run-tests-batch-and-exit - - - -# Core test -core-test/CMakeLists.txt: - git submodule init - -test-core: core-test/CMakeLists.txt $(OBJS) - cd $(PROJECT_ROOT_DIR)/core-test && \ - cmake -DEDITORCONFIG_CMD="$(PROJECT_ROOT_DIR)/bin/editorconfig-el" . - cd $(PROJECT_ROOT_DIR)/core-test && \ - EMACS_BIN=$(EMACS) EDITORCONFIG_CORE_LIBRARY_PATH="$(PROJECT_ROOT_DIR)" \ - ctest --output-on-failure . - - -# Start Emacs that loads *.el in current directory and does not load the user -# init file -sandbox: - $(EMACS) -q -L $(PROJECT_ROOT_DIR) $(MAIN_SRC:%=-l "%") blob - 6ace73f6fe81a0db5d05dd1179d95cd69bdb6617 (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/README.md +++ /dev/null @@ -1,224 +0,0 @@ -![build](https://github.com/editorconfig/editorconfig-emacs/workflows/build/badge.svg) -[![MELPA](https://melpa.org/packages/editorconfig-badge.svg)](http://melpa.org/#/editorconfig) -[![MELPA Stable](https://stable.melpa.org/packages/editorconfig-badge.svg)](https://stable.melpa.org/#/editorconfig) -[![NonGNU ELPA](http://elpa.nongnu.org/nongnu/editorconfig.svg)](http://elpa.nongnu.org/nongnu/editorconfig.html) - -# EditorConfig Emacs Plugin - -This is an [EditorConfig][] plugin for [Emacs][]. - -## Getting Started - -### package.el - -This package is available from [MELPA][], [MELPA Stable][] and [NonGNU ELPA][]. -Install from these repositories and enable global minor-mode `editorconfig-mode`: - -```emacs-lisp -(editorconfig-mode 1) -``` - -Normally, enabling `editorconfig-mode` should be enough for this plugin to work: -all other configurations are optional. -This mode sets up hooks so that EditorConfig properties will be -loaded and applied to the new buffers automatically when visiting files. - -### use-package - -If you use [**use-package**][use-package], add the following to your -`init.el` file: - -```emacs-lisp -(use-package editorconfig - :ensure t - :config - (editorconfig-mode 1)) -``` - -### Manual installation - -Copy all `.el` files in this repository to `~/.emacs.d/lisp` and add the -following: - -```emacs-lisp -(add-to-list 'load-path "~/.emacs.d/lisp") -(require 'editorconfig) -(editorconfig-mode 1) -``` - -## Supported properties - -Current Emacs plugin coverage for EditorConfig's [properties][]: - -* `indent_style` -* `indent_size` -* `tab_width` -* `end_of_line` -* `charset` -* `trim_trailing_whitespace` -* `insert_final_newline = true` is supported -* ~~`insert_final_newline = false`~~ is not enforced - (as in trailing newlines actually being removed automagically), - we just buffer-locally override any preferences that would auto-add them - to files `.editorconfig` marks as trailing-newline-free -* `max_line_length` -* ~~`file_type_ext` (Experimental)~~ (See below) -* ~~`file_type_emacs` (Experimental)~~ (See below) -* `root` (only used by EditorConfig core) - -Not yet covered properties marked with ~~over-strike~~ -– pull requests implementing missing features warmly welcomed! -Typically, you will want to tie these to native functionality, -or the configuration of existing packages handling the feature. - -As several packages have their own handling of, say, indentation, -we might not yet cover some mode you use, but we try to add the -ones that show up on our radar. - -### ~~File Type (file_type_ext, file_type_emacs)~~ - -File-type feature is currently disabled, because this package is now undergoing -big internal refactoring. -For those who want this functionality, -please consider using [editorconfig-custom-majormode](https://github.com/10sr/editorconfig-custom-majormode-el). - -## Customize - -`editorconfig-emacs` provides some customize variables. - -Here are some of these variables: for the full list of available variables, -type M-x customize-group [RET] editorconfig [RET]. - -### `editorconfig-trim-whitespaces-mode` - -Buffer local minor-mode to use to trim trailing whitespaces. - -If set, editorconfig will enable/disable this mode in accord with -`trim_trailing_whitespace` property in `.editorconfig`. -Otherwise, use Emacs built-in `delete-trailing-whitespace` function. - -One possible value is -[`ws-butler-mode`](https://github.com/lewang/ws-butler), with which -only lines touched get trimmed. To use it, add following to your -init.el: - -``` emacs-lisp -(setq editorconfig-trim-whitespaces-mode - 'ws-butler-mode) -``` - -### `editorconfig-after-apply-functions` - -(Formerly `editorconfig-custom-hooks`) - -A list of functions which will be called after loading common EditorConfig settings, -when you can set some custom variables. - -For example, `web-mode` has several variables for indentation offset size and -EditorConfig sets them at once by `indent_size`. You can stop indenting -only blocks of `web-mode` by adding following to your init.el: - -```emacs-lisp -(add-hook 'editorconfig-after-apply-functions - (lambda (props) (setq web-mode-block-padding 0))) -``` - -## Troubleshooting - -Enabling `editorconfig-mode` should be enough for normal cases. - -When EditorConfig properties are not effective for unknown reason, we recommend -first trying `M-x editorconfig-display-current-properties`. - -This command will open a new buffer and display the EditorConfig properties -loaded for current buffer. -You can check if EditorConfig properties were not read for buffers at all, -or they were loaded but did not take effect for some other reasons. - -### Indentation for new major-modes - -Because most Emacs major-modes have their own indentation settings, this plugin -requires explicit support for each major-mode for `indent_size` property. - -By default this plugin ships with settings for many major-modes, but, -sorry to say, it cannot be perfect. Especially it is difficult to support -brand-new major-modes. -Please feel free to submit issue or pull-request for such major-mode! - -Supported major-modes and their indentation configs are defined in the variable -`editorconfig-indentation-alist`. - -### Not work at all for FOO-mode! - -Most cases properties are loaded just after visiting files when -`editorconfig-mode` is enabled. -But it is known that there are major-modes that this mechanism does not work -for and require explicit call of `editorconfig-apply`. - -Typically it will occur when the major-mode is not defined using -`define-derived-mode` (`rpm-spec-mode` is an example for this). -Please feel free to submit issues if you find such modes! - -### `editorconfig-format-buffer` does not work well with lsp-mode - -By default, [lsp-mode][] configures indent-region-function so that Emacs uses -language servers' `textDocument/rangeFormatting` request to format text in -buffers. -So EditorConfig settings are ignored unless language servers -themselves support loading configs from `.editorconfig`. - -To avoid this behavior ad-hocly, set `lsp-enable-indentation` to nil. - -## Submitting Bugs and Feature Requests - -Bugs, feature requests, and other issues should be submitted to the issue -tracker: https://github.com/editorconfig/editorconfig-emacs/issues - -### Development - -To run the test locally, you will need the following tools: - -- Make -- [CMake][] -- [Eask][] - -If you are on `Linux` or `macOS`: - - $ make check-unix - -On `Windows`: - - $ make check-dos - -To start a new Emacs process with current `*.el` and without loading user init -file, run: - - $ make sandbox - -## License - -EditorConfig Emacs Plugin 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 . - - -[Emacs]: https://www.gnu.org/software/emacs/ -[MELPA]: https://melpa.org/#/editorconfig -[MELPA Stable]: https://stable.melpa.org/#/editorconfig -[NonGNU ELPA]: http://elpa.nongnu.org/nongnu/editorconfig.html -[use-package]: https://www.emacswiki.org/emacs/UsePackage -[EditorConfig]: https://editorconfig.org -[EditorConfig C Core]: https://github.com/editorconfig/editorconfig-core-c -[properties]: https://editorconfig.org/#supported-properties -[CMake]: https://cmake.org -[Eask]: https://github.com/emacs-eask/cli -[lsp-mode]: https://github.com/emacs-lsp/lsp-mode blob - 37389027bca563d2da626c3fa5c64dcd70dfb2b8 (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/dir +++ /dev/null @@ -1,18 +0,0 @@ -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 -* EditorConfig: (editorconfig). EditorConfig Emacs Plugin. blob - 5e7d3b85552e2ae6f4f777efca1d94a8bedcc7cd (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/doc/editorconfig.texi +++ /dev/null @@ -1,297 +0,0 @@ -@dircategory Emacs -@direntry -* EditorConfig: (editorconfig). EditorConfig Emacs Plugin. -@end direntry - -@node Top -@chapter EditorConfig Emacs Plugin -@anchor{#editorconfig-emacs-plugin} -This is an @uref{https://editorconfig.org,EditorConfig} plugin for -@uref{https://www.gnu.org/software/emacs/,Emacs}. - -@menu -* Getting Started:: -* Supported properties:: -* Customize:: -* Troubleshooting:: -* Submitting Bugs and Feature Requests:: -* License:: -@end menu - -@node Getting Started -@section Getting Started -@anchor{#getting-started} - -@menu -* packageel:: -* use-package:: -* Manual installation:: -@end menu - -@node packageel -@subsection package.el -@anchor{#package.el} -This package is available from -@uref{https://melpa.org/#/editorconfig,MELPA}, -@uref{https://stable.melpa.org/#/editorconfig,MELPA Stable} and -@uref{http://elpa.nongnu.org/nongnu/editorconfig.html,NonGNU ELPA}. -Install from these repositories and enable global minor-mode -@code{editorconfig-mode}: - -@verbatim -(editorconfig-mode 1) -@end verbatim - -Normally, enabling @code{editorconfig-mode} should be enough for this -plugin to work: all other configurations are optional. This mode sets up -hooks so that EditorConfig properties will be loaded and applied to the -new buffers automatically when visiting files. - -@node use-package -@subsection use-package -@anchor{#use-package} -If you use -@uref{https://www.emacswiki.org/emacs/UsePackage,@strong{use-package}}, -add the following to your @code{init.el} file: - -@verbatim -(use-package editorconfig - :ensure t - :config - (editorconfig-mode 1)) -@end verbatim - -@node Manual installation -@subsection Manual installation -@anchor{#manual-installation} -Copy all @code{.el} files in this repository to @code{~/.emacs.d/lisp} -and add the following: - -@verbatim -(add-to-list 'load-path "~/.emacs.d/lisp") -(require 'editorconfig) -(editorconfig-mode 1) -@end verbatim - -@node Supported properties -@section Supported properties -@anchor{#supported-properties} -Current Emacs plugin coverage for EditorConfig's -@uref{https://editorconfig.org/#supported-properties,properties}: - -@itemize -@item -@code{indent_style} -@item -@code{indent_size} -@item -@code{tab_width} -@item -@code{end_of_line} -@item -@code{charset} -@item -@code{trim_trailing_whitespace} -@item -@code{insert_final_newline = true} is supported -@item -@code{insert_final_newline = false} -is not enforced (as in trailing newlines actually being removed -automagically), we just buffer-locally override any preferences that -would auto-add them to files @code{.editorconfig} marks as -trailing-newline-free -@item -@code{max_line_length} -@item -@code{file_type_ext} (Experimental) -(See below) -@item -@code{file_type_emacs} (Experimental) -(See below) -@item -@code{root} (only used by EditorConfig core) -@end itemize - -Not yet covered properties marked with over-strike -- pull requests -implementing missing features warmly welcomed! Typically, you will want -to tie these to native functionality, or the configuration of existing -packages handling the feature. - -As several packages have their own handling of, say, indentation, we -might not yet cover some mode you use, but we try to add the ones that -show up on our radar. - -@menu -* File Type file_type_ext file_type_emacs:: -@end menu - -@node File Type file_type_ext file_type_emacs -@subsection File Type (file_type_ext, file_type_emacs) -@anchor{#file-type-file_type_ext-file_type_emacs} -File-type feature is currently disabled, because this package is now -undergoing big internal refactoring. For those who want this -functionality, please consider using -@uref{https://github.com/10sr/editorconfig-custom-majormode-el,editorconfig-custom-majormode}. - -@node Customize -@section Customize -@anchor{#customize} -@code{editorconfig-emacs} provides some customize variables. - -Here are some of these variables: for the full list of available -variables, type M-x customize-group [RET] editorconfig [RET]. - -@menu -* editorconfig-trim-whitespaces-mode:: -* editorconfig-after-apply-functions:: -* editorconfig-hack-properties-functions:: -@end menu - -@node editorconfig-trim-whitespaces-mode -@subsection @code{editorconfig-trim-whitespaces-mode} -@anchor{#editorconfig-trim-whitespaces-mode} -Buffer local minor-mode to use to trim trailing whitespaces. - -If set, editorconfig will enable/disable this mode in accord with -@code{trim_trailing_whitespace} property in @code{.editorconfig}. -Otherwise, use Emacs built-in @code{delete-trailing-whitespace} -function. - -One possible value is -@uref{https://github.com/lewang/ws-butler,@code{ws-butler-mode}}, with -which only lines touched get trimmed. To use it, add following to your -init.el: - -@verbatim -(setq editorconfig-trim-whitespaces-mode - 'ws-butler-mode) -@end verbatim - -@node editorconfig-after-apply-functions -@subsection @code{editorconfig-after-apply-functions} -@anchor{#editorconfig-after-apply-functions} -(Formerly @code{editorconfig-custom-hooks}) - -A list of functions which will be called after loading common -EditorConfig settings, when you can set some custom variables. - -For example, @code{web-mode} has several variables for indentation -offset size and EditorConfig sets them at once by @code{indent_size}. -You can stop indenting only blocks of @code{web-mode} by adding -following to your init.el: - -@verbatim -(add-hook 'editorconfig-after-apply-functions - (lambda (props) (setq web-mode-block-padding 0))) -@end verbatim - -@node editorconfig-hack-properties-functions -@subsection @code{editorconfig-hack-properties-functions} -@anchor{#editorconfig-hack-properties-functions} -A list of functions to alter property values before applying them. - -These functions will be run after loading ".editorconfig" files and -before applying them to current buffer, so that you can alter some -properties from ".editorconfig" before they take effect. - -For example, Makefile files always use tab characters for indentation: -you can overwrite "indent_style" property when current @code{major-mode} -is @code{makefile-mode}: - -@verbatim -(add-hook 'editorconfig-hack-properties-functions - '(lambda (props) - (when (derived-mode-p 'makefile-mode) - (puthash 'indent_style "tab" props)))) -@end verbatim - -@node Troubleshooting -@section Troubleshooting -@anchor{#troubleshooting} -Enabling @code{editorconfig-mode} should be enough for normal cases. - -When EditorConfig properties are not effective for unknown reason, we -recommend first trying -@code{M-x editorconfig-display-current-properties}. - -This command will open a new buffer and display the EditorConfig -properties loaded for current buffer. You can check if EditorConfig -properties were not read for buffers at all, or they were loaded but did -not take effect for some other reasons. - -@menu -* Indentation for new major-modes:: -* Not work at all for FOO-mode!:: -@end menu - -@node Indentation for new major-modes -@subsection Indentation for new major-modes -@anchor{#indentation-for-new-major-modes} -Because most Emacs major-modes have their own indentation settings, this -plugin requires explicit support for each major-mode for -@code{indent_size} property. - -By default this plugin ships with settings for many major-modes, but, -sorry to say, it cannot be perfect. Especially it is difficult to -support brand-new major-modes. Please feel free to submit issue or -pull-request for such major-mode! - -Supported major-modes and their indentation configs are defined in the -variable @code{editorconfig-indentation-alist}. - -@node Not work at all for FOO-mode! -@subsection Not work at all for FOO-mode! -@anchor{#not-work-at-all-for-foo-mode} -Most cases properties are loaded just after visiting files when -@code{editorconfig-mode} is enabled. But it is known that there are -major-modes that this mechanism does not work for and require explicit -call of @code{editorconfig-apply}. - -Typically it will occur when the major-mode is not defined using -@code{define-derived-mode} (@code{rpm-spec-mode} is an example for -this). Please feel free to submit issues if you find such modes! - -@node Submitting Bugs and Feature Requests -@section Submitting Bugs and Feature Requests -@anchor{#submitting-bugs-and-feature-requests} -Bugs, feature requests, and other issues should be submitted to the -issue tracker: https://github.com/editorconfig/editorconfig-emacs/issues - -@menu -* Development:: -@end menu - -@node Development -@subsection Development -@anchor{#development} -Make and @uref{https://cmake.org,CMake} must be installed to run the -tests locally: - -@verbatim -$ make check -@end verbatim - -To start a new Emacs process with current @code{*.el} and without -loading user init file, run: - -@verbatim -$ make sandbox -@end verbatim - -@node License -@section License -@anchor{#license} -EditorConfig Emacs Plugin 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 @url{https://www.gnu.org/licenses/}. - -@bye blob - 8aa7143c5e844330511335a5e58e4fb329ebf327 (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/doc/header.txt +++ /dev/null @@ -1,6 +0,0 @@ -@dircategory Emacs -@direntry -* EditorConfig: (editorconfig). EditorConfig Emacs Plugin. -@end direntry - -@node Top blob - 95738d61a39f9b2fb0b4794b221580df850496c1 (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/editorconfig-autoloads.el +++ /dev/null @@ -1,144 +0,0 @@ -;;; editorconfig-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 editorconfig.el - -(autoload 'editorconfig-apply "editorconfig" "\ -Get and apply EditorConfig properties to current buffer. - -This function does not respect the values of `editorconfig-exclude-modes' and -`editorconfig-exclude-regexps' and always applies available properties. -Use `editorconfig-mode-apply' instead to make use of these variables." t) -(defvar editorconfig-mode nil "\ -Non-nil if Editorconfig mode is enabled. -See the `editorconfig-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 `editorconfig-mode'.") -(custom-autoload 'editorconfig-mode "editorconfig" nil) -(autoload 'editorconfig-mode "editorconfig" "\ -Toggle EditorConfig feature. - -To disable EditorConfig in some buffers, modify -`editorconfig-exclude-modes' or `editorconfig-exclude-regexps'. - -This is a global minor mode. If called interactively, toggle the - `Editorconfig 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 \\='editorconfig-mode)'. - -The mode's hook is called both when the mode is enabled and when - it is disabled. - -(fn &optional ARG)" t) -(autoload 'editorconfig-find-current-editorconfig "editorconfig" "\ -Find the closest .editorconfig file for current file." t) -(autoload 'editorconfig-display-current-properties "editorconfig" "\ -Display EditorConfig properties extracted for current buffer." t) -(defalias 'describe-editorconfig-properties 'editorconfig-display-current-properties) -(autoload 'editorconfig-format-buffer "editorconfig" "\ -Format buffer according to .editorconfig indent_style and indent_width." t) -(autoload 'editorconfig-version "editorconfig" "\ -Get EditorConfig version as string. - -If called interactively or if SHOW-VERSION is non-nil, show the -version in the echo area and the messages buffer. - -(fn &optional SHOW-VERSION)" t) -(register-definition-prefixes "editorconfig" '("editorconfig-")) - - -;;; Generated autoloads from editorconfig-conf-mode.el - -(autoload 'editorconfig-conf-mode "editorconfig-conf-mode" "\ -Major mode for editing .editorconfig files. - -(fn)" t) -(add-to-list 'auto-mode-alist '("\\.editorconfig\\'" . editorconfig-conf-mode)) -(register-definition-prefixes "editorconfig-conf-mode" '("editorconfig-conf-mode-")) - - -;;; Generated autoloads from editorconfig-core.el - -(autoload 'editorconfig-core-get-nearest-editorconfig "editorconfig-core" "\ -Return path to .editorconfig file that is closest to DIRECTORY. - -(fn DIRECTORY)") -(autoload 'editorconfig-core-get-properties "editorconfig-core" "\ -Get EditorConfig properties for FILE. -If FILE is not given, use currently visiting file. -Give CONFNAME for basename of config file other than .editorconfig. -If need to specify config format version, give CONFVERSION. - -This function returns an alist of properties. Each element will -look like (KEY . VALUE). - -(fn &optional FILE CONFNAME CONFVERSION)") -(autoload 'editorconfig-core-get-properties-hash "editorconfig-core" "\ -Get EditorConfig properties for FILE. -If FILE is not given, use currently visiting file. -Give CONFNAME for basename of config file other than .editorconfig. -If need to specify config format version, give CONFVERSION. - -This function is almost same as `editorconfig-core-get-properties', but returns -hash object instead. - -(fn &optional FILE CONFNAME CONFVERSION)") -(register-definition-prefixes "editorconfig-core" '("editorconfig-core--")) - - -;;; Generated autoloads from editorconfig-core-handle.el - -(register-definition-prefixes "editorconfig-core-handle" '("editorconfig-core-handle")) - - -;;; Generated autoloads from editorconfig-fnmatch.el - -(autoload 'editorconfig-fnmatch-p "editorconfig-fnmatch" "\ -Test whether STRING match PATTERN. - -Matching ignores case if `case-fold-search' is non-nil. - -PATTERN should be a shell glob pattern, and some zsh-like wildcard matchings can -be used: - -* Matches any string of characters, except path separators (/) -** Matches any string of characters -? Matches any single character -[name] Matches any single character in name -[^name] Matches any single character not in name -{s1,s2,s3} Matches any of the strings given (separated by commas) -{min..max} Matches any number between min and max - -(fn STRING PATTERN)") -(register-definition-prefixes "editorconfig-fnmatch" '("editorconfig-fnmatch-")) - -;;; End of scraped data - -(provide 'editorconfig-autoloads) - -;; Local Variables: -;; version-control: never -;; no-byte-compile: t -;; no-update-autoloads: t -;; no-native-compile: t -;; coding: utf-8-emacs-unix -;; End: - -;;; editorconfig-autoloads.el ends here blob - e582c3f16a014311866a7f1bfe4573c7e3b39b54 (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/editorconfig-conf-mode.el +++ /dev/null @@ -1,95 +0,0 @@ -;;; editorconfig-conf-mode.el --- Major mode for editing .editorconfig files -*- lexical-binding: t -*- - -;; Copyright (C) 2011-2023 EditorConfig Team - -;; Author: EditorConfig Team - -;; See -;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors -;; or the CONTRIBUTORS file for the list of contributors. - -;; This file is part of EditorConfig Emacs Plugin. - -;; EditorConfig Emacs Plugin 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. - -;; EditorConfig Emacs Plugin 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 -;; EditorConfig Emacs Plugin. If not, see . - -;;; Commentary: - -;; Major mode for editing .editorconfig files. - -;;; Code: - -(require 'conf-mode) - -(defvar editorconfig-conf-mode-syntax-table - (let ((table (make-syntax-table conf-unix-mode-syntax-table))) - (modify-syntax-entry ?\; "<" table) - table) - "Syntax table in use in `editorconfig-conf-mode' buffers.") - -(defvar editorconfig-conf-mode-abbrev-table nil - "Abbrev table in use in `editorconfig-conf-mode' buffers.") -(define-abbrev-table 'editorconfig-conf-mode-abbrev-table ()) - -;;;###autoload -(define-derived-mode editorconfig-conf-mode conf-unix-mode "Conf[EditorConfig]" - "Major mode for editing .editorconfig files." - (set-variable 'indent-line-function 'indent-relative) - (let ((key-property-list - '("charset" - "end_of_line" - "file_type_emacs" - "file_type_ext" - "indent_size" - "indent_style" - "insert_final_newline" - "max_line_length" - "root" - "tab_width" - "trim_trailing_whitespace")) - (key-value-list - '("unset" - "true" - "false" - "lf" - "cr" - "crlf" - "space" - "tab" - "latin1" - "utf-8" - "utf-8-bom" - "utf-16be" - "utf-16le")) - (font-lock-value - '(("^[ \t]*\\[\\(.+?\\)\\]" 1 font-lock-type-face) - ("^[ \t]*\\(.+?\\)[ \t]*[=:]" 1 font-lock-variable-name-face)))) - - ;; Highlight all key values - (dolist (key-value key-value-list) - (push `(,(format "[=:][ \t]*\\(%s\\)\\([ \t]\\|$\\)" key-value) - 1 font-lock-constant-face) - font-lock-value)) - ;; Highlight all key properties - (dolist (key-property key-property-list) - (push `(,(format "^[ \t]*\\(%s\\)[ \t]*[=:]" key-property) - 1 font-lock-builtin-face) - font-lock-value)) - - (conf-mode-initialize "#" font-lock-value))) - -;;;###autoload -(add-to-list 'auto-mode-alist '("\\.editorconfig\\'" . editorconfig-conf-mode)) - -(provide 'editorconfig-conf-mode) -;;; editorconfig-conf-mode.el ends here blob - 2659a8be9ea620f6a3bc2e290f34ef911626b994 (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/editorconfig-core-handle.el +++ /dev/null @@ -1,243 +0,0 @@ -;;; editorconfig-core-handle.el --- Handle Class for EditorConfig File -*- lexical-binding: t -*- - -;; Copyright (C) 2011-2023 EditorConfig Team - -;; Author: EditorConfig Team - -;; See -;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors -;; or the CONTRIBUTORS file for the list of contributors. - -;; This file is part of EditorConfig Emacs Plugin. - -;; EditorConfig Emacs Plugin 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. - -;; EditorConfig Emacs Plugin 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 -;; EditorConfig Emacs Plugin. If not, see . - -;;; Commentary: - -;; Handle structures for EditorConfig config file. This library is used -;; internally from editorconfig-core.el . - -;;; Code: - -(require 'cl-lib) - -(require 'editorconfig-fnmatch) - -(defvar editorconfig-core-handle--cache-hash - (make-hash-table :test 'equal) - "Hash of EditorConfig filename and its `editorconfig-core-handle' instance.") - -(cl-defstruct editorconfig-core-handle-section - "Structure representing one section in a .editorconfig file. - -Slots: - -`name' - String of section name (glob string). - -`props' - Alist of properties: (KEY . VALUE)." - (name nil) - (props nil)) - -(defun editorconfig-core-handle-section-get-properties (section file dir) - "Return properties alist when SECTION name match FILE. - -DIR should be the directory where .editorconfig file which has SECTION lives. -IF not match, return nil." - (when (editorconfig-core-handle--fnmatch-p - file (editorconfig-core-handle-section-name section) dir) - (editorconfig-core-handle-section-props section))) - -(cl-defstruct editorconfig-core-handle - "Structure representing an .editorconfig file. - -Slots: -`top-props' - Alist of top properties like ((\"root\" . \"true\")) - -`sections' - List of `editorconfig-core-handle-section' structure objects. - -`mtime' - Last modified time of .editorconfig file. - -`path' - Absolute path to .editorconfig file.' -" - (top-props nil) - (sections nil) - (mtime nil) - (path nil)) - - -(defun editorconfig-core-handle (conf) - "Return EditorConfig handle for CONF, which should be a file path. - -If CONF does not exist return nil." - (when (file-readable-p conf) - (let ((cached (gethash conf editorconfig-core-handle--cache-hash)) - (mtime (nth 5 (file-attributes conf)))) - (if (and cached - (equal (editorconfig-core-handle-mtime cached) mtime)) - cached - (let ((parsed (editorconfig-core-handle--parse-file conf))) - (puthash conf - (make-editorconfig-core-handle :top-props (plist-get parsed :top-props) - :sections (plist-get parsed :sections) - :mtime mtime - :path conf) - editorconfig-core-handle--cache-hash)))))) - -(defun editorconfig-core-handle-root-p (handle) - "Return non-nil if HANDLE represent root EditorConfig file. - -If HANDLE is nil return nil." - (when handle - (string-equal "true" - (downcase (or (cdr (assoc "root" - (editorconfig-core-handle-top-props handle))) - ""))))) - -(defun editorconfig-core-handle-get-properties (handle file) - "Return list of alist of properties from HANDLE for FILE. -The list returned will be ordered by the lines they appear. - -If HANDLE is nil return nil." - (when handle - (let ((dir (file-name-directory (editorconfig-core-handle-path handle)))) - (cl-loop for section in (editorconfig-core-handle-sections handle) - for props = (editorconfig-core-handle-section-get-properties section - file - dir) - when props collect (copy-alist props))))) -(make-obsolete 'editorconfig-core-handle-get-properties - 'editorconfig-core-handle-get-properties-hash - "0.8.0") - - -(defun editorconfig-core-handle-get-properties-hash (handle file) - "Return hash of properties from HANDLE for FILE. - -If HANDLE is nil return nil." - (when handle - (let ((hash (make-hash-table)) - (dir (file-name-directory (editorconfig-core-handle-path - handle)))) - (dolist (section (editorconfig-core-handle-sections handle)) - (cl-loop for (key . value) in (editorconfig-core-handle-section-get-properties section file dir) - do (puthash (intern key) value hash))) - hash))) - -(defun editorconfig-core-handle--fnmatch-p (name pattern dir) - "Return non-nil if NAME match PATTERN. -If pattern has slash, pattern should be relative to DIR. - -This function is a fnmatch with a few modification for EditorConfig usage." - (if (string-match-p "/" pattern) - (let ((pattern (replace-regexp-in-string "^/" "" pattern)) - (dir (file-name-as-directory dir))) - (editorconfig-fnmatch-p name (concat dir pattern))) - (editorconfig-fnmatch-p name (concat "**/" pattern)))) - -(defsubst editorconfig-core-handle--string-trim (str) - "Remove leading and trailing whitespaces from STR." - (replace-regexp-in-string "[[:space:]]+\\'" - "" - (replace-regexp-in-string "\\`[[:space:]]+" - "" - str))) - -(defun editorconfig-core-handle--parse-file (conf) - "Parse EditorConfig file CONF. - -This function returns cons of its top properties alist and -alist of patterns and its properties alist. -The list returned will be ordered by the lines they appear. - -If CONF is not found return nil." - (when (file-readable-p conf) - (with-temp-buffer - ;; NOTE: Use this instead of insert-file-contents-literally to enable - ;; code conversion - (insert-file-contents conf) - (goto-char (point-min)) - (let ((point-max (point-max)) - (sections ()) - (top-props nil) - - ;; String of current line - (line "") - ;; nil when pattern not appeared yet, "" when pattern is empty ("[]") - (pattern nil) - ;; Alist of properties for current PATTERN - (props ()) - - ;; Current line num - (current-line-number 1)) - (while (not (eq (point) point-max)) - (setq line - (buffer-substring-no-properties (line-beginning-position) - (line-end-position))) - (setq line - (replace-regexp-in-string "\\(^\\| \\)\\(#\\|;\\).*$" - "" - (editorconfig-core-handle--string-trim line))) - - (cond - ((string-equal "" line) - nil) - - ;; Start of section - ((string-match "^\\[\\(.*\\)\\]$" - line) - (when pattern - (setq sections - `(,@sections ,(make-editorconfig-core-handle-section - :name pattern - :props props))) - (setq pattern nil) - (setq props nil)) - (setq pattern (match-string 1 line))) - - (t - (let ((idx (string-match "=\\|:" line))) - (unless idx - (error "Error while reading config file: %s:%d:\n %s\n" - conf current-line-number line)) - (let ((key (downcase (editorconfig-core-handle--string-trim - (substring line 0 idx)))) - (value (editorconfig-core-handle--string-trim - (substring line (1+ idx))))) - (when (and (< (length key) 51) - (< (length value) 256)) - (if pattern - (when (< (length pattern) 4097) - (setq props - `(,@props (,key . ,value)))) - (setq top-props - `(,@top-props (,key . ,value))))))))) - (setq current-line-number (1+ current-line-number)) - (goto-char (point-min)) - (forward-line (1- current-line-number))) - (when pattern - (setq sections - `(,@sections ,(make-editorconfig-core-handle-section - :name pattern - :props props)))) - (list :top-props top-props - :sections sections))))) - -(provide 'editorconfig-core-handle) -;;; editorconfig-core-handle.el ends here blob - 999af2c12579f2981e54af9bcedb57705fb273a7 (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/editorconfig-core.el +++ /dev/null @@ -1,182 +0,0 @@ -;;; editorconfig-core.el --- EditorConfig Core library in Emacs Lisp -*- lexical-binding: t -*- - -;; Copyright (C) 2011-2023 EditorConfig Team - -;; Author: EditorConfig Team - -;; See -;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors -;; or the CONTRIBUTORS file for the list of contributors. - -;; This file is part of EditorConfig Emacs Plugin. - -;; EditorConfig Emacs Plugin 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. - -;; EditorConfig Emacs Plugin 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 -;; EditorConfig Emacs Plugin. If not, see . - -;;; Commentary: - -;; This library is one implementation of EditorConfig Core, which parses -;; .editorconfig files and returns properties for given files. -;; This can be used in place of, for example, editorconfig-core-c. - - -;; Use from EditorConfig Emacs Plugin - -;; Emacs plugin (v0.5 or later) can utilize this implementation. -;; By default, the plugin first search for any EditorConfig executable, -;; and fallback to this library if not found. -;; If you always want to use this library, add following lines to your init.el: - -;; (setq editorconfig-get-properties-function -;; 'editorconfig-core-get-properties-hash) - - -;; Functions - -;; editorconfig-core-get-properties (&optional file confname confversion) - -;; Get EditorConfig properties for FILE. - -;; If FILE is not given, use currently visiting file. -;; Give CONFNAME for basename of config file other than .editorconfig. -;; If need to specify config format version, give CONFVERSION. - -;; This functions returns alist of properties. Each element will look like -;; (KEY . VALUE) . - - -;; editorconfig-core-get-properties-hash (&optional file confname confversion) - -;; Get EditorConfig properties for FILE. - -;; This function is almost same as `editorconfig-core-get-properties', but -;; returns hash object instead. - -;;; Code: - -(require 'cl-lib) - -(require 'editorconfig-core-handle) - -(eval-when-compile - (require 'subr-x)) - - -(defun editorconfig-core--get-handles (dir confname &optional result) - "Get list of EditorConfig handlers for DIR from CONFNAME. - -In the resulting list, the handle for root config file comes first, and the -nearest comes last. -The list may contains nil when no file was found for directories. -RESULT is used internally and normally should not be used." - (setq dir (expand-file-name dir)) - (let ((handle (editorconfig-core-handle (concat (file-name-as-directory dir) - confname))) - (parent (file-name-directory (directory-file-name dir)))) - (if (or (string= parent dir) - (and handle (editorconfig-core-handle-root-p handle))) - (cl-remove-if-not 'identity (cons handle result)) - (editorconfig-core--get-handles parent - confname - (cons handle result))))) - -;;;###autoload -(defun editorconfig-core-get-nearest-editorconfig (directory) - "Return path to .editorconfig file that is closest to DIRECTORY." - (when-let* ((handle (car (last - (editorconfig-core--get-handles directory - ".editorconfig"))))) - (editorconfig-core-handle-path handle))) - -;;;###autoload -(defun editorconfig-core-get-properties (&optional file confname confversion) - "Get EditorConfig properties for FILE. -If FILE is not given, use currently visiting file. -Give CONFNAME for basename of config file other than .editorconfig. -If need to specify config format version, give CONFVERSION. - -This function returns an alist of properties. Each element will -look like (KEY . VALUE)." - (let ((hash (editorconfig-core-get-properties-hash file confname confversion)) - (result nil)) - (maphash (lambda (key value) - (add-to-list 'result (cons (symbol-name key) value))) - hash) - result)) - -(defun editorconfig-core--hash-merge (into update) - "Merge two hashes INTO and UPDATE. - -This is a destructive function, hash INTO will be modified. -When the same key exists in both two hashes, values of UPDATE takes precedence." - (maphash (lambda (key value) (puthash key value into)) update) - into) - -;;;###autoload -(defun editorconfig-core-get-properties-hash (&optional file confname confversion) - "Get EditorConfig properties for FILE. -If FILE is not given, use currently visiting file. -Give CONFNAME for basename of config file other than .editorconfig. -If need to specify config format version, give CONFVERSION. - -This function is almost same as `editorconfig-core-get-properties', but returns -hash object instead." - (setq file - (expand-file-name (or file - buffer-file-name - (error "FILE is not given and `buffer-file-name' is nil")))) - (setq confname (or confname ".editorconfig")) - (setq confversion (or confversion "0.12.0")) - (let ((result (make-hash-table))) - (dolist (handle (editorconfig-core--get-handles (file-name-directory file) - confname)) - (editorconfig-core--hash-merge result - (editorconfig-core-handle-get-properties-hash handle - file))) - - ;; Downcase known boolean values - (dolist (key '( end_of_line indent_style indent_size insert_final_newline - trim_trailing_whitespace charset)) - (when-let* ((val (gethash key result))) - (puthash key (downcase val) result))) - - ;; Add indent_size property - (let ((v-indent-size (gethash 'indent_size result)) - (v-indent-style (gethash 'indent_style result))) - (when (and (not v-indent-size) - (string= v-indent-style "tab") - ;; If VERSION < 0.9.0, indent_size should have no default value - (version<= "0.9.0" - confversion)) - (puthash 'indent_size - "tab" - result))) - ;; Add tab_width property - (let ((v-indent-size (gethash 'indent_size result)) - (v-tab-width (gethash 'tab_width result))) - (when (and v-indent-size - (not v-tab-width) - (not (string= v-indent-size "tab"))) - (puthash 'tab_width v-indent-size result))) - ;; Update indent-size property - (let ((v-indent-size (gethash 'indent_size result)) - (v-tab-width (gethash 'tab_width result))) - (when (and v-indent-size - v-tab-width - (string= v-indent-size "tab")) - (puthash 'indent_size v-tab-width result))) - - result)) - -(provide 'editorconfig-core) -;;; editorconfig-core.el ends here blob - 5432197b77836324fd221407f8a71435ef35d98d (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/editorconfig-fnmatch.el +++ /dev/null @@ -1,284 +0,0 @@ -;;; editorconfig-fnmatch.el --- Glob pattern matching in Emacs lisp -*- lexical-binding: t -*- - -;; Copyright (C) 2011-2023 EditorConfig Team - -;; Author: EditorConfig Team - -;; See -;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors -;; or the CONTRIBUTORS file for the list of contributors. - -;; This file is part of EditorConfig Emacs Plugin. - -;; EditorConfig Emacs Plugin 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. - -;; EditorConfig Emacs Plugin 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 -;; EditorConfig Emacs Plugin. If not, see . - -;;; Commentary: - -;; editorconfig-fnmatch.el provides a fnmatch implementation with a few -;; extensions. -;; The main usage of this library is glob pattern matching for EditorConfig, but -;; it can also act solely. - -;; editorconfig-fnmatch-p (name pattern) - -;; Test whether NAME match PATTERN. - -;; PATTERN should be a shell glob pattern, and some zsh-like wildcard matchings -;; can be used: - -;; * Matches any string of characters, except path separators (/) -;; ** Matches any string of characters -;; ? Matches any single character -;; [name] Matches any single character in name -;; [^name] Matches any single character not in name -;; {s1,s2,s3} Matches any of the strings given (separated by commas) -;; {min..max} Matches any number between min and max - - -;; This library is a port from editorconfig-core-py library. -;; https://github.com/editorconfig/editorconfig-core-py/blob/master/editorconfig/fnmatch.py - -;;; Code: - -(require 'cl-lib) - -(defvar editorconfig-fnmatch--cache-hashtable - nil - "Cache of shell pattern and its translation.") -;; Clear cache on file reload -(setq editorconfig-fnmatch--cache-hashtable - (make-hash-table :test 'equal)) - - -(defconst editorconfig-fnmatch--left-brace-regexp - "\\(^\\|[^\\]\\){" - "Regular expression for left brace ({).") - -(defconst editorconfig-fnmatch--right-brace-regexp - "\\(^\\|[^\\]\\)}" - "Regular expression for right brace (}).") - - -(defconst editorconfig-fnmatch--numeric-range-regexp - "\\([+-]?[0-9]+\\)\\.\\.\\([+-]?[0-9]+\\)" - "Regular expression for numeric range (like {-3..+3}).") - -(defun editorconfig-fnmatch--match-num (regexp string) - "Return how many times REGEXP is found in STRING." - (let ((num 0)) - ;; START arg does not work as expected in this case - (while (string-match regexp string) - (setq num (1+ num) - string (substring string (match-end 0)))) - num)) - -;;;###autoload -(defun editorconfig-fnmatch-p (string pattern) - "Test whether STRING match PATTERN. - -Matching ignores case if `case-fold-search' is non-nil. - -PATTERN should be a shell glob pattern, and some zsh-like wildcard matchings can -be used: - -* Matches any string of characters, except path separators (/) -** Matches any string of characters -? Matches any single character -[name] Matches any single character in name -[^name] Matches any single character not in name -{s1,s2,s3} Matches any of the strings given (separated by commas) -{min..max} Matches any number between min and max" - (string-match (editorconfig-fnmatch-translate pattern) - string)) - -;;(editorconfig-fnmatch-translate "{a,{-3..3}}.js") -;;(editorconfig-fnmatch-p "1.js" "{a,{-3..3}}.js") - -(defun editorconfig-fnmatch-translate (pattern) - "Translate a shell PATTERN to a regular expression. - -Translation result will be cached, so same translation will not be done twice." - (let ((cached (gethash pattern - editorconfig-fnmatch--cache-hashtable))) - (or cached - (puthash pattern - (editorconfig-fnmatch--do-translate pattern) - editorconfig-fnmatch--cache-hashtable)))) - - -(defun editorconfig-fnmatch--do-translate (pattern &optional nested) - "Translate a shell PATTERN to a regular expression. - -Set NESTED to t when this function is called from itself. - -This function is called from `editorconfig-fnmatch-translate', when no cached -translation is found for PATTERN." - (let ((index 0) - (length (length pattern)) - (brace-level 0) - (in-brackets nil) - ;; List of strings of resulting regexp - (result ()) - (is-escaped nil) - (matching-braces (= (editorconfig-fnmatch--match-num - editorconfig-fnmatch--left-brace-regexp - pattern) - (editorconfig-fnmatch--match-num - editorconfig-fnmatch--right-brace-regexp - pattern))) - - current-char - pos - has-slash - has-comma - num-range) - - (while (< index length) - (if (and (not is-escaped) - (string-match "[^]\\*?[{},/\\-]+" - ;;(string-match "[^]\\*?[{},/\\-]+" "?.a") - pattern - index) - (eq index (match-beginning 0))) - (setq result `(,@result ,(regexp-quote (match-string 0 pattern))) - index (match-end 0) - is-escaped nil) - - (setq current-char (aref pattern index) - index (1+ index)) - - (cl-case current-char - (?* - (setq pos index) - (if (and (< pos length) - (= (aref pattern pos) ?*)) - (setq result `(,@result ".*")) - (setq result `(,@result "[^/]*")))) - - (?? - (setq result `(,@result "[^/]"))) - - (?\[ - (if in-brackets - (setq result `(,@result "\\[")) - (if (= (aref pattern index) ?/) - ;; Slash after an half-open bracket - (setq result `(,@result "\\[/") - index (+ index 1)) - (setq pos index - has-slash nil) - (while (and (< pos length) - (not (= (aref pattern pos) ?\])) - (not has-slash)) - (if (and (= (aref pattern pos) ?/) - (not (= (aref pattern (- pos 1)) ?\\))) - (setq has-slash t) - (setq pos (1+ pos)))) - (if has-slash - (setq result `(,@result ,(concat "\\[" - (substring pattern - index - (1+ pos)) - "\\]")) - index (+ pos 2)) - (if (and (< index length) - (memq (aref pattern index) - '(?! ?^))) - (setq index (1+ index) - result `(,@result "[^")) - (setq result `(,@result "["))) - (setq in-brackets t))))) - - (?- - (if in-brackets - (setq result `(,@result "-")) - (setq result `(,@result "\\-")))) - - (?\] - (setq result `(,@result "]") - in-brackets nil)) - - (?{ - (setq pos index - has-comma nil) - (while (and (or (and (< pos length) - (not (= (aref pattern pos) ?}))) - is-escaped) - (not has-comma)) - (if (and (eq (aref pattern pos) ?,) - (not is-escaped)) - (setq has-comma t) - (setq is-escaped (and (eq (aref pattern pos) - ?\\) - (not is-escaped)) - pos (1+ pos)))) - (if (and (not has-comma) - (< pos length)) - (let ((pattern-sub (substring pattern index pos))) - (setq num-range (string-match editorconfig-fnmatch--numeric-range-regexp - pattern-sub)) - (if num-range - (let ((number-start (string-to-number (match-string 1 - pattern-sub))) - (number-end (string-to-number (match-string 2 - pattern-sub)))) - (setq result `(,@result ,(concat "\\(?:" - (mapconcat 'number-to-string - (cl-loop for i from number-start to number-end - collect i) - "\\|") - "\\)")))) - (let ((inner (editorconfig-fnmatch--do-translate pattern-sub t))) - (setq result `(,@result ,(format "{%s}" inner))))) - (setq index (1+ pos))) - (if matching-braces - (setq result `(,@result "\\(?:") - brace-level (1+ brace-level)) - (setq result `(,@result "{"))))) - - (?, - (if (and (> brace-level 0) - (not is-escaped)) - (setq result `(,@result "\\|")) - (setq result `(,@result "\\,")))) - - (?} - (if (and (> brace-level 0) - (not is-escaped)) - (setq result `(,@result "\\)") - brace-level (- brace-level 1)) - (setq result `(,@result "}")))) - - (?/ - (if (and (<= (+ index 3) (length pattern)) - (string= (substring pattern index (+ index 3)) "**/")) - (setq result `(,@result "\\(?:/\\|/.*/\\)") - index (+ index 3)) - (setq result `(,@result "/")))) - - (t - (unless (= current-char ?\\) - (setq result `(,@result ,(regexp-quote (char-to-string current-char))))))) - - (if (= current-char ?\\) - (progn (when is-escaped - (setq result `(,@result "\\\\"))) - (setq is-escaped (not is-escaped))) - (setq is-escaped nil)))) - (unless nested - (setq result `("^" ,@result "\\'"))) - (apply #'concat result))) - -(provide 'editorconfig-fnmatch) -;;; editorconfig-fnmatch.el ends here blob - e838d5a5be21f5112b991aabfd5d7d0cdfd22f9c (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/editorconfig-pkg.el +++ /dev/null @@ -1,2 +0,0 @@ -;; Generated package description from editorconfig.el -*- no-byte-compile: t -*- -(define-package "editorconfig" "0.10.1" "EditorConfig Emacs Plugin" '((emacs "26.1") (nadvice "0.3")) :commit "ed760770ed5397120b3d68b69afc0778c48d3a47" :authors '(("EditorConfig Team" . "editorconfig@googlegroups.com")) :maintainer '("EditorConfig Team" . "editorconfig@googlegroups.com") :keywords '("convenience" "editorconfig") :url "https://github.com/editorconfig/editorconfig-emacs#readme") blob - 365bd965d29b1d751788ce3740c3fdb7001de301 (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/editorconfig.el +++ /dev/null @@ -1,990 +0,0 @@ -;;; editorconfig.el --- EditorConfig Emacs Plugin -*- lexical-binding: t -*- - -;; Copyright (C) 2011-2023 EditorConfig Team - -;; Author: EditorConfig Team -;; Version: 0.10.1 -;; URL: https://github.com/editorconfig/editorconfig-emacs#readme -;; Package-Requires: ((emacs "26.1") (nadvice "0.3")) -;; Keywords: convenience editorconfig - -;; See -;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors -;; or the CONTRIBUTORS file for the list of contributors. - -;; This file is part of EditorConfig Emacs Plugin. - -;; EditorConfig Emacs Plugin 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. - -;; EditorConfig Emacs Plugin 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 -;; EditorConfig Emacs Plugin. If not, see . - -;;; Commentary: - -;; EditorConfig helps developers define and maintain consistent -;; coding styles between different editors and IDEs. - -;; The EditorConfig project consists of a file format for defining -;; coding styles and a collection of text editor plugins that enable -;; editors to read the file format and adhere to defined styles. -;; EditorConfig files are easily readable and they work nicely with -;; version control systems. - -;;; Code: - -(require 'cl-lib) -(require 'pcase) - -(require 'nadvice) - -(eval-when-compile - (require 'rx) - (require 'subr-x) - (defvar tex-indent-basic) - (defvar tex-indent-item) - (defvar tex-indent-arg) - (defvar evil-shift-width)) - -(require 'editorconfig-core) - -(defgroup editorconfig nil - "EditorConfig Emacs Plugin. - -EditorConfig helps developers define and maintain consistent -coding styles between different editors and IDEs." - :tag "EditorConfig" - :prefix "editorconfig-" - :group 'tools) - -(define-obsolete-variable-alias - 'edconf-exec-path - 'editorconfig-exec-path - "0.5") -(defcustom editorconfig-exec-path - "editorconfig" - "Path to EditorConfig executable. - -Used by `editorconfig--execute-editorconfig-exec'." - :type 'string - :group 'editorconfig) - -(define-obsolete-variable-alias - 'edconf-get-properties-function - 'editorconfig-get-properties-function - "0.5") -(defcustom editorconfig-get-properties-function - 'editorconfig-core-get-properties-hash - "A function which gets EditorConfig properties for specified file. - -This function will be called with one argument, full path of the target file, -and should return a hash object containing properties, or nil if any core -program is not available. Keys of this hash should be symbols of properties, -and values should be strings of their values. - - -For example, if you always want to use built-in core library instead -of any EditorConfig executable to get properties, add following to -your init.el: - - (set-variable \\='editorconfig-get-properties-function - #\\='editorconfig-core-get-properties-hash) - -Possible known values are: - -* `editorconfig-core-get-properties-hash' (default) - * Always use built-in Emacs-Lisp implementation to get properties -* `editorconfig-get-properties' - * Use `editorconfig-get-properties-from-exec' when - `editorconfig-exec-path' executable is found, otherwise - use `editorconfig-core-get-properties-hash' -* `editorconfig-get-properties-from-exec' - * Get properties by executing EditorConfig executable" - :type 'function - :group 'editorconfig) - -(defcustom editorconfig-mode-lighter " EditorConfig" - "Command `editorconfig-mode' lighter string." - :type 'string - :group 'editorconfig) - -(define-obsolete-variable-alias - 'edconf-custom-hooks - 'editorconfig-after-apply-functions - "0.5") -(define-obsolete-variable-alias - 'editorconfig-custom-hooks - 'editorconfig-after-apply-functions - "0.7.14") -(defcustom editorconfig-after-apply-functions () - "A list of functions after loading common EditorConfig settings. - -Each element in this list is a hook function. This hook function -takes one parameter, which is a property hash table. The value -of properties can be obtained through gethash function. - -The hook does not have to be coding style related; you can add -whatever functionality you want. For example, the following is -an example to add a new property emacs_linum to decide whether to -show line numbers on the left: - - (add-hook \\='editorconfig-after-apply-functions - \\='(lambda (props) - (let ((show-line-num (gethash \\='emacs_linum props))) - (cond ((equal show-line-num \"true\") (linum-mode 1)) - ((equal show-line-num \"false\") (linum-mode 0)))))) - -This hook will be run even when there are no matching sections in -\".editorconfig\", or no \".editorconfig\" file was found at all." - :type 'hook - :group 'editorconfig) - -(defcustom editorconfig-hack-properties-functions () - "A list of function to alter property values before applying them. - -These functions will be run after loading \".editorconfig\" files and before -applying them to current buffer, so that you can alter some properties from -\".editorconfig\" before they take effect. -\(Since 2021/08/30 (v0.9.0): Buffer coding-systems are set before running -this functions, so this variable cannot be used to change coding-systems.) - -For example, Makefiles always use tab characters for indentation: you can -overwrite \"indent_style\" property when current `major-mode' is a -`makefile-mode' with following code: - - (add-hook \\='editorconfig-hack-properties-functions - \\='(lambda (props) - (when (derived-mode-p \\='makefile-mode) - (puthash \\='indent_style \"tab\" props)))) - -This hook will be run even when there are no matching sections in -\".editorconfig\", or no \".editorconfig\" file was found at all." - :type 'hook - :group 'editorconfig) -(make-obsolete-variable 'editorconfig-hack-properties-functions - "Using `editorconfig-after-apply-functions' instead is recommended, - because since 2021/08/30 (v0.9.0) this variable cannot support all properties: - charset values will be referenced before running this hook." - "v0.9.0") - -(define-obsolete-variable-alias - 'edconf-indentation-alist - 'editorconfig-indentation-alist - "0.5") -(defcustom editorconfig-indentation-alist - ;; For contributors: Sort modes in alphabetical order - '((apache-mode apache-indent-level) - (awk-mode c-basic-offset) - (bpftrace-mode c-basic-offset) - (c++-mode c-basic-offset) - (c++-ts-mode c-basic-offset - c-ts-mode-indent-offset) - (c-mode c-basic-offset) - (c-ts-mode c-basic-offset - c-ts-mode-indent-offset) - (cmake-mode cmake-tab-width) - (cmake-ts-mode cmake-tab-width - cmake-ts-mode-indent-offset) - (coffee-mode coffee-tab-width) - (cperl-mode cperl-indent-level) - (crystal-mode crystal-indent-level) - (csharp-mode c-basic-offset) - (csharp-ts-mode c-basic-offset - csharp-ts-mode-indent-offset) - (css-mode css-indent-offset) - (css-ts-mode css-indent-offset) - (d-mode c-basic-offset) - (emacs-lisp-mode lisp-indent-offset) - (enh-ruby-mode enh-ruby-indent-level) - (erlang-mode erlang-indent-level) - (ess-mode ess-indent-offset) - (f90-mode f90-associate-indent - f90-continuation-indent - f90-critical-indent - f90-do-indent - f90-if-indent - f90-program-indent - f90-type-indent) - (feature-mode feature-indent-offset - feature-indent-level) - (fsharp-mode fsharp-continuation-offset - fsharp-indent-level - fsharp-indent-offset) - (gdscript-mode gdscript-indent-offset) - (groovy-mode groovy-indent-offset) - (go-ts-mode go-ts-mode-indent-offset) - (haskell-mode haskell-indent-spaces - haskell-indent-offset - haskell-indentation-layout-offset - haskell-indentation-left-offset - haskell-indentation-starter-offset - haskell-indentation-where-post-offset - haskell-indentation-where-pre-offset - shm-indent-spaces) - (haxor-mode haxor-tab-width) - (html-ts-mode html-ts-mode-indent-offset) - (idl-mode c-basic-offset) - (jade-mode jade-tab-width) - (java-mode c-basic-offset) - (java-ts-mode c-basic-offset - java-ts-mode-indent-offset) - (js-mode js-indent-level) - (js-ts-mode js-indent-level) - (js-jsx-mode js-indent-level sgml-basic-offset) - (js2-mode js2-basic-offset) - (js2-jsx-mode js2-basic-offset sgml-basic-offset) - (js3-mode js3-indent-level) - (json-mode js-indent-level) - (json-ts-mode json-ts-mode-indent-offset) - (julia-mode julia-indent-offset) - (kotlin-mode kotlin-tab-width) - (latex-mode . editorconfig-set-indentation-latex-mode) - (lisp-mode lisp-indent-offset) - (livescript-mode livescript-tab-width) - (lua-mode lua-indent-level) - (matlab-mode matlab-indent-level) - (meson-mode meson-indent-basic) - (mips-mode mips-tab-width) - (mustache-mode mustache-basic-offset) - (nasm-mode nasm-basic-offset) - (nginx-mode nginx-indent-level) - (nxml-mode nxml-child-indent (nxml-attribute-indent . 2)) - (objc-mode c-basic-offset) - (octave-mode octave-block-offset) - (perl-mode perl-indent-level) - ;; No need to change `php-mode-coding-style' value for php-mode - ;; since we run editorconfig later than it resets `c-basic-offset'. - ;; See https://github.com/editorconfig/editorconfig-emacs/issues/116 - ;; for details. - (php-mode c-basic-offset) - (pike-mode c-basic-offset) - (ps-mode ps-mode-tab) - (pug-mode pug-tab-width) - (puppet-mode puppet-indent-level) - (python-mode . editorconfig-set-indentation-python-mode) - (python-ts-mode . editorconfig-set-indentation-python-mode) - (rjsx-mode js-indent-level sgml-basic-offset) - (ruby-mode ruby-indent-level) - (ruby-ts-mode ruby-indent-level) - (rust-mode rust-indent-offset) - (rust-ts-mode rust-indent-offset - rust-ts-mode-indent-offset) - (rustic-mode rustic-indent-offset) - (scala-mode scala-indent:step) - (scss-mode css-indent-offset) - (sgml-mode sgml-basic-offset) - (sh-mode sh-basic-offset sh-indentation) - (bash-ts-mode sh-basic-offset sh-indentation) - (slim-mode slim-indent-offset) - (sml-mode sml-indent-level) - (tcl-mode tcl-indent-level - tcl-continued-indent-level) - (terra-mode terra-indent-level) - (toml-ts-mode toml-ts-mode-indent-offset) - (typescript-mode typescript-indent-level) - (typescript-ts-base-mode typescript-ts-mode-indent-offset) - (verilog-mode verilog-indent-level - verilog-indent-level-behavioral - verilog-indent-level-declaration - verilog-indent-level-module - verilog-cexp-indent - verilog-case-indent) - (web-mode (web-mode-indent-style . (lambda (size) 2)) - web-mode-attr-indent-offset - web-mode-attr-value-indent-offset - web-mode-code-indent-offset - web-mode-css-indent-offset - web-mode-markup-indent-offset - web-mode-sql-indent-offset - web-mode-block-padding - web-mode-script-padding - web-mode-style-padding) - (yaml-mode yaml-indent-offset) - (yaml-ts-mode yaml-indent-offset)) - "Alist of indentation setting methods by modes. - -Each element looks like (MODE . FUNCTION) or (MODE . INDENT-SPEC-LIST). - -If FUNCTION is provided, it will be called when setting the -indentation. The indent size will be passed. - -If INDENT-SPEC-LIST is provided, each element of it must have one of the -following forms: - - 1. VARIABLE - It means (VARIABLE . 1). - - 2. (VARIABLE . SPEC) - Setting VARIABLE according to the type of SPEC: - - - Integer - The value is (* SPEC INDENT-SIZE); - - - Function - The value is (funcall SPEC INDENT-SIZE); - - - Any other type. - The value is SPEC. - -NOTE: Only the **buffer local** value of VARIABLE will be set." - :type '(alist :key-type symbol :value-type sexp) - :risky t - :group 'editorconfig) - -(defcustom editorconfig-exclude-modes () - "Modes in which `editorconfig-mode-apply' will not run." - :type '(repeat (symbol :tag "Major Mode")) - :group 'editorconfig) - -(defcustom editorconfig-exclude-regexps () - "List of regexp for buffer filenames `editorconfig-mode-apply' will not run. - -When variable `buffer-file-name' matches any of the regexps, then -`editorconfig-mode-apply' will not do its work." - :type '(repeat string) - :group 'editorconfig) -(with-eval-after-load 'recentf - (add-to-list 'editorconfig-exclude-regexps - (rx-to-string '(seq string-start - (eval (file-truename (expand-file-name recentf-save-file)))) - t))) - -(defcustom editorconfig-trim-whitespaces-mode nil - "Buffer local minor-mode to use to trim trailing whitespaces. - -If set, enable that mode when `trim_trailing_whitespace` is set to true. -Otherwise, use `delete-trailing-whitespace'." - :type 'symbol - :group 'editorconfig) - -(defvar editorconfig-properties-hash nil - "Hash object of EditorConfig properties that was enabled for current buffer. -Set by `editorconfig-apply' and nil if that is not invoked in -current buffer yet.") -(make-variable-buffer-local 'editorconfig-properties-hash) -(put 'editorconfig-properties-hash - 'permanent-local - t) - -(defvar editorconfig-lisp-use-default-indent nil - "Selectively ignore the value of indent_size for Lisp files. -Prevents `lisp-indent-offset' from being set selectively. - -nil - `lisp-indent-offset' is always set normally. -t - `lisp-indent-offset' is never set normally - (always use default indent for lisps). -number - `lisp-indent-offset' is not set only if indent_size is - equal to this number. For example, if this is set to 2, - `lisp-indent-offset' will not be set only if indent_size is 2.") - -(define-error 'editorconfig-error - "Error thrown from editorconfig lib") - -(defun editorconfig-error (&rest args) - "Signal an `editorconfig-error'. -Make a message by passing ARGS to `format-message'." - (signal 'editorconfig-error (list (apply #'format-message args)))) - -(defun editorconfig--disabled-for-filename (filename) - "Return non-nil when EditorConfig is disabled for FILENAME." - (cl-assert (stringp filename)) - (cl-loop for regexp in editorconfig-exclude-regexps - if (string-match regexp filename) return t - finally return nil)) - -(defun editorconfig--disabled-for-majormode (majormode) - "Return non-nil when Editorconfig is disabled for MAJORMODE." - (cl-assert majormode) - (or (provided-mode-derived-p majormode 'special-mode) - ;; Some special modes (like `archive-mode') are not derived from - ;; `special-mode' - (eq (get majormode 'mode-class) 'special) - (memq majormode - editorconfig-exclude-modes))) - -(defun editorconfig-string-integer-p (string) - "Return non-nil if STRING represents integer." - (and (stringp string) - (string-match-p "\\`[0-9]+\\'" string))) - -(defun editorconfig-set-indentation-python-mode (size) - "Set `python-mode' indent size to SIZE." - (when (boundp 'python-indent-offset) - (setq-local python-indent-offset size)) - ;; For https://launchpad.net/python-mode - (when (boundp 'py-indent-offset) - (setq-local py-indent-offset size))) - -(defun editorconfig-set-indentation-latex-mode (size) - "Set `latex-mode' indent size to SIZE." - (setq-local tex-indent-basic size) - (setq-local tex-indent-item size) - (setq-local tex-indent-arg (* 2 size)) - ;; For AUCTeX - (when (boundp 'TeX-brace-indent-level) - (setq-local TeX-brace-indent-level size)) - (when (boundp 'LaTeX-indent-level) - (setq-local LaTeX-indent-level size)) - (when (boundp 'LaTeX-item-indent) - (setq-local LaTeX-item-indent (- size)))) - -(defun editorconfig--should-set (size symbol) - "Determines if editorconfig should set SYMBOL using SIZE." - (if (eq symbol 'lisp-indent-offset) - (cond - ((null editorconfig-lisp-use-default-indent) t) - ((eql t editorconfig-lisp-use-default-indent) nil) - ((numberp editorconfig-lisp-use-default-indent) - (not (eql size editorconfig-lisp-use-default-indent))) - (t t)) - t)) - -(defun editorconfig-set-indentation (style &optional size tab_width) - "Set indentation type from STYLE, SIZE and TAB_WIDTH." - (if (editorconfig-string-integer-p size) - (setq size (string-to-number size)) - (unless (equal size "tab") (setq size nil))) - (cond (tab_width - (setq tab-width (string-to-number tab_width))) - ((numberp size) - (setq tab-width size))) - (when (equal size "tab") - (setq size tab-width)) - (cond ((equal style "space") - (setq indent-tabs-mode nil)) - ((equal style "tab") - (setq indent-tabs-mode t))) - (when size - (when (featurep 'evil) - (setq-local evil-shift-width size)) - (let ((parent major-mode) - entry) - ;; Find the closet parent mode of `major-mode' in - ;; `editorconfig-indentation-alist'. - (while (and (not (setq entry (assoc parent editorconfig-indentation-alist))) - (setq parent (get parent 'derived-mode-parent)))) - (when entry - (let ((fn-or-list (cdr entry))) - (cond ((functionp fn-or-list) (funcall fn-or-list size)) - ((listp fn-or-list) - (dolist (elem fn-or-list) - (cond ((and (symbolp elem) - (editorconfig--should-set size elem)) - (set (make-local-variable elem) size)) - ((and (consp elem) - (editorconfig--should-set size (car elem))) - (let ((spec (cdr elem))) - (set (make-local-variable (car elem)) - (cond ((functionp spec) (funcall spec size)) - ((integerp spec) (* spec size)) - (t spec)))))))))))))) - -(defvar-local editorconfig--apply-coding-system-currently nil - "Used internally.") -(put 'editorconfig--apply-coding-system-currently - 'permanent-local - t) - -(defun editorconfig-merge-coding-systems (end-of-line charset) - "Return merged coding system symbol of END-OF-LINE and CHARSET." - (let ((eol (cond - ((equal end-of-line "lf") 'undecided-unix) - ((equal end-of-line "cr") 'undecided-mac) - ((equal end-of-line "crlf") 'undecided-dos) - (t 'undecided))) - (cs (cond - ((equal charset "latin1") 'iso-latin-1) - ((equal charset "utf-8") 'utf-8) - ((equal charset "utf-8-bom") 'utf-8-with-signature) - ((equal charset "utf-16be") 'utf-16be-with-signature) - ((equal charset "utf-16le") 'utf-16le-with-signature) - (t 'undecided)))) - (merge-coding-systems cs eol))) - -(cl-defun editorconfig-set-coding-system-revert (end-of-line charset) - "Set buffer coding system by END-OF-LINE and CHARSET. - -This function will revert buffer when the coding-system has been changed." - ;; `editorconfig--advice-find-file-noselect' does not use this function - (let ((coding-system (editorconfig-merge-coding-systems end-of-line - charset))) - (display-warning '(editorconfig editorconfig-set-coding-system-revert) - (format "editorconfig-set-coding-system-revert: buffer-file-name: %S | buffer-file-coding-system: %S | coding-system: %S | apply-currently: %S" - buffer-file-name - buffer-file-coding-system - coding-system - editorconfig--apply-coding-system-currently) - :debug) - (when (eq coding-system 'undecided) - (cl-return-from editorconfig-set-coding-system-revert)) - (when (and buffer-file-coding-system - (memq buffer-file-coding-system - (coding-system-aliases (merge-coding-systems coding-system - buffer-file-coding-system)))) - (cl-return-from editorconfig-set-coding-system-revert)) - (unless (file-readable-p buffer-file-name) - (set-buffer-file-coding-system coding-system) - (cl-return-from editorconfig-set-coding-system-revert)) - (unless (memq coding-system - (coding-system-aliases editorconfig--apply-coding-system-currently)) - ;; Revert functions might call editorconfig-apply again - (unwind-protect - (progn - (setq editorconfig--apply-coding-system-currently coding-system) - ;; Revert without query if buffer is not modified - (let ((revert-without-query '("."))) - (revert-buffer-with-coding-system coding-system))) - (setq editorconfig--apply-coding-system-currently nil))))) - -(defun editorconfig-set-trailing-nl (final-newline) - "Set up requiring final newline by FINAL-NEWLINE. - -This function will set `require-final-newline' and `mode-require-final-newline' -to non-nil when FINAL-NEWLINE is true." - (pcase final-newline - ("true" - ;; keep prefs around how/when the nl is added, if set - otherwise add on save - (setq-local require-final-newline (or require-final-newline t)) - (setq-local mode-require-final-newline (or mode-require-final-newline t))) - ("false" - ;; FIXME: Add functionality for actually REMOVING any trailing newlines here! - ;; (rather than just making sure we don't automagically ADD a new one) - (setq-local require-final-newline nil) - (setq-local mode-require-final-newline nil)))) - -(defun editorconfig-set-trailing-ws (trim-trailing-ws) - "Set up trimming of trailing whitespace at end of lines by TRIM-TRAILING-WS." - (make-local-variable 'write-file-functions) ;; just current buffer - (when (and (equal trim-trailing-ws "true") - (not buffer-read-only)) - ;; when true we push delete-trailing-whitespace (emacs > 21) - ;; to write-file-functions - (if editorconfig-trim-whitespaces-mode - (funcall editorconfig-trim-whitespaces-mode 1) - (add-to-list 'write-file-functions 'delete-trailing-whitespace))) - (when (or (equal trim-trailing-ws "false") - buffer-read-only) - ;; when false we remove every delete-trailing-whitespace - ;; from write-file-functions - (when editorconfig-trim-whitespaces-mode - (funcall editorconfig-trim-whitespaces-mode 0)) - (setq write-file-functions - (remove 'delete-trailing-whitespace write-file-functions)))) - -(defun editorconfig-set-line-length (length) - "Set the max line length (`fill-column') to LENGTH." - (when (and (editorconfig-string-integer-p length) - (> (string-to-number length) 0)) - (setq fill-column (string-to-number length)))) - - -(defun editorconfig--execute-editorconfig-exec (filename) - "Execute EditorConfig core with FILENAME and return output." - (if filename - (with-temp-buffer - (let ((remote (file-remote-p filename)) - (remote-localname (file-remote-p filename - 'localname))) - (display-warning '(editorconfig editorconfig--execute-editorconfig-exec) - (format "editorconfig--execute-editorconfig-exec: filename: %S | remote: %S | remote-localname: %S" - filename - remote - remote-localname) - :debug) - (if remote - (progn - (cd (concat remote "/")) - (setq filename remote-localname)) - (cd "/"))) - (display-warning '(editorconfig editorconfig--execute-editorconfig-exec) - (format "editorconfig--execute-editorconfig-exec: default-directory: %S | filename: %S" - default-directory - filename - ) - :debug) - (if (eq 0 - (process-file editorconfig-exec-path nil t nil filename)) - (buffer-string) - (editorconfig-error (buffer-string)))) - "")) - -(defun editorconfig--parse-properties (props-string) - "Create properties hash table from PROPS-STRING." - (let ((props-list (split-string props-string "\n")) - (properties (make-hash-table))) - (dolist (prop props-list properties) - (let ((key-val (split-string prop " *= *"))) - (when (> (length key-val) 1) - (let ((key (intern (car key-val))) - (val (mapconcat 'identity (cdr key-val) ""))) - (puthash key val properties))))))) - -(defun editorconfig-get-properties-from-exec (filename) - "Get EditorConfig properties of file FILENAME. - -This function uses value of `editorconfig-exec-path' to get properties." - (if (executable-find editorconfig-exec-path) - (editorconfig--parse-properties (editorconfig--execute-editorconfig-exec filename)) - (editorconfig-error "Unable to find editorconfig executable"))) - -(defun editorconfig-get-properties (filename) - "Get EditorConfig properties for file FILENAME. - -It calls `editorconfig-get-properties-from-exec' if -`editorconfig-exec-path' is found, otherwise -`editorconfig-core-get-properties-hash'." - (if (and (executable-find editorconfig-exec-path) - (not (file-remote-p filename))) - (editorconfig-get-properties-from-exec filename) - (require 'editorconfig-core) - (editorconfig-core-get-properties-hash filename))) - -(defun editorconfig-call-get-properties-function (filename) - "Call `editorconfig-get-properties-function' with FILENAME and return result. - -This function also removes `unset' properties and calls -`editorconfig-hack-properties-functions'." - (unless (functionp editorconfig-get-properties-function) - (editorconfig-error "Invalid editorconfig-get-properties-function value")) - (if (stringp filename) - (setq filename (expand-file-name filename)) - (editorconfig-error "Invalid argument: %S" filename)) - (let ((props nil)) - (condition-case err - (setq props (funcall editorconfig-get-properties-function - filename)) - (error - (editorconfig-error "Error from editorconfig-get-properties-function: %S" - err))) - (cl-loop for k being the hash-keys of props using (hash-values v) - when (equal v "unset") do (remhash k props)) - props)) - -(defun editorconfig-set-local-variables (props) - "Set buffer variables according to EditorConfig PROPS." - (editorconfig-set-indentation (gethash 'indent_style props) - (gethash 'indent_size props) - (gethash 'tab_width props)) - (editorconfig-set-trailing-nl (gethash 'insert_final_newline props)) - (editorconfig-set-trailing-ws (gethash 'trim_trailing_whitespace props)) - (editorconfig-set-line-length (gethash 'max_line_length props))) - -;;;###autoload -(defun editorconfig-apply () - "Get and apply EditorConfig properties to current buffer. - -This function does not respect the values of `editorconfig-exclude-modes' and -`editorconfig-exclude-regexps' and always applies available properties. -Use `editorconfig-mode-apply' instead to make use of these variables." - (interactive) - (when buffer-file-name - (condition-case err - (progn - (let ((props (editorconfig-call-get-properties-function buffer-file-name))) - (condition-case err - (run-hook-with-args 'editorconfig-hack-properties-functions props) - (error - (display-warning '(editorconfig editorconfig-hack-properties-functions) - (format "Error while running editorconfig-hack-properties-functions, abort running hook: %S" - err) - :warning))) - (setq editorconfig-properties-hash props) - (editorconfig-set-local-variables props) - (editorconfig-set-coding-system-revert - (gethash 'end_of_line props) - (gethash 'charset props)) - (condition-case err - (run-hook-with-args 'editorconfig-after-apply-functions props) - (error - (display-warning '(editorconfig editorconfig-after-apply-functions) - (format "Error while running editorconfig-after-apply-functions, abort running hook: %S" - err) - :warning))))) - (error - (display-warning '(editorconfig editorconfig-apply) - (format "Error in editorconfig-apply, styles will not be applied: %S" err) - :error))))) - -(defun editorconfig-mode-apply () - "Get and apply EditorConfig properties to current buffer. - -This function does nothing when the major mode is listed in -`editorconfig-exclude-modes', or variable `buffer-file-name' matches -any of regexps in `editorconfig-exclude-regexps'." - (interactive) - (when (and major-mode - (not (editorconfig--disabled-for-majormode major-mode)) - buffer-file-name - (not (editorconfig--disabled-for-filename buffer-file-name))) - (editorconfig-apply))) - -(defun editorconfig-major-mode-hook () - "Function to run when `major-mode' has been changed. - -This functions does not reload .editorconfig file, just sets local variables -again. Changing major mode can reset these variables. - -This function also executes `editorconfig-after-apply-functions' functions." - (display-warning '(editorconfig editorconfig-major-mode-hook) - (format "editorconfig-major-mode-hook: editorconfig-mode: %S, major-mode: %S, -properties-hash: %S" - (and (boundp 'editorconfig-mode) - editorconfig-mode) - major-mode - editorconfig-properties-hash) - :debug) - (when (and (bound-and-true-p editorconfig-mode) - editorconfig-properties-hash) - (editorconfig-set-local-variables editorconfig-properties-hash) - (condition-case err - (run-hook-with-args 'editorconfig-after-apply-functions editorconfig-properties-hash) - (error - (display-warning '(editorconfig editorconfig-major-mode-hook) - (format "Error while running `editorconfig-after-apply-functions': %S" - err)))))) - -(defvar editorconfig--cons-filename-codingsystem nil - "Used interally. - -`editorconfig--advice-find-file-noselect' will set this variable, and -`editorconfig--advice-insert-file-contents' will use this variable to set -`coding-system-for-read' value.") - -(defun editorconfig--advice-insert-file-contents (f filename &rest args) - "Set `coding-system-for-read'. - -This function should be added as an advice function to `insert-file-contents'. -F is that function, and FILENAME and ARGS are arguments passed to F." - ;; This function uses `editorconfig--cons-filename-codingsystem' to decide what coding-system - ;; should be used, which will be set by `editorconfig--advice-find-file-noselect'. - (display-warning '(editorconfig editorconfig--advice-insert-file-contents) - (format "editorconfig--advice-insert-file-contents: filename: %S args: %S codingsystem: %S bufferfilename: %S" - filename args - editorconfig--cons-filename-codingsystem - buffer-file-name) - :debug) - (if (and (stringp filename) - (stringp (car editorconfig--cons-filename-codingsystem)) - (string= (expand-file-name filename) - (car editorconfig--cons-filename-codingsystem)) - (cdr editorconfig--cons-filename-codingsystem) - (not (eq (cdr editorconfig--cons-filename-codingsystem) - 'undecided))) - (let ((coding-system-for-read (cdr editorconfig--cons-filename-codingsystem)) - ;; (coding-system-for-read 'undecided) - ) - (apply f filename args)) - (apply f filename args))) - -(defun editorconfig--advice-find-file-noselect (f filename &rest args) - "Get EditorConfig properties and apply them to buffer to be visited. - -This function should be added as an advice function to `find-file-noselect'. -F is that function, and FILENAME and ARGS are arguments passed to F." - (let ((props nil) - (coding-system nil) - (ret nil)) - (condition-case err - (when (and (stringp filename) - (not (editorconfig--disabled-for-filename filename))) - (setq props (editorconfig-call-get-properties-function filename)) - (setq coding-system - (editorconfig-merge-coding-systems (gethash 'end_of_line props) - (gethash 'charset props)))) - (error - (display-warning '(editorconfig editorconfig--advice-find-file-noselect) - (format "Failed to get properties, styles will not be applied: %S" - err) - :warning))) - - (let ((editorconfig--cons-filename-codingsystem (cons (expand-file-name filename) - coding-system))) - (setq ret (apply f filename args))) - - (condition-case err - (with-current-buffer ret - (when (and props - ;; filename has already been checked - (not (editorconfig--disabled-for-majormode major-mode))) - (when (and (file-remote-p filename) - (not (local-variable-p 'buffer-file-coding-system)) - (not (file-exists-p filename)) - coding-system - (not (eq coding-system - 'undecided))) - ;; When file path indicates it is a remote file and it actually - ;; does not exists, `buffer-file-coding-system' will not be set. - ;; (Seems `insert-file-contents' will not be called) - ;; For that case, explicitly set this value so that saving will be done - ;; with expected coding system. - (set-buffer-file-coding-system coding-system)) - - ;; NOTE: When using editorconfig-2-mode, hack-properties-functions cannot affect coding-system value, - ;; because it has to be set before initializing buffers. - (condition-case err - (run-hook-with-args 'editorconfig-hack-properties-functions props) - (error - (display-warning '(editorconfig editorconfig-hack-properties-functions) - (format "Error while running editorconfig-hack-properties-functions, abort running hook: %S" - err) - :warning))) - (setq editorconfig-properties-hash props) - ;; When initializing buffer, `editorconfig-major-mode-hook' - ;; will be called before setting `editorconfig-properties-hash', so - ;; execute this explicitly here. - (editorconfig-set-local-variables props) - - (condition-case err - (run-hook-with-args 'editorconfig-after-apply-functions props) - (error - (display-warning '(editorconfig editorconfig--advice-find-file-noselect) - (format "Error while running `editorconfig-after-apply-functions': %S" - err)))))) - (error - (display-warning '(editorconfig editorconfig--advice-find-file-noselect) - (format "Error while setting variables from EditorConfig: %S" err)))) - ret)) - -(defvar editorconfig--legacy-version nil - "Use legacy version of editorconfig-mode. - -As of 2021/08/30, `editorconfig-mode' uses totally new implementation by -default. This flag disables this and go back to previous version.") - -;;;###autoload -(define-minor-mode editorconfig-mode - "Toggle EditorConfig feature. - -To disable EditorConfig in some buffers, modify -`editorconfig-exclude-modes' or `editorconfig-exclude-regexps'." - :global t - :lighter editorconfig-mode-lighter - (if (not editorconfig--legacy-version) - (let ((modehooks '(prog-mode-hook - text-mode-hook - read-only-mode-hook - ;; Some modes call `kill-all-local-variables' in their init - ;; code, which clears some values set by editorconfig. - ;; For those modes, editorconfig-apply need to be called - ;; explicitly through their hooks. - rpm-spec-mode-hook))) - (if editorconfig-mode - (progn - (advice-add 'find-file-noselect :around 'editorconfig--advice-find-file-noselect) - (advice-add 'insert-file-contents :around 'editorconfig--advice-insert-file-contents) - (dolist (hook modehooks) - (add-hook hook - 'editorconfig-major-mode-hook - t))) - (advice-remove 'find-file-noselect 'editorconfig--advice-find-file-noselect) - (advice-remove 'insert-file-contents 'editorconfig--advice-insert-file-contents) - (dolist (hook modehooks) - (remove-hook hook 'editorconfig-major-mode-hook)))) - - ;; editorconfig--legacy-version is enabled - ;; See https://github.com/editorconfig/editorconfig-emacs/issues/141 for why - ;; not `after-change-major-mode-hook' - (dolist (hook '(change-major-mode-after-body-hook - read-only-mode-hook - ;; Some modes call `kill-all-local-variables' in their init - ;; code, which clears some values set by editorconfig. - ;; For those modes, editorconfig-apply need to be called - ;; explicitly through their hooks. - rpm-spec-mode-hook)) - (if editorconfig-mode - (add-hook hook 'editorconfig-mode-apply) - (remove-hook hook 'editorconfig-mode-apply))))) - - -;; Tools -;; Some useful commands for users, not required for EditorConfig to work - -;;;###autoload -(defun editorconfig-find-current-editorconfig () - "Find the closest .editorconfig file for current file." - (interactive) - (eval-and-compile (require 'editorconfig-core)) - (when-let* ((file (editorconfig-core-get-nearest-editorconfig - default-directory))) - (find-file file))) - -;;;###autoload -(defun editorconfig-display-current-properties () - "Display EditorConfig properties extracted for current buffer." - (interactive) - (if editorconfig-properties-hash - (let ((buf (get-buffer-create "*EditorConfig Properties*")) - (file buffer-file-name) - (props editorconfig-properties-hash)) - (with-current-buffer buf - (erase-buffer) - (insert (format "# EditorConfig for %s\n" file)) - (maphash (lambda (k v) - (insert (format "%S = %s\n" k v))) - props)) - (display-buffer buf)) - (message "Properties are not applied to current buffer yet.") - nil)) -;;;###autoload -(defalias 'describe-editorconfig-properties - 'editorconfig-display-current-properties) - -;;;###autoload -(defun editorconfig-format-buffer() - "Format buffer according to .editorconfig indent_style and indent_width." - (interactive) - (when (string= (gethash 'indent_style editorconfig-properties-hash) "tab") - (tabify (point-min) (point-max))) - (when (string= (gethash 'indent_style editorconfig-properties-hash) "space") - (untabify (point-min) (point-max))) - (indent-region (point-min) (point-max))) - - - -;; (defconst editorconfig--version -;; (eval-when-compile -;; (require 'lisp-mnt) -;; (declare-function lm-version "lisp-mnt" nil) -;; (lm-version)) -;; "EditorConfig version.") - -(declare-function find-library-name "find-func" (library)) -(declare-function lm-version "lisp-mnt" nil) - -;;;###autoload -(defun editorconfig-version (&optional show-version) - "Get EditorConfig version as string. - -If called interactively or if SHOW-VERSION is non-nil, show the -version in the echo area and the messages buffer." - (interactive (list t)) - (let* ((version (with-temp-buffer - (require 'find-func) - (insert-file-contents (find-library-name "editorconfig")) - (require 'lisp-mnt) - (lm-version))) - (pkg (and (eval-and-compile (require 'package nil t)) - (cadr (assq 'editorconfig - package-alist)))) - (pkg-version (and pkg - (package-version-join (package-desc-version pkg)))) - (version-full (if (and pkg-version - (not (string= version pkg-version))) - (concat version "-" pkg-version) - version))) - (when show-version - (message "EditorConfig Emacs v%s" version-full)) - version-full)) - -(provide 'editorconfig) -;;; editorconfig.el ends here - -;; Local Variables: -;; sentence-end-double-space: t -;; End: blob - d0b45788afb87d8b79a105d77afbd5fea9b91f2f (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1/editorconfig.info +++ /dev/null @@ -1,353 +0,0 @@ -This is doc31Axbt.info, produced by makeinfo version 6.7 from -editorconfig.texi. - -INFO-DIR-SECTION Emacs -START-INFO-DIR-ENTRY -* EditorConfig: (editorconfig). EditorConfig Emacs Plugin. -END-INFO-DIR-ENTRY - - -File: doc31Axbt.info, Node: Top, Next: Getting Started, Up: (dir) - -1 EditorConfig Emacs Plugin -*************************** - -This is an EditorConfig (https://editorconfig.org) plugin for Emacs -(https://www.gnu.org/software/emacs/). - -* Menu: - -* Getting Started:: -* Supported properties:: -* Customize:: -* Troubleshooting:: -* Submitting Bugs and Feature Requests:: -* License:: - - -File: doc31Axbt.info, Node: Getting Started, Next: Supported properties, Prev: Top, Up: Top - -1.1 Getting Started -=================== - -* Menu: - -* packageel:: -* use-package:: -* Manual installation:: - - -File: doc31Axbt.info, Node: packageel, Next: use-package, Up: Getting Started - -1.1.1 package.el ----------------- - -This package is available from MELPA (https://melpa.org/#/editorconfig), -MELPA Stable (https://stable.melpa.org/#/editorconfig) and NonGNU ELPA -(http://elpa.nongnu.org/nongnu/editorconfig.html). Install from these -repositories and enable global minor-mode 'editorconfig-mode': - -(editorconfig-mode 1) - - Normally, enabling 'editorconfig-mode' should be enough for this -plugin to work: all other configurations are optional. This mode sets -up hooks so that EditorConfig properties will be loaded and applied to -the new buffers automatically when visiting files. - - -File: doc31Axbt.info, Node: use-package, Next: Manual installation, Prev: packageel, Up: Getting Started - -1.1.2 use-package ------------------ - -If you use *use-package* (https://www.emacswiki.org/emacs/UsePackage), -add the following to your 'init.el' file: - -(use-package editorconfig - :ensure t - :config - (editorconfig-mode 1)) - - -File: doc31Axbt.info, Node: Manual installation, Prev: use-package, Up: Getting Started - -1.1.3 Manual installation -------------------------- - -Copy all '.el' files in this repository to '~/.emacs.d/lisp' and add the -following: - -(add-to-list 'load-path "~/.emacs.d/lisp") -(require 'editorconfig) -(editorconfig-mode 1) - - -File: doc31Axbt.info, Node: Supported properties, Next: Customize, Prev: Getting Started, Up: Top - -1.2 Supported properties -======================== - -Current Emacs plugin coverage for EditorConfig's properties -(https://editorconfig.org/#supported-properties): - - * 'indent_style' - * 'indent_size' - * 'tab_width' - * 'end_of_line' - * 'charset' - * 'trim_trailing_whitespace' - * 'insert_final_newline = true' is supported - * 'insert_final_newline = false' is not enforced (as in trailing - newlines actually being removed automagically), we just - buffer-locally override any preferences that would auto-add them to - files '.editorconfig' marks as trailing-newline-free - * 'max_line_length' - * 'file_type_ext' (Experimental) (See below) - * 'file_type_emacs' (Experimental) (See below) - * 'root' (only used by EditorConfig core) - - Not yet covered properties marked with over-strike - pull requests -implementing missing features warmly welcomed! Typically, you will want -to tie these to native functionality, or the configuration of existing -packages handling the feature. - - As several packages have their own handling of, say, indentation, we -might not yet cover some mode you use, but we try to add the ones that -show up on our radar. - -* Menu: - -* File Type file_type_ext file_type_emacs:: - - -File: doc31Axbt.info, Node: File Type file_type_ext file_type_emacs, Up: Supported properties - -1.2.1 File Type (file_type_ext, file_type_emacs) ------------------------------------------------- - -File-type feature is currently disabled, because this package is now -undergoing big internal refactoring. For those who want this -functionality, please consider using editorconfig-custom-majormode -(https://github.com/10sr/editorconfig-custom-majormode-el). - - -File: doc31Axbt.info, Node: Customize, Next: Troubleshooting, Prev: Supported properties, Up: Top - -1.3 Customize -============= - -'editorconfig-emacs' provides some customize variables. - - Here are some of these variables: for the full list of available -variables, type M-x customize-group [RET] editorconfig [RET]. - -* Menu: - -* editorconfig-trim-whitespaces-mode:: -* editorconfig-after-apply-functions:: -* editorconfig-hack-properties-functions:: - - -File: doc31Axbt.info, Node: editorconfig-trim-whitespaces-mode, Next: editorconfig-after-apply-functions, Up: Customize - -1.3.1 'editorconfig-trim-whitespaces-mode' ------------------------------------------- - -Buffer local minor-mode to use to trim trailing whitespaces. - - If set, editorconfig will enable/disable this mode in accord with -'trim_trailing_whitespace' property in '.editorconfig'. Otherwise, use -Emacs built-in 'delete-trailing-whitespace' function. - - One possible value is 'ws-butler-mode' -(https://github.com/lewang/ws-butler), with which only lines touched get -trimmed. To use it, add following to your init.el: - -(setq editorconfig-trim-whitespaces-mode - 'ws-butler-mode) - - -File: doc31Axbt.info, Node: editorconfig-after-apply-functions, Next: editorconfig-hack-properties-functions, Prev: editorconfig-trim-whitespaces-mode, Up: Customize - -1.3.2 'editorconfig-after-apply-functions' ------------------------------------------- - -(Formerly 'editorconfig-custom-hooks') - - A list of functions which will be called after loading common -EditorConfig settings, when you can set some custom variables. - - For example, 'web-mode' has several variables for indentation offset -size and EditorConfig sets them at once by 'indent_size'. You can stop -indenting only blocks of 'web-mode' by adding following to your init.el: - -(add-hook 'editorconfig-after-apply-functions - (lambda (props) (setq web-mode-block-padding 0))) - - -File: doc31Axbt.info, Node: editorconfig-hack-properties-functions, Prev: editorconfig-after-apply-functions, Up: Customize - -1.3.3 'editorconfig-hack-properties-functions' ----------------------------------------------- - -A list of functions to alter property values before applying them. - - These functions will be run after loading ".editorconfig" files and -before applying them to current buffer, so that you can alter some -properties from ".editorconfig" before they take effect. - - For example, Makefile files always use tab characters for -indentation: you can overwrite "indent_style" property when current -'major-mode' is 'makefile-mode': - -(add-hook 'editorconfig-hack-properties-functions - '(lambda (props) - (when (derived-mode-p 'makefile-mode) - (puthash 'indent_style "tab" props)))) - - -File: doc31Axbt.info, Node: Troubleshooting, Next: Submitting Bugs and Feature Requests, Prev: Customize, Up: Top - -1.4 Troubleshooting -=================== - -Enabling 'editorconfig-mode' should be enough for normal cases. - - When EditorConfig properties are not effective for unknown reason, we -recommend first trying 'M-x editorconfig-display-current-properties'. - - This command will open a new buffer and display the EditorConfig -properties loaded for current buffer. You can check if EditorConfig -properties were not read for buffers at all, or they were loaded but did -not take effect for some other reasons. - -* Menu: - -* Indentation for new major-modes:: -* Not work at all for FOO-mode!:: - - -File: doc31Axbt.info, Node: Indentation for new major-modes, Next: Not work at all for FOO-mode!, Up: Troubleshooting - -1.4.1 Indentation for new major-modes -------------------------------------- - -Because most Emacs major-modes have their own indentation settings, this -plugin requires explicit support for each major-mode for 'indent_size' -property. - - By default this plugin ships with settings for many major-modes, but, -sorry to say, it cannot be perfect. Especially it is difficult to -support brand-new major-modes. Please feel free to submit issue or -pull-request for such major-mode! - - Supported major-modes and their indentation configs are defined in -the variable 'editorconfig-indentation-alist'. - - -File: doc31Axbt.info, Node: Not work at all for FOO-mode!, Prev: Indentation for new major-modes, Up: Troubleshooting - -1.4.2 Not work at all for FOO-mode! ------------------------------------ - -Most cases properties are loaded just after visiting files when -'editorconfig-mode' is enabled. But it is known that there are -major-modes that this mechanism does not work for and require explicit -call of 'editorconfig-apply'. - - Typically it will occur when the major-mode is not defined using -'define-derived-mode' ('rpm-spec-mode' is an example for this). Please -feel free to submit issues if you find such modes! - - -File: doc31Axbt.info, Node: Submitting Bugs and Feature Requests, Next: License, Prev: Troubleshooting, Up: Top - -1.5 Submitting Bugs and Feature Requests -======================================== - -Bugs, feature requests, and other issues should be submitted to the -issue tracker: https://github.com/editorconfig/editorconfig-emacs/issues - -* Menu: - -* Development:: - - -File: doc31Axbt.info, Node: Development, Up: Submitting Bugs and Feature Requests - -1.5.1 Development ------------------ - -Make and CMake (https://cmake.org) must be installed to run the tests -locally: - -$ make check - - To start a new Emacs process with current '*.el' and without loading -user init file, run: - -$ make sandbox - - -File: doc31Axbt.info, Node: License, Prev: Submitting Bugs and Feature Requests, Up: Top - -1.6 License -=========== - -EditorConfig Emacs Plugin 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 . - - - -Tag Table: -Node: Top205 -Ref: #editorconfig-emacs-plugin334 -Node: Getting Started584 -Ref: #getting-started724 -Node: packageel788 -Ref: #package.el907 -Node: use-package1471 -Ref: #use-package1620 -Node: Manual installation1808 -Ref: #manual-installation1955 -Node: Supported properties2130 -Ref: #supported-properties2286 -Node: File Type file_type_ext file_type_emacs3460 -Ref: #file-type-file_type_ext-file_type_emacs3658 -Node: Customize3917 -Ref: #customize4051 -Node: editorconfig-trim-whitespaces-mode4370 -Ref: #editorconfig-trim-whitespaces-mode4583 -Node: editorconfig-after-apply-functions5074 -Ref: #editorconfig-after-apply-functions5334 -Node: editorconfig-hack-properties-functions5820 -Ref: #editorconfig-hack-properties-functions6045 -Node: Troubleshooting6655 -Ref: #troubleshooting6817 -Node: Indentation for new major-modes7357 -Ref: #indentation-for-new-major-modes7558 -Node: Not work at all for FOO-mode!8074 -Ref: #not-work-at-all-for-foo-mode8271 -Node: Submitting Bugs and Feature Requests8693 -Ref: #submitting-bugs-and-feature-requests8895 -Node: Development9063 -Ref: #development9187 -Node: License9391 -Ref: #license9511 - -End Tag Table - - -Local Variables: -coding: utf-8 -End: blob - /dev/null blob + ec5097cf687443f2c5f19c3cd9069996692f0464 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/.editorconfig @@ -0,0 +1,29 @@ +root = true + +[*] +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true + + +[*.el] +indent_style = space +max_line_length = 80 + + +[Makefile] +indent_style = tab +tab_width = 4 + + +[.gitmodules] +indent_style = tab +tab_width = 4 + + +[*.yml] +indent_style = space +indent_size = 2 + +[ert-tests/**/*.ini] +file_type_ext = editorconfig blob - /dev/null blob + b21179911cc6ec39f5247fdf19290451945da4a6 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/.github/workflows/build.yaml @@ -0,0 +1,71 @@ +name: build + +on: + push: + branches: + - master + pull_request: + types: [opened, synchronize] + branches: + - 'master' + release: + types: [published] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + emacs_version: + - "26.3" + - "27.2" + - "28.2" + - "29.3" + experimental: [false] + include: + - os: ubuntu-latest + emacs_version: snapshot + experimental: true + - os: macos-latest + emacs_version: snapshot + experimental: true + # 2023/8/2 Recently this test always fails + # so remove this until it works + # - os: windows-latest + # emacs_version: snapshot + # experimental: true + exclude: + - os: macos-latest + emacs_version: "26.3" + - os: macos-latest + emacs_version: "27.2" + continue-on-error: ${{ matrix.experimental }} + + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + submodules: recursive + + - name: Setup Emacs + uses: jcs090218/setup-emacs@master + with: + version: ${{ matrix.emacs_version }} + + - uses: emacs-eask/setup-eask@master + with: + version: 'snapshot' + + - name: Run tests (Unix) + if: matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' + run: make check-unix + + - name: Run tests (Windows) + if: matrix.os == 'windows-latest' + run: make check-dos blob - /dev/null blob + 08cd608de82ba5cf12f8cb05e25b5538bd1b9372 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/CHANGELOG.md @@ -0,0 +1,416 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + + +## [Unreleased] + +### Added + +### Changed + +### Deprecated + +### Removed + +### Fixed + +### Security + + +## [0.11.0] - 2024-05-11 + +### Added + +- Support new major-modes + - kotlin-ts-mode ([#310]) + - hcl-mode ([#312]) + - protobuf-mode ([#316]) + - lua-ts-mode ([#317]) + - php-ts-mode ([#318]) + - elixir-ts-mode ([#319]) + - jsonian-mode ([#320]) + - swift-mode ([#321]) + - graphql-mode ([#325]) + - zig-mode ([#326]) + +### Changed + +- Remove editorconfig-mode legacy version ([#304]) + - Remove flag `editorconfig--legacy-version`, which was defined in [#263] +- Separate some utility commands to new file ([#330]) + - Following commands are now defined in `editoroconfig-tools.el`, not `editorconfig.el` + - editorconfig-apply + - editorconfig-mode-apply + - editorconfig-find-current-editorconfig + - editorconfig-display-current-properties (and its alias describe-editorconfig-properties) + - editorconfig-format-buffer + - These commands are configured to be autoloaded functions, except for `editorconfig-mode-apply` + + +## [0.10.1] - 2023-05-19 + +### Fixed + +- Fix when-let (again) ([#305]) +- Fix compile warning of python-mode offset ([#306]) + + +## [0.10.0] - 2023-05-07 + +### Added + +- Enable indentation for tree-sitter based typescript mode ([#282]) +- Add support for json-ts-mode ([#283]) +- Add support for some treesit modes ([#287]) +- Add indent variable associations for numerous tree-sitter modes ([#290]) +- Add js-ts-mode' spec to editorconfig-indentation-alist' ([#293]) +- Add bash-ts-mode to editorconfig-indentation-alist ([#296]) +- Add support for gdscript-mode ([#300]) + +### Changed + +- Drop Emacs 24.x and 25.x ([#286]) + +### Fixed + +- Fix write-file-functions default value ([#295]) +- Check mode-class property for special modes ([#301]) +- Load subr-x when compiling ([#302]) + + +## [0.9.1] - 2022-11-07 + +### Fixed + +- Check filename rather than buffer-file-name for consistency ([#280]) + + +## [0.9.0] - 2022-10-23 + +### Changed + +- Use new implementation by default ([#263]) + - Set `(setq editorconfig--legacy-version t)` to use previous one + + +## [0.8.2] - 2021-08-13 + +### Added + +- Add rustic-mode to editorconfig-indentation-alist ([#208]) +- Add conf-mode abbrev-table definitions ([#220]) +- Add meson-mode indentation rule ([#253]) +- Add support for rjsx-mode ([#254]) +- Update README for NonGNU ELPA repository ([#259]) +- Add new implementation of editorconfig-mode ([#248], [#250], [#251], [#255], [#258], [#260]) + - By default this is disabled: set `(setq editorconfig--enable-20210221-testing t)` to use this + +### Fixed + +- Fix so that "?" does not match "/" ([#211]) +- Fix document typo ([#213]) +- Don't make unchanged vars buffer-local ([#222]) +- Silence byte-compiler warnings ([#235]) +- Use revert-buffer-with-coding-system to set coding system ([#236]) +- Do not run editorconfig-apply on recentf-save-file ([#241]) +- Skip special-mode buffers when applying ([#247]) +- Stop excluding remote files by default ([#234], [#245]) +- Fix editorconfig execution for remote hosts via tramp ([#249]) +- Add minor fixes to tests ([#252]) +- Fix excluding the recentf-save-file when in a symlinked directory ([#256]) + +### Changed + +- Define -mode-apply as an interactive command ([#216]) +- Use elisp core by default ([#209]) +- User functions in the hooks `editorconfig-hack-properties-functions` and + `editorconfig-after-apply-functions` can no longer distinguish explicitly + unset properties from ones that were never set in the first place. ([#222]) + + +## [0.8.1] - 2019-10-10 + +### Added + +- Add indentation support + - [#196] + - enh-ruby-mode + - haxor-mode + - mips-mode + - nasm-mode + - terra-mode + - kotlin-mode + - bpftrace-mode ([#199]) + - f90-mode ([#200]) +- Add explicit support for rpm-spec-mode ([efc1ff4], see [#197] ) +- Add whitelist for file_type_emacs value ([#204]) + + +## [0.8.0] - 2019-03-26 + +### Fixed + +- Allow library forget properties order ([#187]) +- Use API to get version info ([#193]) + - `editorconfig-version()` was added and `editorconfig-core-version` removed +- Update docs and metadata to follow MELPA guidelines ([#189]) +- Refactor ([#188], [#191]) + + +## [0.7.14] - 2018-12-25 + +### Added + +- Add feature to decide major-mode from file_type_ext [EXPERIMENTAL] [#175] ([#178]) ([#179]) ([#180]) +- Add feature to hack properties before applying [#182] +- Add variable editorconfig-trim-whitespaces-mode [#183] + - Useful when you want to use non-default mode like `ws-butler` to trim spaces + +### Fixed + +- Make conf-mode used when a file has .editorconfig extension [01a0640] +- Fix tests +- Fix docs + +### Changed + +- Change hook name -custom-hooks -> -after-apply-functions [bb4bc44] + + +## [0.7.13] - 2018-08-23 + +### Fixed + +- Check editorconfig configs when read only state changes ([#168]) +- use CURDIR instead of PWD in Makefile ([#170]) +- Refactor fnmatch-p ([#171]) +- Update tests + + +## [0.7.12] - 2018-06-20 + +### Added + +- Add /Fix major-mode support + - pug-mode [#149] + - csharp-mode [#154] +- Add variable to disable lisp-indent-offset sometimes [#155] +- Add texinfo doc [#159] + +### Fixed + +- Avoid passing a non-absolute file path to editorconfig(1) [#151] +- Use "-with-signature" coding systems for all UTF-16 charsets [#158] +- Allow normal whitespace when reading EditorConfig settings file [#162] +- Add some fixes to tests + + +## [0.7.11] - 2017-11-07 + +### Added + +- Add /Fix major-mode support + - apache-mode + - groovy-mode + - web-mode +- Add support for a custom lighter +- Add editorconfig-format-buffer function +- Add experimental file_type_emacs support + +### Changed + +- Change hook editorconfig is applied on + + +## [0.7.10] - 2017-06-07 + +*Undocumented* + +## [0.7.9] - 2017-02-22 + +*Undocumented* + +## [0.7.8] - 2016-08-09 + +*Undocumented* + +## [0.7.7] - 2016-07-19 + +*Undocumented* + +## [0.7.6] - 2016-05-05 + +*Undocumented* + +## [0.7.5] - 2016-04-22 + +*Undocumented* + +## [0.7.4] - 2016-03-31 + +*Undocumented* + +## [0.7.3] - 2016-02-12 + +*Undocumented* + +## [0.7.2] - 2016-01-27 + +*Undocumented* + +## [0.7.1] - 2016-01-24 + +*Undocumented* + +## [0.7.0] - 2016-01-17 + +*Undocumented* + +## [0.6.2] - 2016-01-15 + +*Undocumented* + +## [0.6.1] - 2015-12-09 + +*Undocumented* + +## [0.6] - 2015-12-04 + +*Undocumented* + +## [0.5] - 2015-11-04 + +*Undocumented* + +## [0.4] - 2014-12-20 + +*Undocumented* + +## [0.3] - 2014-05-07 + +*Undocumented* + +## [0.2] - 2013-06-06 + +*Undocumented* + +## [0.1] - 2012-02-07 + +*Undocumented* + + +[Unreleased]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.11.0...HEAD +[0.11.0]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.10.1...v0.11.0 +[0.10.1]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.10.0...v0.10.1 +[0.10.0]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.9.1...v0.10.0 +[0.9.1]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.9.0...v0.9.1 +[0.9.0]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.8.2...v0.9.0 +[0.8.2]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.8.1...v0.8.2 +[0.8.1]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.8.0...v0.8.1 +[0.8.0]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.14...v0.8.0 +[0.7.14]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.13...v0.7.14 +[0.7.13]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.12...v0.7.13 +[0.7.12]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.11...v0.7.12 +[0.7.11]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.10...v0.7.11 +[0.7.10]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.9...v0.7.10 +[0.7.9]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.8...v0.7.9 +[0.7.8]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.7...v0.7.8 +[0.7.7]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.6...v0.7.7 +[0.7.6]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.5...v0.7.6 +[0.7.5]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.4...v0.7.5 +[0.7.4]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.3...v0.7.4 +[0.7.3]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.2...v0.7.3 +[0.7.2]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.1...v0.7.2 +[0.7.1]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.7.0...v0.7.1 +[0.7.0]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.6.2...v0.7.0 +[0.6.2]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.6.1...v0.6.2 +[0.6.1]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.6...v0.6.1 +[0.6]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.5...v0.6 +[0.5]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.4...v0.5 +[0.4]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.3...v0.4 +[0.3]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.2...v0.3 +[0.2]: https://github.com/editorconfig/editorconfig-emacs/compare/v0.1...v0.2 +[0.1]: https://github.com/editorconfig/editorconfig-emacs/releases/tag/v0.1 +[#330]: https://github.com/editorconfig/editorconfig-emacs/issues/330 +[#326]: https://github.com/editorconfig/editorconfig-emacs/issues/326 +[#325]: https://github.com/editorconfig/editorconfig-emacs/issues/325 +[#321]: https://github.com/editorconfig/editorconfig-emacs/issues/321 +[#320]: https://github.com/editorconfig/editorconfig-emacs/issues/320 +[#319]: https://github.com/editorconfig/editorconfig-emacs/issues/319 +[#318]: https://github.com/editorconfig/editorconfig-emacs/issues/318 +[#317]: https://github.com/editorconfig/editorconfig-emacs/issues/317 +[#316]: https://github.com/editorconfig/editorconfig-emacs/issues/316 +[#312]: https://github.com/editorconfig/editorconfig-emacs/issues/312 +[#310]: https://github.com/editorconfig/editorconfig-emacs/issues/310 +[#304]: https://github.com/editorconfig/editorconfig-emacs/issues/304 +[#306]: https://github.com/editorconfig/editorconfig-emacs/issues/306 +[#305]: https://github.com/editorconfig/editorconfig-emacs/issues/305 +[#302]: https://github.com/editorconfig/editorconfig-emacs/issues/302 +[#301]: https://github.com/editorconfig/editorconfig-emacs/issues/301 +[#300]: https://github.com/editorconfig/editorconfig-emacs/issues/300 +[#296]: https://github.com/editorconfig/editorconfig-emacs/issues/296 +[#295]: https://github.com/editorconfig/editorconfig-emacs/issues/295 +[#293]: https://github.com/editorconfig/editorconfig-emacs/issues/293 +[#290]: https://github.com/editorconfig/editorconfig-emacs/issues/290 +[#287]: https://github.com/editorconfig/editorconfig-emacs/issues/287 +[#286]: https://github.com/editorconfig/editorconfig-emacs/issues/286 +[#283]: https://github.com/editorconfig/editorconfig-emacs/issues/283 +[#282]: https://github.com/editorconfig/editorconfig-emacs/issues/282 +[#280]: https://github.com/editorconfig/editorconfig-emacs/issues/280 +[#263]: https://github.com/editorconfig/editorconfig-emacs/issues/263 +[#260]: https://github.com/editorconfig/editorconfig-emacs/issues/260 +[#258]: https://github.com/editorconfig/editorconfig-emacs/issues/258 +[#255]: https://github.com/editorconfig/editorconfig-emacs/issues/255 +[#251]: https://github.com/editorconfig/editorconfig-emacs/issues/251 +[#250]: https://github.com/editorconfig/editorconfig-emacs/issues/250 +[#248]: https://github.com/editorconfig/editorconfig-emacs/issues/248 +[#259]: https://github.com/editorconfig/editorconfig-emacs/issues/259 +[#256]: https://github.com/editorconfig/editorconfig-emacs/issues/256 +[#252]: https://github.com/editorconfig/editorconfig-emacs/issues/252 +[#249]: https://github.com/editorconfig/editorconfig-emacs/issues/249 +[#245]: https://github.com/editorconfig/editorconfig-emacs/issues/245 +[#234]: https://github.com/editorconfig/editorconfig-emacs/issues/234 +[#241]: https://github.com/editorconfig/editorconfig-emacs/issues/241 +[#236]: https://github.com/editorconfig/editorconfig-emacs/issues/236 +[#235]: https://github.com/editorconfig/editorconfig-emacs/issues/235 +[#222]: https://github.com/editorconfig/editorconfig-emacs/issues/222 +[#222]: https://github.com/editorconfig/editorconfig-emacs/issues/222 +[#220]: https://github.com/editorconfig/editorconfig-emacs/issues/220 +[#216]: https://github.com/editorconfig/editorconfig-emacs/issues/216 +[#213]: https://github.com/editorconfig/editorconfig-emacs/issues/213 +[#211]: https://github.com/editorconfig/editorconfig-emacs/issues/211 +[#209]: https://github.com/editorconfig/editorconfig-emacs/issues/209 +[#208]: https://github.com/editorconfig/editorconfig-emacs/issues/208 +[#204]: https://github.com/editorconfig/editorconfig-emacs/issues/204 +[#200]: https://github.com/editorconfig/editorconfig-emacs/issues/200 +[#199]: https://github.com/editorconfig/editorconfig-emacs/issues/199 +[#197]: https://github.com/editorconfig/editorconfig-emacs/issues/197 +[#196]: https://github.com/editorconfig/editorconfig-emacs/issues/196 +[#193]: https://github.com/editorconfig/editorconfig-emacs/issues/193 +[#191]: https://github.com/editorconfig/editorconfig-emacs/issues/191 +[#189]: https://github.com/editorconfig/editorconfig-emacs/issues/189 +[#188]: https://github.com/editorconfig/editorconfig-emacs/issues/188 +[#187]: https://github.com/editorconfig/editorconfig-emacs/issues/187 +[#183]: https://github.com/editorconfig/editorconfig-emacs/issues/183 +[#182]: https://github.com/editorconfig/editorconfig-emacs/issues/182 +[#180]: https://github.com/editorconfig/editorconfig-emacs/issues/180 +[#179]: https://github.com/editorconfig/editorconfig-emacs/issues/179 +[#178]: https://github.com/editorconfig/editorconfig-emacs/issues/178 +[#175]: https://github.com/editorconfig/editorconfig-emacs/issues/175 +[#171]: https://github.com/editorconfig/editorconfig-emacs/issues/171 +[#170]: https://github.com/editorconfig/editorconfig-emacs/issues/170 +[#168]: https://github.com/editorconfig/editorconfig-emacs/issues/168 +[#162]: https://github.com/editorconfig/editorconfig-emacs/issues/162 +[#159]: https://github.com/editorconfig/editorconfig-emacs/issues/159 +[#158]: https://github.com/editorconfig/editorconfig-emacs/issues/158 +[#155]: https://github.com/editorconfig/editorconfig-emacs/issues/155 +[#154]: https://github.com/editorconfig/editorconfig-emacs/issues/154 +[#151]: https://github.com/editorconfig/editorconfig-emacs/issues/151 +[#149]: https://github.com/editorconfig/editorconfig-emacs/issues/149 +[01a0640]: https://github.com/editorconfig/editorconfig-emacs/commit/01a064015ed8d00f2853f966f07d2be5b97bfe5e +[efc1ff4]: https://github.com/editorconfig/editorconfig-emacs/commit/efc1ff4b1c3422d6e231b1c01138becab4b9eded +[bb4bc44]: https://github.com/editorconfig/editorconfig-emacs/commit/bb4bc4497783e6607480cd0b761f974136784fdd blob - /dev/null blob + 492a943f829f92a53ea9b6c3404ca14b5cbadbab (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/CONTRIBUTORS @@ -0,0 +1,11 @@ +Contributors to EditorConfig Emacs plugin (chronological order): + +Trey Hunner +Jonas Bernoulli +Johan Sundström +Desmond O. Chang +Steve Jordan +Hong Xu +10sr +Usami Kenta +Izaak "Zaak" Beekman blob - /dev/null blob + 3ac3e5f297c66b9b52bff3b934dbad55ef636080 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/Eask @@ -0,0 +1,20 @@ +(package "editorconfig" + "0.11.0" + "EditorConfig Emacs Plugin") + +(website-url "https://github.com/editorconfig/editorconfig-emacs#readme") +(keywords "convenience" "editorconfig") + +(package-file "editorconfig.el") + +(files "editorconfig-*.el") + +(script "test" "echo \"Error: no test specified\" && exit 1") + +(source "gnu") +(source "melpa") + +(depends-on "emacs" "26.1") +(depends-on "nadvice") + +(setq network-security-level 'low) ; see https://github.com/jcs090218/setup-emacs-windows/issues/156#issuecomment-932956432 blob - /dev/null blob + 963f6de2ede09db7ebfd92aa4d443960ee878692 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/Makefile @@ -0,0 +1,81 @@ +# -*- Makefile -*- + +TEXI_CHAPTER := EditorConfig Emacs Plugin + +EMACS = emacs +EASK = eask +PANDOC = pandoc +AWK = awk + +PROJECT_ROOT_DIR = $(CURDIR) +ERT_TESTS = $(wildcard $(PROJECT_ROOT_DIR)/ert-tests/*.el) + +# Compile with noninteractive and relatively clean environment. +BATCHFLAGS = -batch -q --no-site-file -L $(PROJECT_ROOT_DIR) + +MAIN_SRC = editorconfig.el +SRCS = $(wildcard $(PROJECT_ROOT_DIR)/*.el) +OBJS = $(SRCS:.el=.elc) + +.PHONY: check-unix check-dos \ + compile clean \ + test test-ert test-core \ + sandbox doc + +# CI entry +check-unix: package install compile test +check-dos: package install compile test-ert + +package: + $(EASK) package + +install: + $(EASK) install + +compile: + $(EASK) compile + +clean: + $(EASK) clean elc + + +doc: doc/editorconfig.texi + +doc/editorconfig.texi: README.md doc/header.txt + mkdir -p doc + tail -n +4 $< | $(PANDOC) -s -f markdown -t texinfo -o $@.body + $(AWK) 'f{print} /^@chapter $(TEXI_CHAPTER)/{f=1;print}' $@.body >$@.body2 + cat doc/header.txt $@.body2 >$@ + rm -f $@.body $@.body2 + +test: test-ert test-core + $(EMACS) $(BATCHFLAGS) -l editorconfig.el + + +# ert test +test-ert: $(ERT_TESTS) $(OBJS) + $(EMACS) $(BATCHFLAGS) \ + --eval "(setq debug-on-error t)" \ + --eval "(require 'ert)" \ + --eval "(setq metadata-el-files '($(MAIN_SRC:%=\"%\")))" \ + $(ERT_TESTS:%=-l "%") \ + -f ert-run-tests-batch-and-exit + + + +# Core test +core-test/CMakeLists.txt: + git submodule init + +test-core: core-test/CMakeLists.txt $(OBJS) + cd $(PROJECT_ROOT_DIR)/core-test && \ + cmake -DEDITORCONFIG_CMD="$(PROJECT_ROOT_DIR)/bin/editorconfig-el" . + cd $(PROJECT_ROOT_DIR)/core-test && \ + EMACS_BIN=$(EMACS) EDITORCONFIG_CORE_LIBRARY_PATH="$(PROJECT_ROOT_DIR)" \ + ctest --output-on-failure . + + +# Start Emacs that loads *.el in current directory and does not load the user +# init file +sandbox: + $(EMACS) -q -L $(PROJECT_ROOT_DIR) $(MAIN_SRC:%=-l "%") blob - /dev/null blob + 6ace73f6fe81a0db5d05dd1179d95cd69bdb6617 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/README.md @@ -0,0 +1,224 @@ +![build](https://github.com/editorconfig/editorconfig-emacs/workflows/build/badge.svg) +[![MELPA](https://melpa.org/packages/editorconfig-badge.svg)](http://melpa.org/#/editorconfig) +[![MELPA Stable](https://stable.melpa.org/packages/editorconfig-badge.svg)](https://stable.melpa.org/#/editorconfig) +[![NonGNU ELPA](http://elpa.nongnu.org/nongnu/editorconfig.svg)](http://elpa.nongnu.org/nongnu/editorconfig.html) + +# EditorConfig Emacs Plugin + +This is an [EditorConfig][] plugin for [Emacs][]. + +## Getting Started + +### package.el + +This package is available from [MELPA][], [MELPA Stable][] and [NonGNU ELPA][]. +Install from these repositories and enable global minor-mode `editorconfig-mode`: + +```emacs-lisp +(editorconfig-mode 1) +``` + +Normally, enabling `editorconfig-mode` should be enough for this plugin to work: +all other configurations are optional. +This mode sets up hooks so that EditorConfig properties will be +loaded and applied to the new buffers automatically when visiting files. + +### use-package + +If you use [**use-package**][use-package], add the following to your +`init.el` file: + +```emacs-lisp +(use-package editorconfig + :ensure t + :config + (editorconfig-mode 1)) +``` + +### Manual installation + +Copy all `.el` files in this repository to `~/.emacs.d/lisp` and add the +following: + +```emacs-lisp +(add-to-list 'load-path "~/.emacs.d/lisp") +(require 'editorconfig) +(editorconfig-mode 1) +``` + +## Supported properties + +Current Emacs plugin coverage for EditorConfig's [properties][]: + +* `indent_style` +* `indent_size` +* `tab_width` +* `end_of_line` +* `charset` +* `trim_trailing_whitespace` +* `insert_final_newline = true` is supported +* ~~`insert_final_newline = false`~~ is not enforced + (as in trailing newlines actually being removed automagically), + we just buffer-locally override any preferences that would auto-add them + to files `.editorconfig` marks as trailing-newline-free +* `max_line_length` +* ~~`file_type_ext` (Experimental)~~ (See below) +* ~~`file_type_emacs` (Experimental)~~ (See below) +* `root` (only used by EditorConfig core) + +Not yet covered properties marked with ~~over-strike~~ +– pull requests implementing missing features warmly welcomed! +Typically, you will want to tie these to native functionality, +or the configuration of existing packages handling the feature. + +As several packages have their own handling of, say, indentation, +we might not yet cover some mode you use, but we try to add the +ones that show up on our radar. + +### ~~File Type (file_type_ext, file_type_emacs)~~ + +File-type feature is currently disabled, because this package is now undergoing +big internal refactoring. +For those who want this functionality, +please consider using [editorconfig-custom-majormode](https://github.com/10sr/editorconfig-custom-majormode-el). + +## Customize + +`editorconfig-emacs` provides some customize variables. + +Here are some of these variables: for the full list of available variables, +type M-x customize-group [RET] editorconfig [RET]. + +### `editorconfig-trim-whitespaces-mode` + +Buffer local minor-mode to use to trim trailing whitespaces. + +If set, editorconfig will enable/disable this mode in accord with +`trim_trailing_whitespace` property in `.editorconfig`. +Otherwise, use Emacs built-in `delete-trailing-whitespace` function. + +One possible value is +[`ws-butler-mode`](https://github.com/lewang/ws-butler), with which +only lines touched get trimmed. To use it, add following to your +init.el: + +``` emacs-lisp +(setq editorconfig-trim-whitespaces-mode + 'ws-butler-mode) +``` + +### `editorconfig-after-apply-functions` + +(Formerly `editorconfig-custom-hooks`) + +A list of functions which will be called after loading common EditorConfig settings, +when you can set some custom variables. + +For example, `web-mode` has several variables for indentation offset size and +EditorConfig sets them at once by `indent_size`. You can stop indenting +only blocks of `web-mode` by adding following to your init.el: + +```emacs-lisp +(add-hook 'editorconfig-after-apply-functions + (lambda (props) (setq web-mode-block-padding 0))) +``` + +## Troubleshooting + +Enabling `editorconfig-mode` should be enough for normal cases. + +When EditorConfig properties are not effective for unknown reason, we recommend +first trying `M-x editorconfig-display-current-properties`. + +This command will open a new buffer and display the EditorConfig properties +loaded for current buffer. +You can check if EditorConfig properties were not read for buffers at all, +or they were loaded but did not take effect for some other reasons. + +### Indentation for new major-modes + +Because most Emacs major-modes have their own indentation settings, this plugin +requires explicit support for each major-mode for `indent_size` property. + +By default this plugin ships with settings for many major-modes, but, +sorry to say, it cannot be perfect. Especially it is difficult to support +brand-new major-modes. +Please feel free to submit issue or pull-request for such major-mode! + +Supported major-modes and their indentation configs are defined in the variable +`editorconfig-indentation-alist`. + +### Not work at all for FOO-mode! + +Most cases properties are loaded just after visiting files when +`editorconfig-mode` is enabled. +But it is known that there are major-modes that this mechanism does not work +for and require explicit call of `editorconfig-apply`. + +Typically it will occur when the major-mode is not defined using +`define-derived-mode` (`rpm-spec-mode` is an example for this). +Please feel free to submit issues if you find such modes! + +### `editorconfig-format-buffer` does not work well with lsp-mode + +By default, [lsp-mode][] configures indent-region-function so that Emacs uses +language servers' `textDocument/rangeFormatting` request to format text in +buffers. +So EditorConfig settings are ignored unless language servers +themselves support loading configs from `.editorconfig`. + +To avoid this behavior ad-hocly, set `lsp-enable-indentation` to nil. + +## Submitting Bugs and Feature Requests + +Bugs, feature requests, and other issues should be submitted to the issue +tracker: https://github.com/editorconfig/editorconfig-emacs/issues + +### Development + +To run the test locally, you will need the following tools: + +- Make +- [CMake][] +- [Eask][] + +If you are on `Linux` or `macOS`: + + $ make check-unix + +On `Windows`: + + $ make check-dos + +To start a new Emacs process with current `*.el` and without loading user init +file, run: + + $ make sandbox + +## License + +EditorConfig Emacs Plugin 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 . + + +[Emacs]: https://www.gnu.org/software/emacs/ +[MELPA]: https://melpa.org/#/editorconfig +[MELPA Stable]: https://stable.melpa.org/#/editorconfig +[NonGNU ELPA]: http://elpa.nongnu.org/nongnu/editorconfig.html +[use-package]: https://www.emacswiki.org/emacs/UsePackage +[EditorConfig]: https://editorconfig.org +[EditorConfig C Core]: https://github.com/editorconfig/editorconfig-core-c +[properties]: https://editorconfig.org/#supported-properties +[CMake]: https://cmake.org +[Eask]: https://github.com/emacs-eask/cli +[lsp-mode]: https://github.com/emacs-lsp/lsp-mode blob - /dev/null blob + 37389027bca563d2da626c3fa5c64dcd70dfb2b8 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/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 +* EditorConfig: (editorconfig). EditorConfig Emacs Plugin. blob - /dev/null blob + 5e7d3b85552e2ae6f4f777efca1d94a8bedcc7cd (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/doc/editorconfig.texi @@ -0,0 +1,297 @@ +@dircategory Emacs +@direntry +* EditorConfig: (editorconfig). EditorConfig Emacs Plugin. +@end direntry + +@node Top +@chapter EditorConfig Emacs Plugin +@anchor{#editorconfig-emacs-plugin} +This is an @uref{https://editorconfig.org,EditorConfig} plugin for +@uref{https://www.gnu.org/software/emacs/,Emacs}. + +@menu +* Getting Started:: +* Supported properties:: +* Customize:: +* Troubleshooting:: +* Submitting Bugs and Feature Requests:: +* License:: +@end menu + +@node Getting Started +@section Getting Started +@anchor{#getting-started} + +@menu +* packageel:: +* use-package:: +* Manual installation:: +@end menu + +@node packageel +@subsection package.el +@anchor{#package.el} +This package is available from +@uref{https://melpa.org/#/editorconfig,MELPA}, +@uref{https://stable.melpa.org/#/editorconfig,MELPA Stable} and +@uref{http://elpa.nongnu.org/nongnu/editorconfig.html,NonGNU ELPA}. +Install from these repositories and enable global minor-mode +@code{editorconfig-mode}: + +@verbatim +(editorconfig-mode 1) +@end verbatim + +Normally, enabling @code{editorconfig-mode} should be enough for this +plugin to work: all other configurations are optional. This mode sets up +hooks so that EditorConfig properties will be loaded and applied to the +new buffers automatically when visiting files. + +@node use-package +@subsection use-package +@anchor{#use-package} +If you use +@uref{https://www.emacswiki.org/emacs/UsePackage,@strong{use-package}}, +add the following to your @code{init.el} file: + +@verbatim +(use-package editorconfig + :ensure t + :config + (editorconfig-mode 1)) +@end verbatim + +@node Manual installation +@subsection Manual installation +@anchor{#manual-installation} +Copy all @code{.el} files in this repository to @code{~/.emacs.d/lisp} +and add the following: + +@verbatim +(add-to-list 'load-path "~/.emacs.d/lisp") +(require 'editorconfig) +(editorconfig-mode 1) +@end verbatim + +@node Supported properties +@section Supported properties +@anchor{#supported-properties} +Current Emacs plugin coverage for EditorConfig's +@uref{https://editorconfig.org/#supported-properties,properties}: + +@itemize +@item +@code{indent_style} +@item +@code{indent_size} +@item +@code{tab_width} +@item +@code{end_of_line} +@item +@code{charset} +@item +@code{trim_trailing_whitespace} +@item +@code{insert_final_newline = true} is supported +@item +@code{insert_final_newline = false} +is not enforced (as in trailing newlines actually being removed +automagically), we just buffer-locally override any preferences that +would auto-add them to files @code{.editorconfig} marks as +trailing-newline-free +@item +@code{max_line_length} +@item +@code{file_type_ext} (Experimental) +(See below) +@item +@code{file_type_emacs} (Experimental) +(See below) +@item +@code{root} (only used by EditorConfig core) +@end itemize + +Not yet covered properties marked with over-strike -- pull requests +implementing missing features warmly welcomed! Typically, you will want +to tie these to native functionality, or the configuration of existing +packages handling the feature. + +As several packages have their own handling of, say, indentation, we +might not yet cover some mode you use, but we try to add the ones that +show up on our radar. + +@menu +* File Type file_type_ext file_type_emacs:: +@end menu + +@node File Type file_type_ext file_type_emacs +@subsection File Type (file_type_ext, file_type_emacs) +@anchor{#file-type-file_type_ext-file_type_emacs} +File-type feature is currently disabled, because this package is now +undergoing big internal refactoring. For those who want this +functionality, please consider using +@uref{https://github.com/10sr/editorconfig-custom-majormode-el,editorconfig-custom-majormode}. + +@node Customize +@section Customize +@anchor{#customize} +@code{editorconfig-emacs} provides some customize variables. + +Here are some of these variables: for the full list of available +variables, type M-x customize-group [RET] editorconfig [RET]. + +@menu +* editorconfig-trim-whitespaces-mode:: +* editorconfig-after-apply-functions:: +* editorconfig-hack-properties-functions:: +@end menu + +@node editorconfig-trim-whitespaces-mode +@subsection @code{editorconfig-trim-whitespaces-mode} +@anchor{#editorconfig-trim-whitespaces-mode} +Buffer local minor-mode to use to trim trailing whitespaces. + +If set, editorconfig will enable/disable this mode in accord with +@code{trim_trailing_whitespace} property in @code{.editorconfig}. +Otherwise, use Emacs built-in @code{delete-trailing-whitespace} +function. + +One possible value is +@uref{https://github.com/lewang/ws-butler,@code{ws-butler-mode}}, with +which only lines touched get trimmed. To use it, add following to your +init.el: + +@verbatim +(setq editorconfig-trim-whitespaces-mode + 'ws-butler-mode) +@end verbatim + +@node editorconfig-after-apply-functions +@subsection @code{editorconfig-after-apply-functions} +@anchor{#editorconfig-after-apply-functions} +(Formerly @code{editorconfig-custom-hooks}) + +A list of functions which will be called after loading common +EditorConfig settings, when you can set some custom variables. + +For example, @code{web-mode} has several variables for indentation +offset size and EditorConfig sets them at once by @code{indent_size}. +You can stop indenting only blocks of @code{web-mode} by adding +following to your init.el: + +@verbatim +(add-hook 'editorconfig-after-apply-functions + (lambda (props) (setq web-mode-block-padding 0))) +@end verbatim + +@node editorconfig-hack-properties-functions +@subsection @code{editorconfig-hack-properties-functions} +@anchor{#editorconfig-hack-properties-functions} +A list of functions to alter property values before applying them. + +These functions will be run after loading ".editorconfig" files and +before applying them to current buffer, so that you can alter some +properties from ".editorconfig" before they take effect. + +For example, Makefile files always use tab characters for indentation: +you can overwrite "indent_style" property when current @code{major-mode} +is @code{makefile-mode}: + +@verbatim +(add-hook 'editorconfig-hack-properties-functions + '(lambda (props) + (when (derived-mode-p 'makefile-mode) + (puthash 'indent_style "tab" props)))) +@end verbatim + +@node Troubleshooting +@section Troubleshooting +@anchor{#troubleshooting} +Enabling @code{editorconfig-mode} should be enough for normal cases. + +When EditorConfig properties are not effective for unknown reason, we +recommend first trying +@code{M-x editorconfig-display-current-properties}. + +This command will open a new buffer and display the EditorConfig +properties loaded for current buffer. You can check if EditorConfig +properties were not read for buffers at all, or they were loaded but did +not take effect for some other reasons. + +@menu +* Indentation for new major-modes:: +* Not work at all for FOO-mode!:: +@end menu + +@node Indentation for new major-modes +@subsection Indentation for new major-modes +@anchor{#indentation-for-new-major-modes} +Because most Emacs major-modes have their own indentation settings, this +plugin requires explicit support for each major-mode for +@code{indent_size} property. + +By default this plugin ships with settings for many major-modes, but, +sorry to say, it cannot be perfect. Especially it is difficult to +support brand-new major-modes. Please feel free to submit issue or +pull-request for such major-mode! + +Supported major-modes and their indentation configs are defined in the +variable @code{editorconfig-indentation-alist}. + +@node Not work at all for FOO-mode! +@subsection Not work at all for FOO-mode! +@anchor{#not-work-at-all-for-foo-mode} +Most cases properties are loaded just after visiting files when +@code{editorconfig-mode} is enabled. But it is known that there are +major-modes that this mechanism does not work for and require explicit +call of @code{editorconfig-apply}. + +Typically it will occur when the major-mode is not defined using +@code{define-derived-mode} (@code{rpm-spec-mode} is an example for +this). Please feel free to submit issues if you find such modes! + +@node Submitting Bugs and Feature Requests +@section Submitting Bugs and Feature Requests +@anchor{#submitting-bugs-and-feature-requests} +Bugs, feature requests, and other issues should be submitted to the +issue tracker: https://github.com/editorconfig/editorconfig-emacs/issues + +@menu +* Development:: +@end menu + +@node Development +@subsection Development +@anchor{#development} +Make and @uref{https://cmake.org,CMake} must be installed to run the +tests locally: + +@verbatim +$ make check +@end verbatim + +To start a new Emacs process with current @code{*.el} and without +loading user init file, run: + +@verbatim +$ make sandbox +@end verbatim + +@node License +@section License +@anchor{#license} +EditorConfig Emacs Plugin 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 @url{https://www.gnu.org/licenses/}. + +@bye blob - /dev/null blob + 8aa7143c5e844330511335a5e58e4fb329ebf327 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/doc/header.txt @@ -0,0 +1,6 @@ +@dircategory Emacs +@direntry +* EditorConfig: (editorconfig). EditorConfig Emacs Plugin. +@end direntry + +@node Top blob - /dev/null blob + 0658318ed5306cfc1ce3c79400afb3a26abcb9b3 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/editorconfig-autoloads.el @@ -0,0 +1,148 @@ +;;; editorconfig-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 editorconfig.el + +(defvar editorconfig-mode nil "\ +Non-nil if Editorconfig mode is enabled. +See the `editorconfig-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 `editorconfig-mode'.") +(custom-autoload 'editorconfig-mode "editorconfig" nil) +(autoload 'editorconfig-mode "editorconfig" "\ +Toggle EditorConfig feature. + +To disable EditorConfig in some buffers, modify +`editorconfig-exclude-modes' or `editorconfig-exclude-regexps'. + +This is a global minor mode. If called interactively, toggle the +`Editorconfig 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 \\='editorconfig-mode)'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +(fn &optional ARG)" t) +(autoload 'editorconfig-version "editorconfig" "\ +Get EditorConfig version as string. + +If called interactively or if SHOW-VERSION is non-nil, show the +version in the echo area and the messages buffer. + +(fn &optional SHOW-VERSION)" t) +(register-definition-prefixes "editorconfig" '("editorconfig-")) + + +;;; Generated autoloads from editorconfig-conf-mode.el + +(autoload 'editorconfig-conf-mode "editorconfig-conf-mode" "\ +Major mode for editing .editorconfig files. + +(fn)" t) +(add-to-list 'auto-mode-alist '("\\.editorconfig\\'" . editorconfig-conf-mode)) +(register-definition-prefixes "editorconfig-conf-mode" '("editorconfig-conf-mode-")) + + +;;; Generated autoloads from editorconfig-core.el + +(autoload 'editorconfig-core-get-nearest-editorconfig "editorconfig-core" "\ +Return path to .editorconfig file that is closest to DIRECTORY. + +(fn DIRECTORY)") +(autoload 'editorconfig-core-get-properties "editorconfig-core" "\ +Get EditorConfig properties for FILE. +If FILE is not given, use currently visiting file. +Give CONFNAME for basename of config file other than .editorconfig. +If need to specify config format version, give CONFVERSION. + +This function returns an alist of properties. Each element will +look like (KEY . VALUE). + +(fn &optional FILE CONFNAME CONFVERSION)") +(autoload 'editorconfig-core-get-properties-hash "editorconfig-core" "\ +Get EditorConfig properties for FILE. +If FILE is not given, use currently visiting file. +Give CONFNAME for basename of config file other than .editorconfig. +If need to specify config format version, give CONFVERSION. + +This function is almost same as `editorconfig-core-get-properties', but returns +hash object instead. + +(fn &optional FILE CONFNAME CONFVERSION)") +(register-definition-prefixes "editorconfig-core" '("editorconfig-core--")) + + +;;; Generated autoloads from editorconfig-core-handle.el + +(register-definition-prefixes "editorconfig-core-handle" '("editorconfig-core-handle")) + + +;;; Generated autoloads from editorconfig-fnmatch.el + +(autoload 'editorconfig-fnmatch-p "editorconfig-fnmatch" "\ +Test whether STRING match PATTERN. + +Matching ignores case if `case-fold-search' is non-nil. + +PATTERN should be a shell glob pattern, and some zsh-like wildcard matchings can +be used: + +* Matches any string of characters, except path separators (/) +** Matches any string of characters +? Matches any single character +[name] Matches any single character in name +[^name] Matches any single character not in name +{s1,s2,s3} Matches any of the strings given (separated by commas) +{min..max} Matches any number between min and max + +(fn STRING PATTERN)") +(register-definition-prefixes "editorconfig-fnmatch" '("editorconfig-fnmatch-")) + + +;;; Generated autoloads from editorconfig-tools.el + +(autoload 'editorconfig-apply "editorconfig-tools" "\ +Get and apply EditorConfig properties to current buffer. + +This function does not respect the values of `editorconfig-exclude-modes' and +`editorconfig-exclude-regexps' and always applies available properties. +Use `editorconfig-mode-apply' instead to make use of these variables." t) +(autoload 'editorconfig-find-current-editorconfig "editorconfig-tools" "\ +Find the closest .editorconfig file for current file." t) +(autoload 'editorconfig-display-current-properties "editorconfig-tools" "\ +Display EditorConfig properties extracted for current buffer." t) +(defalias 'describe-editorconfig-properties 'editorconfig-display-current-properties) +(autoload 'editorconfig-format-buffer "editorconfig-tools" "\ +Format buffer according to .editorconfig indent_style and indent_width." t) +(register-definition-prefixes "editorconfig-tools" '("editorconfig-mode-apply")) + +;;; End of scraped data + +(provide 'editorconfig-autoloads) + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; no-native-compile: t +;; coding: utf-8-emacs-unix +;; End: + +;;; editorconfig-autoloads.el ends here blob - /dev/null blob + 2b4ddd4410f6a21e8d274292b63963355e6a8807 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/editorconfig-conf-mode.el @@ -0,0 +1,95 @@ +;;; editorconfig-conf-mode.el --- Major mode for editing .editorconfig files -*- lexical-binding: t -*- + +;; Copyright (C) 2011-2024 EditorConfig Team + +;; Author: EditorConfig Team + +;; See +;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors +;; or the CONTRIBUTORS file for the list of contributors. + +;; This file is part of EditorConfig Emacs Plugin. + +;; EditorConfig Emacs Plugin 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. + +;; EditorConfig Emacs Plugin 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 +;; EditorConfig Emacs Plugin. If not, see . + +;;; Commentary: + +;; Major mode for editing .editorconfig files. + +;;; Code: + +(require 'conf-mode) + +(defvar editorconfig-conf-mode-syntax-table + (let ((table (make-syntax-table conf-unix-mode-syntax-table))) + (modify-syntax-entry ?\; "<" table) + table) + "Syntax table in use in `editorconfig-conf-mode' buffers.") + +(defvar editorconfig-conf-mode-abbrev-table nil + "Abbrev table in use in `editorconfig-conf-mode' buffers.") +(define-abbrev-table 'editorconfig-conf-mode-abbrev-table ()) + +;;;###autoload +(define-derived-mode editorconfig-conf-mode conf-unix-mode "Conf[EditorConfig]" + "Major mode for editing .editorconfig files." + (set-variable 'indent-line-function 'indent-relative) + (let ((key-property-list + '("charset" + "end_of_line" + "file_type_emacs" + "file_type_ext" + "indent_size" + "indent_style" + "insert_final_newline" + "max_line_length" + "root" + "tab_width" + "trim_trailing_whitespace")) + (key-value-list + '("unset" + "true" + "false" + "lf" + "cr" + "crlf" + "space" + "tab" + "latin1" + "utf-8" + "utf-8-bom" + "utf-16be" + "utf-16le")) + (font-lock-value + '(("^[ \t]*\\[\\(.+?\\)\\]" 1 font-lock-type-face) + ("^[ \t]*\\(.+?\\)[ \t]*[=:]" 1 font-lock-variable-name-face)))) + + ;; Highlight all key values + (dolist (key-value key-value-list) + (push `(,(format "[=:][ \t]*\\(%s\\)\\([ \t]\\|$\\)" key-value) + 1 font-lock-constant-face) + font-lock-value)) + ;; Highlight all key properties + (dolist (key-property key-property-list) + (push `(,(format "^[ \t]*\\(%s\\)[ \t]*[=:]" key-property) + 1 font-lock-builtin-face) + font-lock-value)) + + (conf-mode-initialize "#" font-lock-value))) + +;;;###autoload +(add-to-list 'auto-mode-alist '("\\.editorconfig\\'" . editorconfig-conf-mode)) + +(provide 'editorconfig-conf-mode) +;;; editorconfig-conf-mode.el ends here blob - /dev/null blob + d225e1456ba325c14e0392659ccdc77b8351eb06 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/editorconfig-core-handle.el @@ -0,0 +1,243 @@ +;;; editorconfig-core-handle.el --- Handle Class for EditorConfig File -*- lexical-binding: t -*- + +;; Copyright (C) 2011-2024 EditorConfig Team + +;; Author: EditorConfig Team + +;; See +;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors +;; or the CONTRIBUTORS file for the list of contributors. + +;; This file is part of EditorConfig Emacs Plugin. + +;; EditorConfig Emacs Plugin 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. + +;; EditorConfig Emacs Plugin 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 +;; EditorConfig Emacs Plugin. If not, see . + +;;; Commentary: + +;; Handle structures for EditorConfig config file. This library is used +;; internally from editorconfig-core.el . + +;;; Code: + +(require 'cl-lib) + +(require 'editorconfig-fnmatch) + +(defvar editorconfig-core-handle--cache-hash + (make-hash-table :test 'equal) + "Hash of EditorConfig filename and its `editorconfig-core-handle' instance.") + +(cl-defstruct editorconfig-core-handle-section + "Structure representing one section in a .editorconfig file. + +Slots: + +`name' + String of section name (glob string). + +`props' + Alist of properties: (KEY . VALUE)." + (name nil) + (props nil)) + +(defun editorconfig-core-handle-section-get-properties (section file dir) + "Return properties alist when SECTION name match FILE. + +DIR should be the directory where .editorconfig file which has SECTION lives. +IF not match, return nil." + (when (editorconfig-core-handle--fnmatch-p + file (editorconfig-core-handle-section-name section) dir) + (editorconfig-core-handle-section-props section))) + +(cl-defstruct editorconfig-core-handle + "Structure representing an .editorconfig file. + +Slots: +`top-props' + Alist of top properties like ((\"root\" . \"true\")) + +`sections' + List of `editorconfig-core-handle-section' structure objects. + +`mtime' + Last modified time of .editorconfig file. + +`path' + Absolute path to .editorconfig file.' +" + (top-props nil) + (sections nil) + (mtime nil) + (path nil)) + + +(defun editorconfig-core-handle (conf) + "Return EditorConfig handle for CONF, which should be a file path. + +If CONF does not exist return nil." + (when (file-readable-p conf) + (let ((cached (gethash conf editorconfig-core-handle--cache-hash)) + (mtime (nth 5 (file-attributes conf)))) + (if (and cached + (equal (editorconfig-core-handle-mtime cached) mtime)) + cached + (let ((parsed (editorconfig-core-handle--parse-file conf))) + (puthash conf + (make-editorconfig-core-handle :top-props (plist-get parsed :top-props) + :sections (plist-get parsed :sections) + :mtime mtime + :path conf) + editorconfig-core-handle--cache-hash)))))) + +(defun editorconfig-core-handle-root-p (handle) + "Return non-nil if HANDLE represent root EditorConfig file. + +If HANDLE is nil return nil." + (when handle + (string-equal "true" + (downcase (or (cdr (assoc "root" + (editorconfig-core-handle-top-props handle))) + ""))))) + +(defun editorconfig-core-handle-get-properties (handle file) + "Return list of alist of properties from HANDLE for FILE. +The list returned will be ordered by the lines they appear. + +If HANDLE is nil return nil." + (when handle + (let ((dir (file-name-directory (editorconfig-core-handle-path handle)))) + (cl-loop for section in (editorconfig-core-handle-sections handle) + for props = (editorconfig-core-handle-section-get-properties section + file + dir) + when props collect (copy-alist props))))) +(make-obsolete 'editorconfig-core-handle-get-properties + 'editorconfig-core-handle-get-properties-hash + "0.8.0") + + +(defun editorconfig-core-handle-get-properties-hash (handle file) + "Return hash of properties from HANDLE for FILE. + +If HANDLE is nil return nil." + (when handle + (let ((hash (make-hash-table)) + (dir (file-name-directory (editorconfig-core-handle-path + handle)))) + (dolist (section (editorconfig-core-handle-sections handle)) + (cl-loop for (key . value) in (editorconfig-core-handle-section-get-properties section file dir) + do (puthash (intern key) value hash))) + hash))) + +(defun editorconfig-core-handle--fnmatch-p (name pattern dir) + "Return non-nil if NAME match PATTERN. +If pattern has slash, pattern should be relative to DIR. + +This function is a fnmatch with a few modification for EditorConfig usage." + (if (string-match-p "/" pattern) + (let ((pattern (replace-regexp-in-string "^/" "" pattern)) + (dir (file-name-as-directory dir))) + (editorconfig-fnmatch-p name (concat dir pattern))) + (editorconfig-fnmatch-p name (concat "**/" pattern)))) + +(defsubst editorconfig-core-handle--string-trim (str) + "Remove leading and trailing whitespaces from STR." + (replace-regexp-in-string "[[:space:]]+\\'" + "" + (replace-regexp-in-string "\\`[[:space:]]+" + "" + str))) + +(defun editorconfig-core-handle--parse-file (conf) + "Parse EditorConfig file CONF. + +This function returns cons of its top properties alist and +alist of patterns and its properties alist. +The list returned will be ordered by the lines they appear. + +If CONF is not found return nil." + (when (file-readable-p conf) + (with-temp-buffer + ;; NOTE: Use this instead of insert-file-contents-literally to enable + ;; code conversion + (insert-file-contents conf) + (goto-char (point-min)) + (let ((point-max (point-max)) + (sections ()) + (top-props nil) + + ;; String of current line + (line "") + ;; nil when pattern not appeared yet, "" when pattern is empty ("[]") + (pattern nil) + ;; Alist of properties for current PATTERN + (props ()) + + ;; Current line num + (current-line-number 1)) + (while (not (eq (point) point-max)) + (setq line + (buffer-substring-no-properties (line-beginning-position) + (line-end-position))) + (setq line + (replace-regexp-in-string "\\(^\\| \\)\\(#\\|;\\).*$" + "" + (editorconfig-core-handle--string-trim line))) + + (cond + ((string-equal "" line) + nil) + + ;; Start of section + ((string-match "^\\[\\(.*\\)\\]$" + line) + (when pattern + (setq sections + `(,@sections ,(make-editorconfig-core-handle-section + :name pattern + :props props))) + (setq pattern nil) + (setq props nil)) + (setq pattern (match-string 1 line))) + + (t + (let ((idx (string-match "=\\|:" line))) + (unless idx + (error "Error while reading config file: %s:%d:\n %s\n" + conf current-line-number line)) + (let ((key (downcase (editorconfig-core-handle--string-trim + (substring line 0 idx)))) + (value (editorconfig-core-handle--string-trim + (substring line (1+ idx))))) + (when (and (< (length key) 51) + (< (length value) 256)) + (if pattern + (when (< (length pattern) 4097) + (setq props + `(,@props (,key . ,value)))) + (setq top-props + `(,@top-props (,key . ,value))))))))) + (setq current-line-number (1+ current-line-number)) + (goto-char (point-min)) + (forward-line (1- current-line-number))) + (when pattern + (setq sections + `(,@sections ,(make-editorconfig-core-handle-section + :name pattern + :props props)))) + (list :top-props top-props + :sections sections))))) + +(provide 'editorconfig-core-handle) +;;; editorconfig-core-handle.el ends here blob - /dev/null blob + c7b52deaafd893efcd2ee6b6d40d942e22092fb7 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/editorconfig-core.el @@ -0,0 +1,182 @@ +;;; editorconfig-core.el --- EditorConfig Core library in Emacs Lisp -*- lexical-binding: t -*- + +;; Copyright (C) 2011-2024 EditorConfig Team + +;; Author: EditorConfig Team + +;; See +;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors +;; or the CONTRIBUTORS file for the list of contributors. + +;; This file is part of EditorConfig Emacs Plugin. + +;; EditorConfig Emacs Plugin 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. + +;; EditorConfig Emacs Plugin 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 +;; EditorConfig Emacs Plugin. If not, see . + +;;; Commentary: + +;; This library is one implementation of EditorConfig Core, which parses +;; .editorconfig files and returns properties for given files. +;; This can be used in place of, for example, editorconfig-core-c. + + +;; Use from EditorConfig Emacs Plugin + +;; Emacs plugin (v0.5 or later) can utilize this implementation. +;; By default, the plugin first search for any EditorConfig executable, +;; and fallback to this library if not found. +;; If you always want to use this library, add following lines to your init.el: + +;; (setq editorconfig-get-properties-function +;; 'editorconfig-core-get-properties-hash) + + +;; Functions + +;; editorconfig-core-get-properties (&optional file confname confversion) + +;; Get EditorConfig properties for FILE. + +;; If FILE is not given, use currently visiting file. +;; Give CONFNAME for basename of config file other than .editorconfig. +;; If need to specify config format version, give CONFVERSION. + +;; This functions returns alist of properties. Each element will look like +;; (KEY . VALUE) . + + +;; editorconfig-core-get-properties-hash (&optional file confname confversion) + +;; Get EditorConfig properties for FILE. + +;; This function is almost same as `editorconfig-core-get-properties', but +;; returns hash object instead. + +;;; Code: + +(require 'cl-lib) + +(require 'editorconfig-core-handle) + +(eval-when-compile + (require 'subr-x)) + + +(defun editorconfig-core--get-handles (dir confname &optional result) + "Get list of EditorConfig handlers for DIR from CONFNAME. + +In the resulting list, the handle for root config file comes first, and the +nearest comes last. +The list may contains nil when no file was found for directories. +RESULT is used internally and normally should not be used." + (setq dir (expand-file-name dir)) + (let ((handle (editorconfig-core-handle (concat (file-name-as-directory dir) + confname))) + (parent (file-name-directory (directory-file-name dir)))) + (if (or (string= parent dir) + (and handle (editorconfig-core-handle-root-p handle))) + (cl-remove-if-not 'identity (cons handle result)) + (editorconfig-core--get-handles parent + confname + (cons handle result))))) + +;;;###autoload +(defun editorconfig-core-get-nearest-editorconfig (directory) + "Return path to .editorconfig file that is closest to DIRECTORY." + (when-let* ((handle (car (last + (editorconfig-core--get-handles directory + ".editorconfig"))))) + (editorconfig-core-handle-path handle))) + +;;;###autoload +(defun editorconfig-core-get-properties (&optional file confname confversion) + "Get EditorConfig properties for FILE. +If FILE is not given, use currently visiting file. +Give CONFNAME for basename of config file other than .editorconfig. +If need to specify config format version, give CONFVERSION. + +This function returns an alist of properties. Each element will +look like (KEY . VALUE)." + (let ((hash (editorconfig-core-get-properties-hash file confname confversion)) + (result nil)) + (maphash (lambda (key value) + (add-to-list 'result (cons (symbol-name key) value))) + hash) + result)) + +(defun editorconfig-core--hash-merge (into update) + "Merge two hashes INTO and UPDATE. + +This is a destructive function, hash INTO will be modified. +When the same key exists in both two hashes, values of UPDATE takes precedence." + (maphash (lambda (key value) (puthash key value into)) update) + into) + +;;;###autoload +(defun editorconfig-core-get-properties-hash (&optional file confname confversion) + "Get EditorConfig properties for FILE. +If FILE is not given, use currently visiting file. +Give CONFNAME for basename of config file other than .editorconfig. +If need to specify config format version, give CONFVERSION. + +This function is almost same as `editorconfig-core-get-properties', but returns +hash object instead." + (setq file + (expand-file-name (or file + buffer-file-name + (error "FILE is not given and `buffer-file-name' is nil")))) + (setq confname (or confname ".editorconfig")) + (setq confversion (or confversion "0.12.0")) + (let ((result (make-hash-table))) + (dolist (handle (editorconfig-core--get-handles (file-name-directory file) + confname)) + (editorconfig-core--hash-merge result + (editorconfig-core-handle-get-properties-hash handle + file))) + + ;; Downcase known boolean values + (dolist (key '( end_of_line indent_style indent_size insert_final_newline + trim_trailing_whitespace charset)) + (when-let* ((val (gethash key result))) + (puthash key (downcase val) result))) + + ;; Add indent_size property + (let ((v-indent-size (gethash 'indent_size result)) + (v-indent-style (gethash 'indent_style result))) + (when (and (not v-indent-size) + (string= v-indent-style "tab") + ;; If VERSION < 0.9.0, indent_size should have no default value + (version<= "0.9.0" + confversion)) + (puthash 'indent_size + "tab" + result))) + ;; Add tab_width property + (let ((v-indent-size (gethash 'indent_size result)) + (v-tab-width (gethash 'tab_width result))) + (when (and v-indent-size + (not v-tab-width) + (not (string= v-indent-size "tab"))) + (puthash 'tab_width v-indent-size result))) + ;; Update indent-size property + (let ((v-indent-size (gethash 'indent_size result)) + (v-tab-width (gethash 'tab_width result))) + (when (and v-indent-size + v-tab-width + (string= v-indent-size "tab")) + (puthash 'indent_size v-tab-width result))) + + result)) + +(provide 'editorconfig-core) +;;; editorconfig-core.el ends here blob - /dev/null blob + 25a344dc2ef9d65a9c4dc06bf44cfee6bb6dbcc0 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/editorconfig-fnmatch.el @@ -0,0 +1,284 @@ +;;; editorconfig-fnmatch.el --- Glob pattern matching in Emacs lisp -*- lexical-binding: t -*- + +;; Copyright (C) 2011-2024 EditorConfig Team + +;; Author: EditorConfig Team + +;; See +;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors +;; or the CONTRIBUTORS file for the list of contributors. + +;; This file is part of EditorConfig Emacs Plugin. + +;; EditorConfig Emacs Plugin 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. + +;; EditorConfig Emacs Plugin 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 +;; EditorConfig Emacs Plugin. If not, see . + +;;; Commentary: + +;; editorconfig-fnmatch.el provides a fnmatch implementation with a few +;; extensions. +;; The main usage of this library is glob pattern matching for EditorConfig, but +;; it can also act solely. + +;; editorconfig-fnmatch-p (name pattern) + +;; Test whether NAME match PATTERN. + +;; PATTERN should be a shell glob pattern, and some zsh-like wildcard matchings +;; can be used: + +;; * Matches any string of characters, except path separators (/) +;; ** Matches any string of characters +;; ? Matches any single character +;; [name] Matches any single character in name +;; [^name] Matches any single character not in name +;; {s1,s2,s3} Matches any of the strings given (separated by commas) +;; {min..max} Matches any number between min and max + + +;; This library is a port from editorconfig-core-py library. +;; https://github.com/editorconfig/editorconfig-core-py/blob/master/editorconfig/fnmatch.py + +;;; Code: + +(require 'cl-lib) + +(defvar editorconfig-fnmatch--cache-hashtable + nil + "Cache of shell pattern and its translation.") +;; Clear cache on file reload +(setq editorconfig-fnmatch--cache-hashtable + (make-hash-table :test 'equal)) + + +(defconst editorconfig-fnmatch--left-brace-regexp + "\\(^\\|[^\\]\\){" + "Regular expression for left brace ({).") + +(defconst editorconfig-fnmatch--right-brace-regexp + "\\(^\\|[^\\]\\)}" + "Regular expression for right brace (}).") + + +(defconst editorconfig-fnmatch--numeric-range-regexp + "\\([+-]?[0-9]+\\)\\.\\.\\([+-]?[0-9]+\\)" + "Regular expression for numeric range (like {-3..+3}).") + +(defun editorconfig-fnmatch--match-num (regexp string) + "Return how many times REGEXP is found in STRING." + (let ((num 0)) + ;; START arg does not work as expected in this case + (while (string-match regexp string) + (setq num (1+ num) + string (substring string (match-end 0)))) + num)) + +;;;###autoload +(defun editorconfig-fnmatch-p (string pattern) + "Test whether STRING match PATTERN. + +Matching ignores case if `case-fold-search' is non-nil. + +PATTERN should be a shell glob pattern, and some zsh-like wildcard matchings can +be used: + +* Matches any string of characters, except path separators (/) +** Matches any string of characters +? Matches any single character +[name] Matches any single character in name +[^name] Matches any single character not in name +{s1,s2,s3} Matches any of the strings given (separated by commas) +{min..max} Matches any number between min and max" + (string-match (editorconfig-fnmatch-translate pattern) + string)) + +;;(editorconfig-fnmatch-translate "{a,{-3..3}}.js") +;;(editorconfig-fnmatch-p "1.js" "{a,{-3..3}}.js") + +(defun editorconfig-fnmatch-translate (pattern) + "Translate a shell PATTERN to a regular expression. + +Translation result will be cached, so same translation will not be done twice." + (let ((cached (gethash pattern + editorconfig-fnmatch--cache-hashtable))) + (or cached + (puthash pattern + (editorconfig-fnmatch--do-translate pattern) + editorconfig-fnmatch--cache-hashtable)))) + + +(defun editorconfig-fnmatch--do-translate (pattern &optional nested) + "Translate a shell PATTERN to a regular expression. + +Set NESTED to t when this function is called from itself. + +This function is called from `editorconfig-fnmatch-translate', when no cached +translation is found for PATTERN." + (let ((index 0) + (length (length pattern)) + (brace-level 0) + (in-brackets nil) + ;; List of strings of resulting regexp + (result ()) + (is-escaped nil) + (matching-braces (= (editorconfig-fnmatch--match-num + editorconfig-fnmatch--left-brace-regexp + pattern) + (editorconfig-fnmatch--match-num + editorconfig-fnmatch--right-brace-regexp + pattern))) + + current-char + pos + has-slash + has-comma + num-range) + + (while (< index length) + (if (and (not is-escaped) + (string-match "[^]\\*?[{},/\\-]+" + ;;(string-match "[^]\\*?[{},/\\-]+" "?.a") + pattern + index) + (eq index (match-beginning 0))) + (setq result `(,@result ,(regexp-quote (match-string 0 pattern))) + index (match-end 0) + is-escaped nil) + + (setq current-char (aref pattern index) + index (1+ index)) + + (cl-case current-char + (?* + (setq pos index) + (if (and (< pos length) + (= (aref pattern pos) ?*)) + (setq result `(,@result ".*")) + (setq result `(,@result "[^/]*")))) + + (?? + (setq result `(,@result "[^/]"))) + + (?\[ + (if in-brackets + (setq result `(,@result "\\[")) + (if (= (aref pattern index) ?/) + ;; Slash after an half-open bracket + (setq result `(,@result "\\[/") + index (+ index 1)) + (setq pos index + has-slash nil) + (while (and (< pos length) + (not (= (aref pattern pos) ?\])) + (not has-slash)) + (if (and (= (aref pattern pos) ?/) + (not (= (aref pattern (- pos 1)) ?\\))) + (setq has-slash t) + (setq pos (1+ pos)))) + (if has-slash + (setq result `(,@result ,(concat "\\[" + (substring pattern + index + (1+ pos)) + "\\]")) + index (+ pos 2)) + (if (and (< index length) + (memq (aref pattern index) + '(?! ?^))) + (setq index (1+ index) + result `(,@result "[^")) + (setq result `(,@result "["))) + (setq in-brackets t))))) + + (?- + (if in-brackets + (setq result `(,@result "-")) + (setq result `(,@result "\\-")))) + + (?\] + (setq result `(,@result "]") + in-brackets nil)) + + (?{ + (setq pos index + has-comma nil) + (while (and (or (and (< pos length) + (not (= (aref pattern pos) ?}))) + is-escaped) + (not has-comma)) + (if (and (eq (aref pattern pos) ?,) + (not is-escaped)) + (setq has-comma t) + (setq is-escaped (and (eq (aref pattern pos) + ?\\) + (not is-escaped)) + pos (1+ pos)))) + (if (and (not has-comma) + (< pos length)) + (let ((pattern-sub (substring pattern index pos))) + (setq num-range (string-match editorconfig-fnmatch--numeric-range-regexp + pattern-sub)) + (if num-range + (let ((number-start (string-to-number (match-string 1 + pattern-sub))) + (number-end (string-to-number (match-string 2 + pattern-sub)))) + (setq result `(,@result ,(concat "\\(?:" + (mapconcat 'number-to-string + (cl-loop for i from number-start to number-end + collect i) + "\\|") + "\\)")))) + (let ((inner (editorconfig-fnmatch--do-translate pattern-sub t))) + (setq result `(,@result ,(format "{%s}" inner))))) + (setq index (1+ pos))) + (if matching-braces + (setq result `(,@result "\\(?:") + brace-level (1+ brace-level)) + (setq result `(,@result "{"))))) + + (?, + (if (and (> brace-level 0) + (not is-escaped)) + (setq result `(,@result "\\|")) + (setq result `(,@result "\\,")))) + + (?} + (if (and (> brace-level 0) + (not is-escaped)) + (setq result `(,@result "\\)") + brace-level (- brace-level 1)) + (setq result `(,@result "}")))) + + (?/ + (if (and (<= (+ index 3) (length pattern)) + (string= (substring pattern index (+ index 3)) "**/")) + (setq result `(,@result "\\(?:/\\|/.*/\\)") + index (+ index 3)) + (setq result `(,@result "/")))) + + (t + (unless (= current-char ?\\) + (setq result `(,@result ,(regexp-quote (char-to-string current-char))))))) + + (if (= current-char ?\\) + (progn (when is-escaped + (setq result `(,@result "\\\\"))) + (setq is-escaped (not is-escaped))) + (setq is-escaped nil)))) + (unless nested + (setq result `("^" ,@result "\\'"))) + (apply #'concat result))) + +(provide 'editorconfig-fnmatch) +;;; editorconfig-fnmatch.el ends here blob - /dev/null blob + 5388bb2d1f8a48e9477f9bb5d269f3b51e5fef8d (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/editorconfig-pkg.el @@ -0,0 +1,2 @@ +;; Generated package description from editorconfig.el -*- no-byte-compile: t -*- +(define-package "editorconfig" "0.11.0" "EditorConfig Emacs Plugin" '((emacs "26.1") (nadvice "0.3")) :commit "f1531bab5b57e40759167b7e5db49acbbc09972f" :authors '(("EditorConfig Team" . "editorconfig@googlegroups.com")) :maintainer '("EditorConfig Team" . "editorconfig@googlegroups.com") :keywords '("convenience" "editorconfig") :url "https://github.com/editorconfig/editorconfig-emacs#readme") blob - /dev/null blob + 12c40577143b91b42639905224106ebbdbc9af67 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/editorconfig-tools.el @@ -0,0 +1,132 @@ +;;; editorconfig-tools.el --- Editorconfig tools -*- lexical-binding: t -*- + +;; Copyright (C) 2011-2024 EditorConfig Team + +;; Author: EditorConfig Team + +;; See +;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors +;; or the CONTRIBUTORS file for the list of contributors. + +;; This file is part of EditorConfig Emacs Plugin. + +;; EditorConfig Emacs Plugin 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. + +;; EditorConfig Emacs Plugin 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 +;; EditorConfig Emacs Plugin. If not, see . + +;;; Commentary: + +;; Some utility commands for users, not used from editorconfig-mode. + +;;; Code: + +(require 'cl-lib) + +(eval-when-compile + (require 'subr-x)) + + +(require 'editorconfig) + +;;;###autoload +(defun editorconfig-apply () + "Get and apply EditorConfig properties to current buffer. + +This function does not respect the values of `editorconfig-exclude-modes' and +`editorconfig-exclude-regexps' and always applies available properties. +Use `editorconfig-mode-apply' instead to make use of these variables." + (interactive) + (when buffer-file-name + (condition-case err + (progn + (let ((props (editorconfig-call-get-properties-function buffer-file-name))) + (condition-case err + (run-hook-with-args 'editorconfig-hack-properties-functions props) + (error + (display-warning '(editorconfig editorconfig-hack-properties-functions) + (format "Error while running editorconfig-hack-properties-functions, abort running hook: %S" + err) + :warning))) + (setq editorconfig-properties-hash props) + (editorconfig-set-local-variables props) + (editorconfig-set-coding-system-revert + (gethash 'end_of_line props) + (gethash 'charset props)) + (condition-case err + (run-hook-with-args 'editorconfig-after-apply-functions props) + (error + (display-warning '(editorconfig editorconfig-after-apply-functions) + (format "Error while running editorconfig-after-apply-functions, abort running hook: %S" + err) + :warning))))) + (error + (display-warning '(editorconfig editorconfig-apply) + (format "Error in editorconfig-apply, styles will not be applied: %S" err) + :error))))) + +(defun editorconfig-mode-apply () + "Get and apply EditorConfig properties to current buffer. + +This function does nothing when the major mode is listed in +`editorconfig-exclude-modes', or variable `buffer-file-name' matches +any of regexps in `editorconfig-exclude-regexps'." + (interactive) + (when (and major-mode + (not (editorconfig--disabled-for-majormode major-mode)) + buffer-file-name + (not (editorconfig--disabled-for-filename buffer-file-name))) + (editorconfig-apply))) + + +;;;###autoload +(defun editorconfig-find-current-editorconfig () + "Find the closest .editorconfig file for current file." + (interactive) + (eval-and-compile (require 'editorconfig-core)) + (when-let* ((file (editorconfig-core-get-nearest-editorconfig + default-directory))) + (find-file file))) + +;;;###autoload +(defun editorconfig-display-current-properties () + "Display EditorConfig properties extracted for current buffer." + (interactive) + (if editorconfig-properties-hash + (let ((buf (get-buffer-create "*EditorConfig Properties*")) + (file buffer-file-name) + (props editorconfig-properties-hash)) + (with-current-buffer buf + (erase-buffer) + (insert (format "# EditorConfig for %s\n" file)) + (maphash (lambda (k v) + (insert (format "%S = %s\n" k v))) + props)) + (display-buffer buf)) + (message "Properties are not applied to current buffer yet.") + nil)) +;;;###autoload +(defalias 'describe-editorconfig-properties + 'editorconfig-display-current-properties) + +;;;###autoload +(defun editorconfig-format-buffer() + "Format buffer according to .editorconfig indent_style and indent_width." + (interactive) + (when (string= (gethash 'indent_style editorconfig-properties-hash) "tab") + (tabify (point-min) (point-max))) + (when (string= (gethash 'indent_style editorconfig-properties-hash) "space") + (untabify (point-min) (point-max))) + (indent-region (point-min) (point-max))) + + +(provide 'editorconfig-tools) +;;; editorconfig-tools.el ends here blob - /dev/null blob + 3a12d6ff5157f7ff245dc85e0ed970f9bbbbd9cc (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/editorconfig.el @@ -0,0 +1,887 @@ +;;; editorconfig.el --- EditorConfig Emacs Plugin -*- lexical-binding: t -*- + +;; Copyright (C) 2011-2024 EditorConfig Team + +;; Author: EditorConfig Team +;; Version: 0.11.0 +;; URL: https://github.com/editorconfig/editorconfig-emacs#readme +;; Package-Requires: ((emacs "26.1") (nadvice "0.3")) +;; Keywords: convenience editorconfig + +;; See +;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors +;; or the CONTRIBUTORS file for the list of contributors. + +;; This file is part of EditorConfig Emacs Plugin. + +;; EditorConfig Emacs Plugin 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. + +;; EditorConfig Emacs Plugin 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 +;; EditorConfig Emacs Plugin. If not, see . + +;;; Commentary: + +;; EditorConfig helps developers define and maintain consistent +;; coding styles between different editors and IDEs. + +;; The EditorConfig project consists of a file format for defining +;; coding styles and a collection of text editor plugins that enable +;; editors to read the file format and adhere to defined styles. +;; EditorConfig files are easily readable and they work nicely with +;; version control systems. + +;;; Code: + +(require 'cl-lib) +(require 'pcase) + +(require 'nadvice) + +(eval-when-compile + (require 'rx) + (require 'subr-x) + (defvar tex-indent-basic) + (defvar tex-indent-item) + (defvar tex-indent-arg) + (defvar evil-shift-width)) + +(require 'editorconfig-core) + +(defgroup editorconfig nil + "EditorConfig Emacs Plugin. + +EditorConfig helps developers define and maintain consistent +coding styles between different editors and IDEs." + :tag "EditorConfig" + :prefix "editorconfig-" + :group 'tools) + +(define-obsolete-variable-alias + 'edconf-exec-path + 'editorconfig-exec-path + "0.5") +(defcustom editorconfig-exec-path + "editorconfig" + "Path to EditorConfig executable. + +Used by `editorconfig--execute-editorconfig-exec'." + :type 'string + :group 'editorconfig) + +(define-obsolete-variable-alias + 'edconf-get-properties-function + 'editorconfig-get-properties-function + "0.5") +(defcustom editorconfig-get-properties-function + 'editorconfig-core-get-properties-hash + "A function which gets EditorConfig properties for specified file. + +This function will be called with one argument, full path of the target file, +and should return a hash object containing properties, or nil if any core +program is not available. Keys of this hash should be symbols of properties, +and values should be strings of their values. + + +For example, if you always want to use built-in core library instead +of any EditorConfig executable to get properties, add following to +your init.el: + + (set-variable \\='editorconfig-get-properties-function + #\\='editorconfig-core-get-properties-hash) + +Possible known values are: + +* `editorconfig-core-get-properties-hash' (default) + * Always use built-in Emacs-Lisp implementation to get properties +* `editorconfig-get-properties' + * Use `editorconfig-get-properties-from-exec' when + `editorconfig-exec-path' executable is found, otherwise + use `editorconfig-core-get-properties-hash' +* `editorconfig-get-properties-from-exec' + * Get properties by executing EditorConfig executable" + :type 'function + :group 'editorconfig) + +(defcustom editorconfig-mode-lighter " EditorConfig" + "Command `editorconfig-mode' lighter string." + :type 'string + :group 'editorconfig) + +(define-obsolete-variable-alias + 'edconf-custom-hooks + 'editorconfig-after-apply-functions + "0.5") +(define-obsolete-variable-alias + 'editorconfig-custom-hooks + 'editorconfig-after-apply-functions + "0.7.14") +(defcustom editorconfig-after-apply-functions () + "A list of functions after loading common EditorConfig settings. + +Each element in this list is a hook function. This hook function +takes one parameter, which is a property hash table. The value +of properties can be obtained through gethash function. + +The hook does not have to be coding style related; you can add +whatever functionality you want. For example, the following is +an example to add a new property emacs_linum to decide whether to +show line numbers on the left: + + (add-hook \\='editorconfig-after-apply-functions + \\='(lambda (props) + (let ((show-line-num (gethash \\='emacs_linum props))) + (cond ((equal show-line-num \"true\") (linum-mode 1)) + ((equal show-line-num \"false\") (linum-mode 0)))))) + +This hook will be run even when there are no matching sections in +\".editorconfig\", or no \".editorconfig\" file was found at all." + :type 'hook + :group 'editorconfig) + +(defcustom editorconfig-hack-properties-functions () + "A list of function to alter property values before applying them. + +These functions will be run after loading \".editorconfig\" files and before +applying them to current buffer, so that you can alter some properties from +\".editorconfig\" before they take effect. +\(Since 2021/08/30 (v0.9.0): Buffer coding-systems are set before running +this functions, so this variable cannot be used to change coding-systems.) + +For example, Makefiles always use tab characters for indentation: you can +overwrite \"indent_style\" property when current `major-mode' is a +`makefile-mode' with following code: + + (add-hook \\='editorconfig-hack-properties-functions + \\='(lambda (props) + (when (derived-mode-p \\='makefile-mode) + (puthash \\='indent_style \"tab\" props)))) + +This hook will be run even when there are no matching sections in +\".editorconfig\", or no \".editorconfig\" file was found at all." + :type 'hook + :group 'editorconfig) +(make-obsolete-variable 'editorconfig-hack-properties-functions + "Using `editorconfig-after-apply-functions' instead is recommended, + because since 2021/08/30 (v0.9.0) this variable cannot support all properties: + charset values will be referenced before running this hook." + "v0.9.0") + +(define-obsolete-variable-alias + 'edconf-indentation-alist + 'editorconfig-indentation-alist + "0.5") +(defcustom editorconfig-indentation-alist + ;; For contributors: Sort modes in alphabetical order + '((apache-mode apache-indent-level) + (awk-mode c-basic-offset) + (bpftrace-mode c-basic-offset) + (c++-mode c-basic-offset) + (c++-ts-mode c-basic-offset + c-ts-mode-indent-offset) + (c-mode c-basic-offset) + (c-ts-mode c-basic-offset + c-ts-mode-indent-offset) + (cmake-mode cmake-tab-width) + (cmake-ts-mode cmake-tab-width + cmake-ts-mode-indent-offset) + (coffee-mode coffee-tab-width) + (cperl-mode cperl-indent-level) + (crystal-mode crystal-indent-level) + (csharp-mode c-basic-offset) + (csharp-ts-mode c-basic-offset + csharp-ts-mode-indent-offset) + (css-mode css-indent-offset) + (css-ts-mode css-indent-offset) + (d-mode c-basic-offset) + (elixir-ts-mode elixir-ts-indent-offset) + (emacs-lisp-mode lisp-indent-offset) + (enh-ruby-mode enh-ruby-indent-level) + (erlang-mode erlang-indent-level) + (ess-mode ess-indent-offset) + (f90-mode f90-associate-indent + f90-continuation-indent + f90-critical-indent + f90-do-indent + f90-if-indent + f90-program-indent + f90-type-indent) + (feature-mode feature-indent-offset + feature-indent-level) + (fsharp-mode fsharp-continuation-offset + fsharp-indent-level + fsharp-indent-offset) + (gdscript-mode gdscript-indent-offset) + (graphql-mode graphql-indent-level) + (groovy-mode groovy-indent-offset) + (go-ts-mode go-ts-mode-indent-offset) + (haskell-mode haskell-indent-spaces + haskell-indent-offset + haskell-indentation-layout-offset + haskell-indentation-left-offset + haskell-indentation-starter-offset + haskell-indentation-where-post-offset + haskell-indentation-where-pre-offset + shm-indent-spaces) + (haxor-mode haxor-tab-width) + (hcl-mode hcl-indent-level) + (html-ts-mode html-ts-mode-indent-offset) + (idl-mode c-basic-offset) + (jade-mode jade-tab-width) + (java-mode c-basic-offset) + (java-ts-mode c-basic-offset + java-ts-mode-indent-offset) + (js-mode js-indent-level) + (js-ts-mode js-indent-level) + (js-jsx-mode js-indent-level sgml-basic-offset) + (js2-mode js2-basic-offset) + (js2-jsx-mode js2-basic-offset sgml-basic-offset) + (js3-mode js3-indent-level) + (json-mode js-indent-level) + (json-ts-mode json-ts-mode-indent-offset) + (jsonian-mode jsonian-default-indentation) + (julia-mode julia-indent-offset) + (kotlin-mode kotlin-tab-width) + (kotlin-ts-mode kotlin-ts-mode-indent-offset) + (latex-mode . editorconfig-set-indentation-latex-mode) + (lisp-mode lisp-indent-offset) + (livescript-mode livescript-tab-width) + (lua-mode lua-indent-level) + (lua-ts-mode lua-ts-indent-offset) + (matlab-mode matlab-indent-level) + (meson-mode meson-indent-basic) + (mips-mode mips-tab-width) + (mustache-mode mustache-basic-offset) + (nasm-mode nasm-basic-offset) + (nginx-mode nginx-indent-level) + (nxml-mode nxml-child-indent (nxml-attribute-indent . 2)) + (objc-mode c-basic-offset) + (octave-mode octave-block-offset) + (perl-mode perl-indent-level) + ;; No need to change `php-mode-coding-style' value for php-mode + ;; since we run editorconfig later than it resets `c-basic-offset'. + ;; See https://github.com/editorconfig/editorconfig-emacs/issues/116 + ;; for details. + (php-mode c-basic-offset) + (php-ts-mode php-ts-mode-indent-offset) + (pike-mode c-basic-offset) + (protobuf-mode c-basic-offset) + (ps-mode ps-mode-tab) + (pug-mode pug-tab-width) + (puppet-mode puppet-indent-level) + (python-mode . editorconfig-set-indentation-python-mode) + (python-ts-mode . editorconfig-set-indentation-python-mode) + (rjsx-mode js-indent-level sgml-basic-offset) + (ruby-mode ruby-indent-level) + (ruby-ts-mode ruby-indent-level) + (rust-mode rust-indent-offset) + (rust-ts-mode rust-indent-offset + rust-ts-mode-indent-offset) + (rustic-mode rustic-indent-offset) + (scala-mode scala-indent:step) + (scss-mode css-indent-offset) + (sgml-mode sgml-basic-offset) + (sh-mode sh-basic-offset sh-indentation) + (swift-mode swift-mode:basic-offset) + (bash-ts-mode sh-basic-offset sh-indentation) + (slim-mode slim-indent-offset) + (sml-mode sml-indent-level) + (tcl-mode tcl-indent-level + tcl-continued-indent-level) + (terra-mode terra-indent-level) + (toml-ts-mode toml-ts-mode-indent-offset) + (typescript-mode typescript-indent-level) + (typescript-ts-base-mode typescript-ts-mode-indent-offset) + (verilog-mode verilog-indent-level + verilog-indent-level-behavioral + verilog-indent-level-declaration + verilog-indent-level-module + verilog-cexp-indent + verilog-case-indent) + (web-mode (web-mode-indent-style . (lambda (size) 2)) + web-mode-attr-indent-offset + web-mode-attr-value-indent-offset + web-mode-code-indent-offset + web-mode-css-indent-offset + web-mode-markup-indent-offset + web-mode-sql-indent-offset + web-mode-block-padding + web-mode-script-padding + web-mode-style-padding) + (yaml-mode yaml-indent-offset) + (yaml-ts-mode yaml-indent-offset) + (zig-mode zig-indent-offset)) + "Alist of indentation setting methods by modes. + +Each element looks like (MODE . FUNCTION) or (MODE . INDENT-SPEC-LIST). + +If FUNCTION is provided, it will be called when setting the +indentation. The indent size will be passed. + +If INDENT-SPEC-LIST is provided, each element of it must have one of the +following forms: + + 1. VARIABLE + It means (VARIABLE . 1). + + 2. (VARIABLE . SPEC) + Setting VARIABLE according to the type of SPEC: + + - Integer + The value is (* SPEC INDENT-SIZE); + + - Function + The value is (funcall SPEC INDENT-SIZE); + + - Any other type. + The value is SPEC. + +NOTE: Only the **buffer local** value of VARIABLE will be set." + :type '(alist :key-type symbol :value-type sexp) + :risky t + :group 'editorconfig) + +(defcustom editorconfig-exclude-modes () + "Modes in which `editorconfig-mode-apply' will not run." + :type '(repeat (symbol :tag "Major Mode")) + :group 'editorconfig) + +(defcustom editorconfig-exclude-regexps () + "List of regexp for buffer filenames `editorconfig-mode-apply' will not run. + +When variable `buffer-file-name' matches any of the regexps, then +`editorconfig-mode-apply' will not do its work." + :type '(repeat string) + :group 'editorconfig) +(with-eval-after-load 'recentf + (add-to-list 'editorconfig-exclude-regexps + (rx-to-string '(seq string-start + (eval (file-truename (expand-file-name recentf-save-file)))) + t))) + +(defcustom editorconfig-trim-whitespaces-mode nil + "Buffer local minor-mode to use to trim trailing whitespaces. + +If set, enable that mode when `trim_trailing_whitespace` is set to true. +Otherwise, use `delete-trailing-whitespace'." + :type 'symbol + :group 'editorconfig) + +(defvar editorconfig-properties-hash nil + "Hash object of EditorConfig properties that was enabled for current buffer. +Set by `editorconfig-apply' and nil if that is not invoked in +current buffer yet.") +(make-variable-buffer-local 'editorconfig-properties-hash) +(put 'editorconfig-properties-hash + 'permanent-local + t) + +(defvar editorconfig-lisp-use-default-indent nil + "Selectively ignore the value of indent_size for Lisp files. +Prevents `lisp-indent-offset' from being set selectively. + +nil - `lisp-indent-offset' is always set normally. +t - `lisp-indent-offset' is never set normally + (always use default indent for lisps). +number - `lisp-indent-offset' is not set only if indent_size is + equal to this number. For example, if this is set to 2, + `lisp-indent-offset' will not be set only if indent_size is 2.") + +(define-error 'editorconfig-error + "Error thrown from editorconfig lib") + +(defun editorconfig-error (&rest args) + "Signal an `editorconfig-error'. +Make a message by passing ARGS to `format-message'." + (signal 'editorconfig-error (list (apply #'format-message args)))) + +(defun editorconfig--disabled-for-filename (filename) + "Return non-nil when EditorConfig is disabled for FILENAME." + (cl-assert (stringp filename)) + (cl-loop for regexp in editorconfig-exclude-regexps + if (string-match regexp filename) return t + finally return nil)) + +(defun editorconfig--disabled-for-majormode (majormode) + "Return non-nil when Editorconfig is disabled for MAJORMODE." + (cl-assert majormode) + (or (provided-mode-derived-p majormode 'special-mode) + ;; Some special modes (like `archive-mode') are not derived from + ;; `special-mode' + (eq (get majormode 'mode-class) 'special) + (memq majormode + editorconfig-exclude-modes))) + +(defun editorconfig-string-integer-p (string) + "Return non-nil if STRING represents integer." + (and (stringp string) + (string-match-p "\\`[0-9]+\\'" string))) + +(defun editorconfig-set-indentation-python-mode (size) + "Set `python-mode' indent size to SIZE." + (when (boundp 'python-indent-offset) + (setq-local python-indent-offset size)) + ;; For https://gitlab.com/python-mode-devs/python-mode + (when (boundp 'py-indent-offset) + (setq-local py-indent-offset size))) + +(defun editorconfig-set-indentation-latex-mode (size) + "Set `latex-mode' indent size to SIZE." + (setq-local tex-indent-basic size) + (setq-local tex-indent-item size) + (setq-local tex-indent-arg (* 2 size)) + ;; For AUCTeX + (when (boundp 'TeX-brace-indent-level) + (setq-local TeX-brace-indent-level size)) + (when (boundp 'LaTeX-indent-level) + (setq-local LaTeX-indent-level size)) + (when (boundp 'LaTeX-item-indent) + (setq-local LaTeX-item-indent (- size)))) + +(defun editorconfig--should-set (size symbol) + "Determines if editorconfig should set SYMBOL using SIZE." + (if (eq symbol 'lisp-indent-offset) + (cond + ((null editorconfig-lisp-use-default-indent) t) + ((eql t editorconfig-lisp-use-default-indent) nil) + ((numberp editorconfig-lisp-use-default-indent) + (not (eql size editorconfig-lisp-use-default-indent))) + (t t)) + t)) + +(defun editorconfig-set-indentation (style &optional size tab_width) + "Set indentation type from STYLE, SIZE and TAB_WIDTH." + (if (editorconfig-string-integer-p size) + (setq size (string-to-number size)) + (unless (equal size "tab") (setq size nil))) + (cond (tab_width + (setq tab-width (string-to-number tab_width))) + ((numberp size) + (setq tab-width size))) + (when (equal size "tab") + (setq size tab-width)) + (cond ((equal style "space") + (setq indent-tabs-mode nil)) + ((equal style "tab") + (setq indent-tabs-mode t))) + (when size + (when (featurep 'evil) + (setq-local evil-shift-width size)) + (let ((parent major-mode) + entry) + ;; Find the closet parent mode of `major-mode' in + ;; `editorconfig-indentation-alist'. + (while (and (not (setq entry (assoc parent editorconfig-indentation-alist))) + (setq parent (get parent 'derived-mode-parent)))) + (when entry + (let ((fn-or-list (cdr entry))) + (cond ((functionp fn-or-list) (funcall fn-or-list size)) + ((listp fn-or-list) + (dolist (elem fn-or-list) + (cond ((and (symbolp elem) + (editorconfig--should-set size elem)) + (set (make-local-variable elem) size)) + ((and (consp elem) + (editorconfig--should-set size (car elem))) + (let ((spec (cdr elem))) + (set (make-local-variable (car elem)) + (cond ((functionp spec) (funcall spec size)) + ((integerp spec) (* spec size)) + (t spec)))))))))))))) + +(defvar-local editorconfig--apply-coding-system-currently nil + "Used internally.") +(put 'editorconfig--apply-coding-system-currently + 'permanent-local + t) + +(defun editorconfig-merge-coding-systems (end-of-line charset) + "Return merged coding system symbol of END-OF-LINE and CHARSET." + (let ((eol (cond + ((equal end-of-line "lf") 'undecided-unix) + ((equal end-of-line "cr") 'undecided-mac) + ((equal end-of-line "crlf") 'undecided-dos) + (t 'undecided))) + (cs (cond + ((equal charset "latin1") 'iso-latin-1) + ((equal charset "utf-8") 'utf-8) + ((equal charset "utf-8-bom") 'utf-8-with-signature) + ((equal charset "utf-16be") 'utf-16be-with-signature) + ((equal charset "utf-16le") 'utf-16le-with-signature) + (t 'undecided)))) + (merge-coding-systems cs eol))) + +(cl-defun editorconfig-set-coding-system-revert (end-of-line charset) + "Set buffer coding system by END-OF-LINE and CHARSET. + +This function will revert buffer when the coding-system has been changed." + ;; `editorconfig--advice-find-file-noselect' does not use this function + (let ((coding-system (editorconfig-merge-coding-systems end-of-line + charset))) + (display-warning '(editorconfig editorconfig-set-coding-system-revert) + (format "editorconfig-set-coding-system-revert: buffer-file-name: %S | buffer-file-coding-system: %S | coding-system: %S | apply-currently: %S" + buffer-file-name + buffer-file-coding-system + coding-system + editorconfig--apply-coding-system-currently) + :debug) + (when (eq coding-system 'undecided) + (cl-return-from editorconfig-set-coding-system-revert)) + (when (and buffer-file-coding-system + (memq buffer-file-coding-system + (coding-system-aliases (merge-coding-systems coding-system + buffer-file-coding-system)))) + (cl-return-from editorconfig-set-coding-system-revert)) + (unless (file-readable-p buffer-file-name) + (set-buffer-file-coding-system coding-system) + (cl-return-from editorconfig-set-coding-system-revert)) + (unless (memq coding-system + (coding-system-aliases editorconfig--apply-coding-system-currently)) + ;; Revert functions might call editorconfig-apply again + (unwind-protect + (progn + (setq editorconfig--apply-coding-system-currently coding-system) + ;; Revert without query if buffer is not modified + (let ((revert-without-query '("."))) + (revert-buffer-with-coding-system coding-system))) + (setq editorconfig--apply-coding-system-currently nil))))) + +(defun editorconfig-set-trailing-nl (final-newline) + "Set up requiring final newline by FINAL-NEWLINE. + +This function will set `require-final-newline' and `mode-require-final-newline' +to non-nil when FINAL-NEWLINE is true." + (pcase final-newline + ("true" + ;; keep prefs around how/when the nl is added, if set - otherwise add on save + (setq-local require-final-newline (or require-final-newline t)) + (setq-local mode-require-final-newline (or mode-require-final-newline t))) + ("false" + ;; FIXME: Add functionality for actually REMOVING any trailing newlines here! + ;; (rather than just making sure we don't automagically ADD a new one) + (setq-local require-final-newline nil) + (setq-local mode-require-final-newline nil)))) + +(defun editorconfig-set-trailing-ws (trim-trailing-ws) + "Set up trimming of trailing whitespace at end of lines by TRIM-TRAILING-WS." + (make-local-variable 'write-file-functions) ;; just current buffer + (when (and (equal trim-trailing-ws "true") + (not buffer-read-only)) + ;; when true we push delete-trailing-whitespace (emacs > 21) + ;; to write-file-functions + (if editorconfig-trim-whitespaces-mode + (funcall editorconfig-trim-whitespaces-mode 1) + (add-to-list 'write-file-functions 'delete-trailing-whitespace))) + (when (or (equal trim-trailing-ws "false") + buffer-read-only) + ;; when false we remove every delete-trailing-whitespace + ;; from write-file-functions + (when editorconfig-trim-whitespaces-mode + (funcall editorconfig-trim-whitespaces-mode 0)) + (setq write-file-functions + (remove 'delete-trailing-whitespace write-file-functions)))) + +(defun editorconfig-set-line-length (length) + "Set the max line length (`fill-column') to LENGTH." + (when (and (editorconfig-string-integer-p length) + (> (string-to-number length) 0)) + (setq fill-column (string-to-number length)))) + + +(defun editorconfig--execute-editorconfig-exec (filename) + "Execute EditorConfig core with FILENAME and return output." + (if filename + (with-temp-buffer + (let ((remote (file-remote-p filename)) + (remote-localname (file-remote-p filename + 'localname))) + (display-warning '(editorconfig editorconfig--execute-editorconfig-exec) + (format "editorconfig--execute-editorconfig-exec: filename: %S | remote: %S | remote-localname: %S" + filename + remote + remote-localname) + :debug) + (if remote + (progn + (cd (concat remote "/")) + (setq filename remote-localname)) + (cd "/"))) + (display-warning '(editorconfig editorconfig--execute-editorconfig-exec) + (format "editorconfig--execute-editorconfig-exec: default-directory: %S | filename: %S" + default-directory + filename + ) + :debug) + (if (eq 0 + (process-file editorconfig-exec-path nil t nil filename)) + (buffer-string) + (editorconfig-error (buffer-string)))) + "")) + +(defun editorconfig--parse-properties (props-string) + "Create properties hash table from PROPS-STRING." + (let ((props-list (split-string props-string "\n")) + (properties (make-hash-table))) + (dolist (prop props-list properties) + (let ((key-val (split-string prop " *= *"))) + (when (> (length key-val) 1) + (let ((key (intern (car key-val))) + (val (mapconcat 'identity (cdr key-val) ""))) + (puthash key val properties))))))) + +(defun editorconfig-get-properties-from-exec (filename) + "Get EditorConfig properties of file FILENAME. + +This function uses value of `editorconfig-exec-path' to get properties." + (if (executable-find editorconfig-exec-path) + (editorconfig--parse-properties (editorconfig--execute-editorconfig-exec filename)) + (editorconfig-error "Unable to find editorconfig executable"))) + +(defun editorconfig-get-properties (filename) + "Get EditorConfig properties for file FILENAME. + +It calls `editorconfig-get-properties-from-exec' if +`editorconfig-exec-path' is found, otherwise +`editorconfig-core-get-properties-hash'." + (if (and (executable-find editorconfig-exec-path) + (not (file-remote-p filename))) + (editorconfig-get-properties-from-exec filename) + (require 'editorconfig-core) + (editorconfig-core-get-properties-hash filename))) + +(defun editorconfig-call-get-properties-function (filename) + "Call `editorconfig-get-properties-function' with FILENAME and return result. + +This function also removes `unset' properties and calls +`editorconfig-hack-properties-functions'." + (unless (functionp editorconfig-get-properties-function) + (editorconfig-error "Invalid editorconfig-get-properties-function value")) + (if (stringp filename) + (setq filename (expand-file-name filename)) + (editorconfig-error "Invalid argument: %S" filename)) + (let ((props nil)) + (condition-case err + (setq props (funcall editorconfig-get-properties-function + filename)) + (error + (editorconfig-error "Error from editorconfig-get-properties-function: %S" + err))) + (cl-loop for k being the hash-keys of props using (hash-values v) + when (equal v "unset") do (remhash k props)) + props)) + +(defun editorconfig-set-local-variables (props) + "Set buffer variables according to EditorConfig PROPS." + (editorconfig-set-indentation (gethash 'indent_style props) + (gethash 'indent_size props) + (gethash 'tab_width props)) + (editorconfig-set-trailing-nl (gethash 'insert_final_newline props)) + (editorconfig-set-trailing-ws (gethash 'trim_trailing_whitespace props)) + (editorconfig-set-line-length (gethash 'max_line_length props))) + + +(defun editorconfig-major-mode-hook () + "Function to run when `major-mode' has been changed. + +This functions does not reload .editorconfig file, just sets local variables +again. Changing major mode can reset these variables. + +This function also executes `editorconfig-after-apply-functions' functions." + (display-warning '(editorconfig editorconfig-major-mode-hook) + (format "editorconfig-major-mode-hook: editorconfig-mode: %S, major-mode: %S, -properties-hash: %S" + (and (boundp 'editorconfig-mode) + editorconfig-mode) + major-mode + editorconfig-properties-hash) + :debug) + (when (and (bound-and-true-p editorconfig-mode) + editorconfig-properties-hash) + (editorconfig-set-local-variables editorconfig-properties-hash) + (condition-case err + (run-hook-with-args 'editorconfig-after-apply-functions editorconfig-properties-hash) + (error + (display-warning '(editorconfig editorconfig-major-mode-hook) + (format "Error while running `editorconfig-after-apply-functions': %S" + err)))))) + +(defvar editorconfig--cons-filename-codingsystem nil + "Used interally. + +`editorconfig--advice-find-file-noselect' will set this variable, and +`editorconfig--advice-insert-file-contents' will use this variable to set +`coding-system-for-read' value.") + +(defun editorconfig--advice-insert-file-contents (f filename &rest args) + "Set `coding-system-for-read'. + +This function should be added as an advice function to `insert-file-contents'. +F is that function, and FILENAME and ARGS are arguments passed to F." + ;; This function uses `editorconfig--cons-filename-codingsystem' to decide what coding-system + ;; should be used, which will be set by `editorconfig--advice-find-file-noselect'. + (display-warning '(editorconfig editorconfig--advice-insert-file-contents) + (format "editorconfig--advice-insert-file-contents: filename: %S args: %S codingsystem: %S bufferfilename: %S" + filename args + editorconfig--cons-filename-codingsystem + buffer-file-name) + :debug) + (if (and (stringp filename) + (stringp (car editorconfig--cons-filename-codingsystem)) + (string= (expand-file-name filename) + (car editorconfig--cons-filename-codingsystem)) + (cdr editorconfig--cons-filename-codingsystem) + (not (eq (cdr editorconfig--cons-filename-codingsystem) + 'undecided))) + (let ((coding-system-for-read (cdr editorconfig--cons-filename-codingsystem)) + ;; (coding-system-for-read 'undecided) + ) + (apply f filename args)) + (apply f filename args))) + +(defun editorconfig--advice-find-file-noselect (f filename &rest args) + "Get EditorConfig properties and apply them to buffer to be visited. + +This function should be added as an advice function to `find-file-noselect'. +F is that function, and FILENAME and ARGS are arguments passed to F." + (let ((props nil) + (coding-system nil) + (ret nil)) + (condition-case err + (when (and (stringp filename) + (not (editorconfig--disabled-for-filename filename))) + (setq props (editorconfig-call-get-properties-function filename)) + (setq coding-system + (editorconfig-merge-coding-systems (gethash 'end_of_line props) + (gethash 'charset props)))) + (error + (display-warning '(editorconfig editorconfig--advice-find-file-noselect) + (format "Failed to get properties, styles will not be applied: %S" + err) + :warning))) + + (let ((editorconfig--cons-filename-codingsystem (cons (expand-file-name filename) + coding-system))) + (setq ret (apply f filename args))) + + (condition-case err + (with-current-buffer ret + (when (and props + ;; filename has already been checked + (not (editorconfig--disabled-for-majormode major-mode))) + (when (and (file-remote-p filename) + (not (local-variable-p 'buffer-file-coding-system)) + (not (file-exists-p filename)) + coding-system + (not (eq coding-system + 'undecided))) + ;; When file path indicates it is a remote file and it actually + ;; does not exists, `buffer-file-coding-system' will not be set. + ;; (Seems `insert-file-contents' will not be called) + ;; For that case, explicitly set this value so that saving will be done + ;; with expected coding system. + (set-buffer-file-coding-system coding-system)) + + ;; NOTE: When using editorconfig-2-mode, hack-properties-functions cannot affect coding-system value, + ;; because it has to be set before initializing buffers. + (condition-case err + (run-hook-with-args 'editorconfig-hack-properties-functions props) + (error + (display-warning '(editorconfig editorconfig-hack-properties-functions) + (format "Error while running editorconfig-hack-properties-functions, abort running hook: %S" + err) + :warning))) + (setq editorconfig-properties-hash props) + ;; When initializing buffer, `editorconfig-major-mode-hook' + ;; will be called before setting `editorconfig-properties-hash', so + ;; execute this explicitly here. + (editorconfig-set-local-variables props) + + (condition-case err + (run-hook-with-args 'editorconfig-after-apply-functions props) + (error + (display-warning '(editorconfig editorconfig--advice-find-file-noselect) + (format "Error while running `editorconfig-after-apply-functions': %S" + err)))))) + (error + (display-warning '(editorconfig editorconfig--advice-find-file-noselect) + (format "Error while setting variables from EditorConfig: %S" err)))) + ret)) + + +;;;###autoload +(define-minor-mode editorconfig-mode + "Toggle EditorConfig feature. + +To disable EditorConfig in some buffers, modify +`editorconfig-exclude-modes' or `editorconfig-exclude-regexps'." + :global t + :lighter editorconfig-mode-lighter + (let ((modehooks '(prog-mode-hook + text-mode-hook + read-only-mode-hook + ;; Some modes call `kill-all-local-variables' in their init + ;; code, which clears some values set by editorconfig. + ;; For those modes, editorconfig-apply need to be called + ;; explicitly through their hooks. + rpm-spec-mode-hook))) + (if editorconfig-mode + (progn + (advice-add 'find-file-noselect :around 'editorconfig--advice-find-file-noselect) + (advice-add 'insert-file-contents :around 'editorconfig--advice-insert-file-contents) + (dolist (hook modehooks) + (add-hook hook + 'editorconfig-major-mode-hook + t))) + (advice-remove 'find-file-noselect 'editorconfig--advice-find-file-noselect) + (advice-remove 'insert-file-contents 'editorconfig--advice-insert-file-contents) + (dolist (hook modehooks) + (remove-hook hook 'editorconfig-major-mode-hook))))) + + +;; (defconst editorconfig--version +;; (eval-when-compile +;; (require 'lisp-mnt) +;; (declare-function lm-version "lisp-mnt" nil) +;; (lm-version)) +;; "EditorConfig version.") + +(declare-function find-library-name "find-func" (library)) +(declare-function lm-version "lisp-mnt" nil) + +;;;###autoload +(defun editorconfig-version (&optional show-version) + "Get EditorConfig version as string. + +If called interactively or if SHOW-VERSION is non-nil, show the +version in the echo area and the messages buffer." + (interactive (list t)) + (let* ((version (with-temp-buffer + (require 'find-func) + (insert-file-contents (find-library-name "editorconfig")) + (require 'lisp-mnt) + (lm-version))) + (pkg (and (eval-and-compile (require 'package nil t)) + (cadr (assq 'editorconfig + package-alist)))) + (pkg-version (and pkg + (package-version-join (package-desc-version pkg)))) + (version-full (if (and pkg-version + (not (string= version pkg-version))) + (concat version "-" pkg-version) + version))) + (when show-version + (message "EditorConfig Emacs v%s" version-full)) + version-full)) + +(provide 'editorconfig) +;;; editorconfig.el ends here + +;; Local Variables: +;; sentence-end-double-space: t +;; End: blob - /dev/null blob + 1b4024afc832eecc4997745254bd44559e22ffb2 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0/editorconfig.info @@ -0,0 +1,353 @@ +This is doc369236.info, produced by makeinfo version 6.8 from +editorconfig.texi. + +INFO-DIR-SECTION Emacs +START-INFO-DIR-ENTRY +* EditorConfig: (editorconfig). EditorConfig Emacs Plugin. +END-INFO-DIR-ENTRY + + +File: doc369236.info, Node: Top, Next: Getting Started, Up: (dir) + +1 EditorConfig Emacs Plugin +*************************** + +This is an EditorConfig (https://editorconfig.org) plugin for Emacs +(https://www.gnu.org/software/emacs/). + +* Menu: + +* Getting Started:: +* Supported properties:: +* Customize:: +* Troubleshooting:: +* Submitting Bugs and Feature Requests:: +* License:: + + +File: doc369236.info, Node: Getting Started, Next: Supported properties, Prev: Top, Up: Top + +1.1 Getting Started +=================== + +* Menu: + +* packageel:: +* use-package:: +* Manual installation:: + + +File: doc369236.info, Node: packageel, Next: use-package, Up: Getting Started + +1.1.1 package.el +---------------- + +This package is available from MELPA (https://melpa.org/#/editorconfig), +MELPA Stable (https://stable.melpa.org/#/editorconfig) and NonGNU ELPA +(http://elpa.nongnu.org/nongnu/editorconfig.html). Install from these +repositories and enable global minor-mode 'editorconfig-mode': + +(editorconfig-mode 1) + + Normally, enabling 'editorconfig-mode' should be enough for this +plugin to work: all other configurations are optional. This mode sets +up hooks so that EditorConfig properties will be loaded and applied to +the new buffers automatically when visiting files. + + +File: doc369236.info, Node: use-package, Next: Manual installation, Prev: packageel, Up: Getting Started + +1.1.2 use-package +----------------- + +If you use *use-package* (https://www.emacswiki.org/emacs/UsePackage), +add the following to your 'init.el' file: + +(use-package editorconfig + :ensure t + :config + (editorconfig-mode 1)) + + +File: doc369236.info, Node: Manual installation, Prev: use-package, Up: Getting Started + +1.1.3 Manual installation +------------------------- + +Copy all '.el' files in this repository to '~/.emacs.d/lisp' and add the +following: + +(add-to-list 'load-path "~/.emacs.d/lisp") +(require 'editorconfig) +(editorconfig-mode 1) + + +File: doc369236.info, Node: Supported properties, Next: Customize, Prev: Getting Started, Up: Top + +1.2 Supported properties +======================== + +Current Emacs plugin coverage for EditorConfig's properties +(https://editorconfig.org/#supported-properties): + + * 'indent_style' + * 'indent_size' + * 'tab_width' + * 'end_of_line' + * 'charset' + * 'trim_trailing_whitespace' + * 'insert_final_newline = true' is supported + * 'insert_final_newline = false' is not enforced (as in trailing + newlines actually being removed automagically), we just + buffer-locally override any preferences that would auto-add them to + files '.editorconfig' marks as trailing-newline-free + * 'max_line_length' + * 'file_type_ext' (Experimental) (See below) + * 'file_type_emacs' (Experimental) (See below) + * 'root' (only used by EditorConfig core) + + Not yet covered properties marked with over-strike - pull requests +implementing missing features warmly welcomed! Typically, you will want +to tie these to native functionality, or the configuration of existing +packages handling the feature. + + As several packages have their own handling of, say, indentation, we +might not yet cover some mode you use, but we try to add the ones that +show up on our radar. + +* Menu: + +* File Type file_type_ext file_type_emacs:: + + +File: doc369236.info, Node: File Type file_type_ext file_type_emacs, Up: Supported properties + +1.2.1 File Type (file_type_ext, file_type_emacs) +------------------------------------------------ + +File-type feature is currently disabled, because this package is now +undergoing big internal refactoring. For those who want this +functionality, please consider using editorconfig-custom-majormode +(https://github.com/10sr/editorconfig-custom-majormode-el). + + +File: doc369236.info, Node: Customize, Next: Troubleshooting, Prev: Supported properties, Up: Top + +1.3 Customize +============= + +'editorconfig-emacs' provides some customize variables. + + Here are some of these variables: for the full list of available +variables, type M-x customize-group [RET] editorconfig [RET]. + +* Menu: + +* editorconfig-trim-whitespaces-mode:: +* editorconfig-after-apply-functions:: +* editorconfig-hack-properties-functions:: + + +File: doc369236.info, Node: editorconfig-trim-whitespaces-mode, Next: editorconfig-after-apply-functions, Up: Customize + +1.3.1 'editorconfig-trim-whitespaces-mode' +------------------------------------------ + +Buffer local minor-mode to use to trim trailing whitespaces. + + If set, editorconfig will enable/disable this mode in accord with +'trim_trailing_whitespace' property in '.editorconfig'. Otherwise, use +Emacs built-in 'delete-trailing-whitespace' function. + + One possible value is 'ws-butler-mode' +(https://github.com/lewang/ws-butler), with which only lines touched get +trimmed. To use it, add following to your init.el: + +(setq editorconfig-trim-whitespaces-mode + 'ws-butler-mode) + + +File: doc369236.info, Node: editorconfig-after-apply-functions, Next: editorconfig-hack-properties-functions, Prev: editorconfig-trim-whitespaces-mode, Up: Customize + +1.3.2 'editorconfig-after-apply-functions' +------------------------------------------ + +(Formerly 'editorconfig-custom-hooks') + + A list of functions which will be called after loading common +EditorConfig settings, when you can set some custom variables. + + For example, 'web-mode' has several variables for indentation offset +size and EditorConfig sets them at once by 'indent_size'. You can stop +indenting only blocks of 'web-mode' by adding following to your init.el: + +(add-hook 'editorconfig-after-apply-functions + (lambda (props) (setq web-mode-block-padding 0))) + + +File: doc369236.info, Node: editorconfig-hack-properties-functions, Prev: editorconfig-after-apply-functions, Up: Customize + +1.3.3 'editorconfig-hack-properties-functions' +---------------------------------------------- + +A list of functions to alter property values before applying them. + + These functions will be run after loading ".editorconfig" files and +before applying them to current buffer, so that you can alter some +properties from ".editorconfig" before they take effect. + + For example, Makefile files always use tab characters for +indentation: you can overwrite "indent_style" property when current +'major-mode' is 'makefile-mode': + +(add-hook 'editorconfig-hack-properties-functions + '(lambda (props) + (when (derived-mode-p 'makefile-mode) + (puthash 'indent_style "tab" props)))) + + +File: doc369236.info, Node: Troubleshooting, Next: Submitting Bugs and Feature Requests, Prev: Customize, Up: Top + +1.4 Troubleshooting +=================== + +Enabling 'editorconfig-mode' should be enough for normal cases. + + When EditorConfig properties are not effective for unknown reason, we +recommend first trying 'M-x editorconfig-display-current-properties'. + + This command will open a new buffer and display the EditorConfig +properties loaded for current buffer. You can check if EditorConfig +properties were not read for buffers at all, or they were loaded but did +not take effect for some other reasons. + +* Menu: + +* Indentation for new major-modes:: +* Not work at all for FOO-mode!:: + + +File: doc369236.info, Node: Indentation for new major-modes, Next: Not work at all for FOO-mode!, Up: Troubleshooting + +1.4.1 Indentation for new major-modes +------------------------------------- + +Because most Emacs major-modes have their own indentation settings, this +plugin requires explicit support for each major-mode for 'indent_size' +property. + + By default this plugin ships with settings for many major-modes, but, +sorry to say, it cannot be perfect. Especially it is difficult to +support brand-new major-modes. Please feel free to submit issue or +pull-request for such major-mode! + + Supported major-modes and their indentation configs are defined in +the variable 'editorconfig-indentation-alist'. + + +File: doc369236.info, Node: Not work at all for FOO-mode!, Prev: Indentation for new major-modes, Up: Troubleshooting + +1.4.2 Not work at all for FOO-mode! +----------------------------------- + +Most cases properties are loaded just after visiting files when +'editorconfig-mode' is enabled. But it is known that there are +major-modes that this mechanism does not work for and require explicit +call of 'editorconfig-apply'. + + Typically it will occur when the major-mode is not defined using +'define-derived-mode' ('rpm-spec-mode' is an example for this). Please +feel free to submit issues if you find such modes! + + +File: doc369236.info, Node: Submitting Bugs and Feature Requests, Next: License, Prev: Troubleshooting, Up: Top + +1.5 Submitting Bugs and Feature Requests +======================================== + +Bugs, feature requests, and other issues should be submitted to the +issue tracker: https://github.com/editorconfig/editorconfig-emacs/issues + +* Menu: + +* Development:: + + +File: doc369236.info, Node: Development, Up: Submitting Bugs and Feature Requests + +1.5.1 Development +----------------- + +Make and CMake (https://cmake.org) must be installed to run the tests +locally: + +$ make check + + To start a new Emacs process with current '*.el' and without loading +user init file, run: + +$ make sandbox + + +File: doc369236.info, Node: License, Prev: Submitting Bugs and Feature Requests, Up: Top + +1.6 License +=========== + +EditorConfig Emacs Plugin 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 . + + + +Tag Table: +Node: Top205 +Ref: #editorconfig-emacs-plugin334 +Node: Getting Started584 +Ref: #getting-started724 +Node: packageel788 +Ref: #package.el907 +Node: use-package1471 +Ref: #use-package1620 +Node: Manual installation1808 +Ref: #manual-installation1955 +Node: Supported properties2130 +Ref: #supported-properties2286 +Node: File Type file_type_ext file_type_emacs3460 +Ref: #file-type-file_type_ext-file_type_emacs3658 +Node: Customize3917 +Ref: #customize4051 +Node: editorconfig-trim-whitespaces-mode4370 +Ref: #editorconfig-trim-whitespaces-mode4583 +Node: editorconfig-after-apply-functions5074 +Ref: #editorconfig-after-apply-functions5334 +Node: editorconfig-hack-properties-functions5820 +Ref: #editorconfig-hack-properties-functions6045 +Node: Troubleshooting6655 +Ref: #troubleshooting6817 +Node: Indentation for new major-modes7357 +Ref: #indentation-for-new-major-modes7558 +Node: Not work at all for FOO-mode!8074 +Ref: #not-work-at-all-for-foo-mode8271 +Node: Submitting Bugs and Feature Requests8693 +Ref: #submitting-bugs-and-feature-requests8895 +Node: Development9063 +Ref: #development9187 +Node: License9391 +Ref: #license9511 + +End Tag Table + + +Local Variables: +coding: utf-8 +End: blob - bbde9fd1f6166ef94007e680b12daa867cc4b9b1 (mode 644) blob + /dev/null --- elpa/editorconfig-0.10.1.signed +++ /dev/null @@ -1,2 +0,0 @@ -Good signature from 066DAFCB81E42C40 GNU ELPA Signing Agent (2019) (trust undefined) created at 2023-05-19T11:05:05+0200 using RSA -Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) (trust undefined) created at 2023-05-19T11:05:05+0200 using EDDSA \ No newline at end of file blob - /dev/null blob + f72a058811fabe0b74bdd821148a10327528e812 (mode 644) --- /dev/null +++ elpa/editorconfig-0.11.0.signed @@ -0,0 +1 @@ +Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) (trust undefined) created at 2024-05-11T11:10:04+0200 using EDDSA \ No newline at end of file blob - 6d22de33f570450fc5a630a3b4d331ce4aeccc53 (mode 644) blob + /dev/null --- elpa/embark-1.0/.dir-locals.el +++ /dev/null @@ -1,6 +0,0 @@ -;;; Directory Local Variables -;;; For more information see (info "(emacs) Directory Variables") - -((emacs-lisp-mode - (show-trailing-whitespace . t) - (indent-tabs-mode . nil))) blob - 7a694c9699a986b9adf1f6cb8a18a6e923e47ed9 (mode 644) blob + /dev/null --- elpa/embark-1.0/.elpaignore +++ /dev/null @@ -1 +0,0 @@ -LICENSE \ No newline at end of file blob - 9cadf6d74d03b179ae017c1674c76b1eb739269d (mode 644) blob + /dev/null --- elpa/embark-1.0/CHANGELOG.org +++ /dev/null @@ -1,85 +0,0 @@ -#+title: Embark changelog - -* Version 1.0 (2023-12-08) -- You can now use around action hooks with multitarget actions (that - you couldn't previously was an oversight). -- Users of the =embark-consult= package can now use consult async search - commands such as =consult-grep= as multitarget actions (through - =embark-act-all=) to search a list of files. For example, you can use - =consult-find= to search among file /names/ and once you have the - relevant files in the minibuffer, you can use =embark-act-all= to - search for some text in those files. When acting on buffers consult - async search commands will search the associated file if there is - one, or else the =default-directory= of the buffer. -- =embark-bindings= and similar commands now show definition of keyboard - macros. -- =embark-org= now recognizes Org links in non-org buffers. -- Now pressing RET in an =embark-collect= on a selection made by - using =embark-select= in a normal buffer will take you to the location - each target was collected from. -- Some functions renamed for greater consistency (these functions are - unlikely to be referred to in user's configuration): - - =embark-target-completion-at-point= → =embark-target-completion-list-candidate= - - =embark-target-top-minibuffer-completion= → =embark-target-top-minibuffer-candidate= - - =embark-completions-buffer-candidates= → =embark-completion-list-candidates= -* Version 0.23 (2023-09-19) -- Added a mode line indicator showing the number of selected targets in - the current buffer (contributed by @minad, thanks!) -- Now =embark-select= can also be called as a top-level command, from - outside =embark-act=. When called that way, it will select the first - target at point. -- =embark-org= now has support for acting on references to org headings - in other buffers, by jumping to the heading first and then running - the action. One source of references to org headings in other - buffers are agenda views: each agenda item is such a reference. But - this feature also supports some great third party commands which - produce references to org headings, such as =org-ql-find= from the - =org-ql= package or =consult-org-heading= from =consult=. -- Renamed =embark-isearch= to =embark-isearch-forward= and added - =embark-isearch-backward=. -- =embark-become= now removes any invisible text from the minibuffer - input on the grounds that users probably expect the target command - to receive exactly the input they can see. -- The meaning of the prefix argument in =embark-bindings= has flipped: - now by default global key bindings are excluded and you can use =C-u= - to include them. -- If any candidate in an embark-collect buffer contains a newline, - then candidates will be separated by horizontal lines. This is handy - for the kill-ring, which you can browse by calling =embark-collect= - from =yank-pop=. -* Version 0.22.1 (2023-04-20) -** New feature: selections -Now users can select several targets to make an ad hoc collection. The -commands =embark-act-all=, =embark-export= and =embark-collect= will act on -the selection if it is non-empty. To select or deselect a target use -the =embark-select= action (bound to =SPC= in =embark-general-map=). If you -have some targets selected, then using =embark-select= through -=embark-act-all= will deselect them. - -Before this change the Embark Collect buffers had their own -implementation of selections which has been removed. This is how to -translate the old bindings to the new feature (which is available in -all buffers, not just Embark Collect buffers!): - -| Task | Old binding | New binding | -|--------------------+-------------+---------------| -| Mark a candidate | m | a SPC | -| Unmark a candidate | u | a SPC | -| Unmark all | U | A SPC | -| Mark all [1] | t | A SPC | -| Toggle all marks | t | not available | - -[1] Marking all candidates (with either the old =t= or the new =A SPC=) -requires that there are no marked candidates to begin with. - -In order to make room for the binding of =embark-select= to -=SPC=, some other key bindings were moved: - -- =mark= in =embark-general-map= was moved to =C-SPC=. -- =outline-mark-subtree= in =embark-heading-map= was moved to =C-SPC=. -- =whitespace-cleanup-region= in =embark-region-map= was moved to =F=. - -* Version 0.21.1 (2020-01-30) -- Finally started this changelog on 2023-04-20. Known issues with the - changelog: it started very late, the first entry is not very - informative. blob - 655de6a04de6a2caa29977fb12cccfc6c80f5e80 (mode 644) blob + /dev/null --- elpa/embark-1.0/README-elpa +++ /dev/null @@ -1,1392 +0,0 @@ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - EMBARK: EMACS MINI-BUFFER ACTIONS ROOTED IN - KEYMAPS - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - - - -1 Overview -══════════ - - Embark makes it easy to choose a command to run based on what is near - point, both during a minibuffer completion session (in a way familiar - to Helm or Counsel users) and in normal buffers. Bind the command - `embark-act' to a key and it acts like prefix-key for a keymap of - /actions/ (commands) relevant to the /target/ around point. With point - on an URL in a buffer you can open the URL in a browser or eww or - download the file it points to. If while switching buffers you spot an - old one, you can kill it right there and continue to select another. - Embark comes preconfigured with over a hundred actions for common - types of targets such as files, buffers, identifiers, s-expressions, - sentences; and it is easy to add more actions and more target types. - Embark can also collect all the candidates in a minibuffer to an - occur-like buffer or export them to a buffer in a major-mode specific - to the type of candidates, such as dired for a set of files, ibuffer - for a set of buffers, or customize for a set of variables. - - -1.1 Acting on targets -───────────────────── - - You can think of `embark-act' as a keyboard-based version of a - right-click contextual menu. The `embark-act' command (which you - should bind to a convenient key), acts as a prefix for a keymap - offering you relevant /actions/ to use on a /target/ determined by the - context: - - • In the minibuffer, the target is the current top completion - candidate. - • In the `*Completions*' buffer the target is the completion at point. - • In a regular buffer, the target is the region if active, or else the - file, symbol, URL, s-expression or defun at point. - - Multiple targets can be present at the same location and you can cycle - between them by repeating the `embark-act' key binding. The type of - actions offered depend on the type of the target. Here is a sample of - a few of the actions offered in the default configuration: - - • For files you get offered actions like deleting, copying, renaming, - visiting in another window, running a shell command on the file, - etc. - • For buffers the actions include switching to or killing the buffer. - • For package names the actions include installing, removing or - visiting the homepage. - • For Emacs Lisp symbols the actions include finding the definition, - looking up documentation, evaluating (which for a variable - immediately shows the value, but for a function lets you pass it - some arguments first). There are some actions specific to variables, - such as setting the value directly or though the customize system, - and some actions specific to commands, such as binding it to a key. - - By default when you use `embark-act' if you don't immediately select - an action, after a short delay Embark will pop up a buffer showing a - list of actions and their corresponding key bindings. If you are using - `embark-act' outside the minibuffer, Embark will also highlight the - current target. These behaviors are configurable via the variable - `embark-indicators'. Instead of selecting an action via its key - binding, you can select it by name with completion by typing `C-h' - after `embark-act'. - - Everything is easily configurable: determining the current target, - classifying it, and deciding which actions are offered for each type - in the classification. The above introduction just mentions part of - the default configuration. - - Configuring which actions are offered for a type is particularly easy - and requires no programming: the variable `embark-keymap-alist' - associates target types with variables containing keymaps, and those - keymaps containing bindings for the actions. (To examine the available - categories and their associated keymaps, you can use `C-h v - embark-keymap-alist' or customize that variable.) For example, in the - default configuration the type `file' is associated with the symbol - `embark-file-map'. That symbol names a keymap with single-letter key - bindings for common Emacs file commands, for instance `c' is bound to - `copy-file'. This means that if you are in the minibuffer after - running a command that prompts for a file, such as `find-file' or - `rename-file', you can copy a file by running `embark-act' and then - pressing `c'. - - These action keymaps are very convenient but not strictly necessary - when using `embark-act': you can use any command that reads from the - minibuffer as an action and the target of the action will be inserted - at the first minibuffer prompt. After running `embark-act' all of your - key bindings and even `execute-extended-command' can be used to run a - command. For example, if you want to replace all occurrences of the - symbol at point, just use `M-%' as the action, there is no need to - bind `query-replace' in one of Embark's keymaps. Also, those action - keymaps are normal Emacs keymaps and you should feel free to bind in - them whatever commands you find useful as actions and want to be - available through convenient bindings. - - The actions in `embark-general-map' are available no matter what type - of completion you are in the middle of. By default this includes - bindings to save the current candidate in the kill ring and to insert - the current candidate in the previously selected buffer (the buffer - that was current when you executed a command that opened up the - minibuffer). - - Emacs's minibuffer completion system includes metadata indicating the - /category/ of what is being completed. For example, `find-file''s - metadata indicates a category of `file' and `switch-to-buffer''s - metadata indicates a category of `buffer'. Embark has the related - notion of the /type/ of a target for actions, and by default when - category metadata is present it is taken to be the type of minibuffer - completion candidates when used as targets. Emacs commands often do - not set useful category metadata so the [Marginalia] package, which - supplies this missing metadata, is highly recommended for use with - Embark. - - Embark's default configuration has actions for the following target - types: files, buffers, symbols, packages, URLs, bookmarks, and as a - somewhat special case, actions for when the region is active. You can - read about the [default actions and their key bindings] on the GitHub - project wiki. - - -[Marginalia] - -[default actions and their key bindings] - - - -1.2 The default action on a target -────────────────────────────────── - - Embark has a notion of default action for a target: - - • If the target is a minibuffer completion candidate, then the default - action is whatever command opened the minibuffer in the first place. - For example if you run `kill-buffer', then the default action will - be to kill buffers. - • If the target comes from a regular buffer (i.e., not a minibuffer), - then the default action is whatever is bound to `RET' in the keymap - of actions for that type of target. For example, in Embark's default - configuration for a URL found at point the default action is - `browse-url', because `RET' is bound to `browse-url' in the - `embark-url-map' keymap. - - To run the default action you can press `RET' after running - `embark-act'. Note that if there are several different targets at a - given location, each has its own default action, so first cycle to the - target you want and then press `RET' to run the corresponding default - action. - - There is also `embark-dwim' which runs the default action for the - first target found. It's pretty handy in non-minibuffer buffers: with - Embark's default configuration it will: - - • Open the file at point. - • Open the URL at point in a web browser (using the `browse-url' - command). - • Compose a new email to the email address at point. - • In an Emacs Lisp buffer, if point is on an opening parenthesis or - right after a closing one, it will evaluate the corresponding - expression. - • Go to the definition of an Emacs Lisp function, variable or macro at - point. - • Find the file corresponding to an Emacs Lisp library at point. - - -1.3 Working with sets of possible targets -───────────────────────────────────────── - - Besides acting individually on targets, Embark lets you work - collectively on a set of target /candidates/. For example, while you - are in the minibuffer the candidates are simply the possible - completions of your input. Embark provides three main commands to work - on candidate sets: - - • The `embark-act-all' command runs the same action on each of the - current candidates. It is just like using `embark-act' on each - candidate in turn. (Because you can easily act on many more - candidates than you meant to, by default Embark asks you to confirm - uses of `embark-act-all'; you can turn this off by setting the user - option `embark-confirm-act-all' to `nil'.) - - • The `embark-collect' command produces a buffer listing all the - current candidates, for you to peruse and run actions on at your - leisure. The candidates are displayed as a list showing additional - annotations. If any of the candidates contain newlines, then - horizontal lines are used to separate candidates. - - The Embark Collect buffer is somewhat "dired-like": you can select - and deselect candidates through `embark-select' (available as an - action in `embark-act', bound to `SPC'; but you could also give it a - global key binding). In an Embark Collect buffer `embark-act' is - bound to `a' and `embark-act-all' is bound to `A'; `embark-act-all' - will act on all currently marked candidates if there any, and will - act on all candidates if none are marked. In particular, this means - that `a SPC' will toggle whether the candidate at point is selected, - and `A SPC' will select all candidates if none are selected, or - deselect all selected candidates if there are some. - - • The `embark-export' command tries to open a buffer in an appropriate - major mode for the set of candidates. If the candidates are files - export produces a Dired buffer; if they are buffers, you get an - Ibuffer buffer; and if they are packages you get a buffer in package - menu mode. - - If you use the grepping commands from the [Consult] package, - `consult-grep', `consult-git-grep' or `consult-ripgrep', then you - should install the `embark-consult' package, which adds support for - exporting a list of grep results to an honest grep-mode buffer, on - which you can even use [wgrep] if you wish. - - When in doubt choosing between exporting and collecting, a good rule - of thumb is to always prefer `embark-export' since when an exporter to - a special major mode is available for a given type of target, it will - be more featureful than an Embark collect buffer, and if no such - exporter is configured the `embark-export' command falls back to the - generic `embark-collect'. - - These commands are always available as "actions" (although they do not - act on just the current target but on all candidates) for `embark-act' - and are bound to `A', `S' (for "snapshot"), and `E', respectively, in - `embark-general-map'. This means that you do not have to bind your own - key bindings for these (although you can, of course!), just a key - binding for `embark-act'. - - In Embark Collect or Embark Export buffers that were obtained by - running `embark-collect' or `embark-export' from within a minibuffer - completion session, `g' is bound to a command that restarts the - completion session, that is, the command that opened the minibuffer is - run again and the minibuffer contents restored. You can then interact - normally with the command, perhaps editing the minibuffer contents, - and, if you wish, you can rerun `embark-collect' or `embark-export' to - get an updated buffer. - - -[Consult] - -[wgrep] - -1.3.1 Selecting some targets to make an ad hoc candidate set -╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - - The commands for working with sets of candidates just described, - namely `embark-act-all', `embark-export' and `embark-collect' by - default work with all candidates defined in the current context. For - example, in the minibuffer they operate on all currently completion - candidates, or in a dired buffer they work on all marked files (or all - files if none are marked). Embark also has a notion of /selection/, - where you can accumulate an ad hoc list of targets for these commands - to work on. - - The selection is controlled by using the `embark-select' action, bound - to `SPC' in `embark-general-map' so that it is always available (you - can also give `embark-select' a global key binding if you wish; when - called directly, not as an action for `embark-act', it will select the - first target at point). Calling this action on a target toggles its - membership in the current buffer's Embark selection; that is, it adds - it to selection if not selected and removes it from the selection if - it was selected. Whenever the selection for a buffer is non-empty, the - commands `embark-act-all', `embark-export' and `embark-collect' will - act on the selection. - - To deselect all selected targets, you can use the `embark-select' - action through `embark-act-all', since this will run `embark-select' - on each member of the current selection. Similarly if no targets are - selected and you are in a minibuffer completion session, running - `embark-select' from `embark-act-all' will select all the current - completion candidates. - - By default, whenever some targets are selected in the current buffer, - a count of selected targets appears in the mode line. This can be - turned off or customized through the `embark-selection-indicator' user - option. - - The selection functionality is supported in every buffer: - - • In the minibuffer this gives a convenient way to act on several - completion candidates that don't follow any simple pattern: just go - through the completions selecting the ones you want, then use - `embark-act-all'. For example, you could attach several files at - once to an email. - • For Embark Collect buffers this functionality enables a dired-like - workflow, in which you mark various candidates and apply an action - to all at once. (It supersedes a previous ad hoc dired-like - interface that was implemented only in Embark Collect buffers, with - a slightly different interface.) - • In a eww buffer you could use this to select various links you wish - to follow up on, and then collect them into a buffer. Similarly, - while reading Emacs's info manual you could select some symbols you - want to read more about and export them to an `apropos-mode' buffer. - • You can use selections in regular text or programming buffers to do - complex editing operations. For example, if you have three - paragraphs scattered over a file and you want to bring them - together, you can select each one, insert them all somewhere and - finally delete all of them (from their original locations). - - -1.3.2 `embark-live' a live-updating variant of `embark-collect' -╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - - Finally, there is also an `embark-live' variant of the - `embark-collect' command which automatically updates the collection - after each change in the source buffer. Users of a completion UI that - automatically updates and displays the candidate list (such as - Vertico, Icomplete, Fido-mode, or MCT) will probably not want to use - `embark-live' from the minibuffer as they will then have two live - updating displays of the completion candidates! - - A more likely use of `embark-live' is to be called from a regular - buffer to display a sort of live updating "table of contents" for the - buffer. This depends on having appropriate candidate collectors - configured in `embark-candidate-collectors'. There are not many in - Embark's default configuration, but you can try this experiment: open - a dired buffer in a directory that has very many files, mark a few, - and run `embark-live'. You'll get an Embark Collect buffer containing - only the marked files, which updates as you mark or unmark files in - dired. To make `embark-live' genuinely useful other candidate - collectors are required. The `embark-consult' package (documented - near the end of this manual) contains a few: one for imenu items and - one for outline headings as used by `outline-minor-mode'. Those - collectors really do give `embark-live' a table-of-contents feel. - - -1.4 Switching to a different command without losing what you've typed -───────────────────────────────────────────────────────────────────── - - Embark also has the `embark-become' command which is useful for when - you run a command, start typing at the minibuffer and realize you - meant a different command. The most common case for me is that I run - `switch-to-buffer', start typing a buffer name and realize I haven't - opened the file I had in mind yet! I'll use this situation as a - running example to illustrate `embark-become'. When this happens I - can, of course, press `C-g' and then run `find-file' and open the - file, but this requires retyping the portion of the file name you - already typed. This process can be streamlined with `embark-become': - while still in the `switch-to-buffer' you can run `embark-become' and - effectively make the `switch-to-buffer' command become `find-file' for - this run. - - You can bind `embark-become' to a key in `minibuffer-local-map', but - it is also available as an action under the letter `B' (uppercase), so - you don't need a binding if you already have one for `embark-act'. So, - assuming I have `embark-act' bound to, say, `C-.', once I realize I - haven't open the file I can type `C-. B C-x C-f' to have - `switch-to-buffer' become `find-file' without losing what I have - already typed in the minibuffer. - - But for even more convenience, `embark-become' offers shorter key - bindings for commands you are likely to want the current command to - become. When you use `embark-become' it looks for the current command - in all keymaps named in the list `embark-become-keymaps' and then - activates all keymaps that contain it. For example, the default value - of `embark-become-keymaps' contains a keymap - `embark-become-file+buffer-map' with bindings for several commands - related to files and buffers, in particular, it binds - `switch-to-buffer' to `b' and `find-file' to `f'. So when I - accidentally try to switch to a buffer for a file I haven't opened - yet, `embark-become' finds that the command I ran, `switch-to-buffer', - is in the keymap `embark-become-file+buffer-map', so it activates that - keymap (and any others that also contain a binding for - `switch-to-buffer'). The end result is that I can type `C-. B f' to - switch to `find-file'. - - -2 Quick start -═════════════ - - The easiest way to install Embark is from GNU ELPA, just run `M-x - package-install RET embark RET'. (It is also available on MELPA.) It - is highly recommended to also install [Marginalia] (also available on - GNU ELPA), so that Embark can offer you preconfigured actions in more - contexts. For `use-package' users, the following is a very reasonable - starting configuration: - - ┌──── - │ (use-package marginalia - │ :ensure t - │ :config - │ (marginalia-mode)) - │ - │ (use-package embark - │ :ensure t - │ - │ :bind - │ (("C-." . embark-act) ;; pick some comfortable binding - │ ("C-;" . embark-dwim) ;; good alternative: M-. - │ ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' - │ - │ :init - │ - │ ;; Optionally replace the key help with a completing-read interface - │ (setq prefix-help-command #'embark-prefix-help-command) - │ - │ ;; Show the Embark target at point via Eldoc. You may adjust the Eldoc - │ ;; strategy, if you want to see the documentation from multiple providers. - │ (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target) - │ ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) - │ - │ :config - │ - │ ;; Hide the mode line of the Embark live/completions buffers - │ (add-to-list 'display-buffer-alist - │ '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" - │ nil - │ (window-parameters (mode-line-format . none))))) - │ - │ ;; Consult users will also want the embark-consult package. - │ (use-package embark-consult - │ :ensure t ; only need to install it, embark loads it after consult if found - │ :hook - │ (embark-collect-mode . consult-preview-at-point-mode)) - └──── - - About the suggested key bindings for `embark-act' and `embark-dwim': - • Those key bindings are unlikely to work in the terminal, but - terminal users are probably well aware of this and will know to - select different bindings. - • The suggested `C-.' binding is used by default in (at least some - installations of) GNOME to input emojis, and Emacs doesn't even get - a chance to respond to the binding. You can select a different key - binding for `embark-act' or use `ibus-setup' to change the shortcut - for emoji insertion (Emacs 29 will likely use `C-x 8 e e', in case - you want to set the same one system-wide). - • The suggested alternative of `M-.' for `embark-dwim' is bound by - default to `xref-find-definitions'. That is a very useful command - but overwriting it with `embark-dwim' is sensible since in Embark's - default configuration, `embark-dwim' will also find the definition - of the identifier at point. (Note that `xref-find-definitions' with - a prefix argument prompts you for an identifier, `embark-dwim' does - not cover this case). - - Other Embark commands such as `embark-act-all', `embark-become', - `embark-collect', and `embark-export' can be run through `embark-act' - as actions bound to `A', `B', `S' (for "snapshot"), and `E' - respectively, and thus don't really need a dedicated key binding, but - feel free to bind them directly if you so wish. If you do choose to - bind them directly, you'll probably want to bind them in - `minibuffer-local-map', since they are most useful in the minibuffer - (in fact, `embark-become' only works in the minibuffer). - - The command `embark-dwim' executes the default action at - point. Another good keybinding for `embark-dwim' is `M-.' since - `embark-dwim' acts like `xref-find-definitions' on the symbol at - point. `C-.' can be seen as a right-click context menu at point and - `M-.' acts like left-click. The keybindings are mnemonic, both act at - the point (`.'). - - Embark needs to know what your minibuffer completion system considers - to be the list of candidates and which one is the current candidate. - Embark works out of the box if you use Emacs's default tab completion, - the built-in `icomplete-mode' or `fido-mode', or the third-party - packages [Vertico] or [Ivy]. - - If you are a [Helm] or [Ivy] user you are unlikely to want Embark - since those packages include comprehensive functionality for acting on - minibuffer completion candidates. (Embark does come with Ivy - integration despite this.) - - -[Marginalia] - -[Vertico] - -[Ivy] - -[Helm] - - -3 Advanced configuration -════════════════════════ - -3.1 Showing information about available targets and actions -─────────────────────────────────────────────────────────── - - By default, if you run `embark-act' and do not immediately select an - action, after a short delay Embark will pop up a buffer called - `*Embark Actions*' containing a list of available actions with their - key bindings. You can scroll that buffer with the mouse of with the - usual commands `scroll-other-window' and `scroll-other-window-down' - (bound by default to `C-M-v' and `C-M-S-v'). - - That functionality is provided by the `embark-mixed-indicator', but - Embark has other indicators that can provide information about the - target and its type, what other targets you can cycle to, and which - actions have key bindings in the action map for the current type of - target. Any number of indicators can be active at once and the user - option `embark-indicators' should be set to a list of the desired - indicators. - - Embark comes with the following indicators: - - • `embark-minimal-indicator': shows a messages in the echo area or - minibuffer prompt showing the current target and the types of all - targets starting with the current one; this one is on by default. - - • `embark-highlight-indicator': highlights the target at point; also - on by default. - - • `embark-verbose-indicator': displays a table of actions and their - key bindings in a buffer; this is not on by default, in favor of the - mixed indicator described next. - - • `embark-mixed-indicator': starts out by behaving as the minimal - indicator but after a short delay acts as the verbose indicator; - this is on by default. - - • `embark-isearch-highlight-indicator': this only does something when - the current target is the symbol at point, in which case it lazily - highlights all occurrences of that symbol in the current buffer, - like isearch; also on by default. - - Users of the popular [which-key] package may prefer to use the - `embark-which-key-indicator' from the [Embark wiki]. Just copy its - definition from the wiki into your configuration and customize the - `embark-indicators' user option to exclude the mixed and verbose - indicators and to include `embark-which-key-indicator'. - - -[which-key] - -[Embark wiki] - - - -3.2 Selecting commands via completions instead of key bindings -────────────────────────────────────────────────────────────── - - As an alternative to reading the list of actions in the verbose or - mixed indicators (see the previous section for a description of - these), you can press the `embark-help-key', which is `C-h' by default - (but you may prefer `?' to free up `C-h' for use as a prefix) after - running `embark-act'. Pressing the help key will prompt you for the - name of an action with completion (but feel free to enter a command - that is not among the offered candidates!), and will also remind you - of the key bindings. You can press `embark-keymap-prompter-key', which - is `@' by default, at the prompt and then one of the key bindings to - enter the name of the corresponding action. - - You may think that with the `*Embark Actions*' buffer popping up to - remind you of the key bindings you'd never want to use completion to - select an action by name, but personally I find that typing a small - portion of the action name to narrow down the list of candidates feels - significantly faster than visually scanning the entire list of - actions. - - If you find you prefer entering actions that way, you can configure - embark to always prompt you for actions by setting the variable - `embark-prompter' to `embark-completing-read-prompter'. - - -3.3 Quitting the minibuffer after an action -─────────────────────────────────────────── - - By default, if you call `embark-act' from the minibuffer it quits the - minibuffer after performing the action. You can change this by setting - the user option `embark-quit-after-action' to `nil'. Having - `embark-act' /not/ quit the minibuffer can be useful to turn commands - into little "thing managers". For example, you can use `find-file' as - a little file manager or `describe-package' as a little package - manager: you can run those commands, perform a series of actions, and - then quit the command. - - If you want to control the quitting behavior in a fine-grained manner - depending on the action, you can set `embark-quit-after-action' to an - alist, associating commands to either `t' for quitting or `nil' for - not quitting. When using an alist, you can use the special key `t' to - specify the default behavior. For example, to specify that by default - actions should not quit the minibuffer but that using `kill-buffer' as - an action should quit, you can use the following configuration: - - ┌──── - │ (setq embark-quit-after-action '((kill-buffer . t) (t . nil))) - └──── - - The variable `embark-quit-after-action' only specifies a default, that - is, it only controls whether or not `embark-act' quits the minibuffer - when you call it without a prefix argument, and you can select the - opposite behavior to what the variable says by calling `embark-act' - with `C-u'. Also note that both the variable - `embark-quit-after-action' and `C-u' have no effect when you call - `embark-act' outside the minibuffer. - - If you find yourself using the quitting and non-quitting variants of - `embark-act' about equally often, independently of the action, you may - prefer to simply have separate commands for them instead of a single - command that you call with `C-u' half the time. You could, for - example, keep the default exiting behavior of `embark-act' and define - a non-quitting version as follows: - - ┌──── - │ (defun embark-act-noquit () - │ "Run action but don't quit the minibuffer afterwards." - │ (interactive) - │ (let ((embark-quit-after-action nil)) - │ (embark-act))) - └──── - - -3.4 Running some setup after injecting the target -───────────────────────────────────────────────── - - You can customize what happens after the target is inserted at the - minibuffer prompt of an action. There are - `embark-target-injection-hooks', that are run by default after - injecting the target into the minibuffer. The variable - `embark-target-injection-hooks' is an alist associating commands to - their setup hooks. There are two special keys: if no setup hook is - specified for a given action, the hook associated to `t' is run; and - the hook associated to `:always' is run regardless of the - action. (This variable used to have the less explicit name of - `embark-setup-action-hooks', so please update your configuration.) - - For example, consider using `shell-command' as an action during file - completion. It would be useful to insert a space before the target - file name and to leave the point at the beginning, so you can - immediately type the shell command to run on that file. That's why in - Embark's default configuration there is an entry in - `embark-target-injection-hooks' associating `shell-command' to a hook - that includes `embark--shell-prep', a simple helper function that - quotes all the spaces in the file name, inserts an extra space at the - beginning of the line and leaves point to the left of it. - - Now, the preparation that `embark--shell-prep' does would be useless - if Embark did what it normally does after it inserts the target of the - action at the minibuffer prompt, which is to "press `RET'" for you, - accepting the target as is; if Embark did that for `shell-command' you - wouldn't get a chance to type in the command to execute! That is why - in Embark's default configuration the entry for `shell-command' in - `embark-target-injection-hooks' also contains the function - `embark--allow-edit'. - - Embark used to have a dedicated variable `embark-allow-edit-actions' - to which you could add commands for which Embark should forgo pressing - `RET' for you after inserting the target. Since its effect can also be - achieved via the general `embark-target-injection-hooks' mechanism, - that variable has been removed to simplify Embark. Be sure to update - your configuration; if you had something like: - - ┌──── - │ (add-to-list 'embark-allow-edit-actions 'my-command) - └──── - - you should replace it with: - - ┌──── - │ (push 'embark--allow-edit - │ (alist-get 'my-command embark-target-injection-hooks)) - └──── - - - Also note that while you could abuse `embark--allow-edit' so that you - have to confirm "dangerous" actions such as `delete-file', it is - better to implement confirmation by adding the `embark--confirm' - function to the appropriate entry of a different hook alist, namely, - `embark-pre-action-hooks'. - - Besides `embark--allow-edit', Embark comes with another function that - is of general utility in action setup hooks: - `embark--ignore-target'. Use it for commands that do prompt you in the - minibuffer but for which inserting the target would be - inappropriate. This is not a common situation but does occasionally - arise. For example it is used by default for - `shell-command-on-region': that command is used as an action for - region targets, and it prompts you for a shell command; you typically - do /not/ want the target, that is the contents of the region, to be - entered at that prompt! - - -3.5 Running hooks before, after or around an action -─────────────────────────────────────────────────── - - Embark has three variables, `embark-pre-action-hooks', - `embark-post-action-hooks' and `embark-around-action-hooks', which are - alists associating commands to hooks that should run before or after - or as around advice for the command when used as an action. As with - `embark-target-injection-hooks', there are two special keys for the - alists: `t' designates the default hook to run when no specific hook - is specified for a command; and the hook associated to `:always' runs - regardless. - - The default values of those variables are fairly extensive, adding - creature comforts to make running actions a smooth experience. Embark - comes with several functions intended to be added to these hooks, and - used in the default values of `embark-pre-action-hooks', - `embark-post-action-hooks' and `embark-around-action-hooks'. - - For pre-action hooks: - - `embark--confirm' - Prompt the user for confirmation before executing the - action. This is used be default for commands deemed "dangerous", - or, more accurately, hard to undo, such as `delete-file' and - `kill-buffer'. - - `embark--unmark-target' - Unmark the active region. Use this for commands you want to act - on the region contents but without the region being active. The - default configuration uses this function as a pre-action hook - for `occur' and `query-replace', for example, so that you can - use them as actions with region targets to search the whole - buffer for the text contained in the region. Without this - pre-action hook using `occur' as an action for a region target - would be pointless: it would search for the the region contents - /in the region/, (typically, due to the details of regexps) - finding only one match! - - `embark--beginning-of-target' - Move to the beginning of the target (for targets that report - bounds). This is used by default for backward motion commands - such as `backward-sexp', so that they don't accidentally leave - you on the current target. - - `embark--end-of-target' - Move to the end of the target. This is used similarly to the - previous function, but also for commands that act on the last - s-expression like `eval-last-sexp'. This allow you to act on an - s-expression from anywhere inside it and still use - `eval-last-sexp' as an action. - - `embark--xref-push-markers' - Push the current location on the xref marker stack. Use this for - commands that take you somewhere and for which you'd like to be - able to come back to where you were using - `xref-pop-marker-stack'. This is used by default for - `find-library'. - - For post-action hooks: - - `embark--restart' - Restart the command currently prompting in the minibuffer, so - that the list of completion candidates is updated. This is - useful as a post action hook for commands that delete or rename - a completion candidate; for example the default value of - `embark-post-action-hooks' uses it for `delete-file', - `kill-buffer', `rename-file', `rename-buffer', etc. - - For around-action hooks: - - `embark--mark-target' - Save existing mark and point location, mark the target and run - the action. Most targets at point outside the minibuffer report - which region of the buffer they correspond to (this is the - information used by `embark-highlight-indicator' to know what - portion of the buffer to highlight); this function marks that - region. It is useful as an around action hook for commands that - expect a region to be marked, for example, it is used by default - for `indent-region' so that it works on s-expression targets, or - for `fill-region' so that it works on paragraph targets. - - `embark--cd' - Run the action with `default-directory' set to the directory - associated to the current target. The target should be of type - `file', `buffer', `bookmark' or `library', and the associated - directory is what you'd expect in each case. - - `embark--narrow-to-target' - Run the action with buffer narrowed to current target. Use this - as an around hook to localize the effect of actions that don't - already work on just the region. In the default configuration it - is used for `repunctuate-sentences'. - - `embark--save-excursion' - Run the action restoring point at the end. The current default - configuration doesn't use this but it is available for users. - - -3.6 Creating your own keymaps -───────────────────────────── - - All internal keymaps are defined with the standard helper macro - `defvar-keymap'. For example a simple version of the file action - keymap could be defined as follows: - - ┌──── - │ (defvar-keymap embark-file-map - │ :doc "Example keymap with a few file actions" - │ :parent embark-general-map - │ "d" #'delete-file - │ "r" #'rename-file - │ "c" #'copy-file) - └──── - - These action keymaps are perfectly normal Emacs keymaps. You may want - to inherit from the `embark-general-map' if you want to access the - default Embark actions. Note that `embark-collect' and `embark-export' - are also made available via `embark-general-map'. - - -3.7 Defining actions for new categories of targets -────────────────────────────────────────────────── - - It is easy to configure Embark to provide actions for new types of - targets, either in the minibuffer or outside it. I present below two - very detailed examples of how to do this. At several points I'll - explain more than one way to proceed, typically with the easiest - option first. I include the alternative options since there will be - similar situations where the easiest option is not available. - - -3.7.1 New minibuffer target example - tab-bar tabs -╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - - As an example, take the new [tab bars] from Emacs 27. I'll explain how - to configure Embark to offer tab-specific actions when you use the - tab-bar-mode commands that mention tabs by name. The configuration - explained here is now built-in to Embark (and Marginalia), but it's - still a good self-contained example. In order to setup up tab actions - you would need to: (1) make sure Embark knows those commands deal with - tabs, (2) define a keymap for tab actions and configure Embark so it - knows that's the keymap you want. - - -[tab bars] - - -◊ 3.7.1.1 Telling Embark about commands that prompt for tabs by name - - For step (1), it would be great if the `tab-bar-mode' commands - reported the completion category `tab' when asking you for a tab with - completion. (All built-in Emacs commands that prompt for file names, - for example, do have metadata indicating that they want a `file'.) - They do not, unfortunately, and I will describe a couple of ways to - deal with this. - - Maybe the easiest thing is to configure [Marginalia] to enhance those - commands. All of the `tab-bar-*-tab-by-name' commands have the words - "tab by name" in the minibuffer prompt, so you can use: - - ┌──── - │ (add-to-list 'marginalia-prompt-categories '("tab by name" . tab)) - └──── - - That's it! But in case you are ever in a situation where you don't - already have commands that prompt for the targets you want, I'll - describe how writing your own command with appropriate `category' - metadata looks: - - ┌──── - │ (defun my-select-tab-by-name (tab) - │ (interactive - │ (list - │ (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) - │ (tab-bar-tabs)) - │ (user-error "No tabs found")))) - │ (completing-read - │ "Tabs: " - │ (lambda (string predicate action) - │ (if (eq action 'metadata) - │ '(metadata (category . tab)) - │ (complete-with-action - │ action tab-list string predicate))))))) - │ (tab-bar-select-tab-by-name tab)) - └──── - - As you can see, the built-in support for setting the category - meta-datum is not very easy to use or pretty to look at. To help with - this I recommend the `consult--read' function from the excellent - [Consult] package. With that function we can rewrite the command as - follows: - - ┌──── - │ (defun my-select-tab-by-name (tab) - │ (interactive - │ (list - │ (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) - │ (tab-bar-tabs)) - │ (user-error "No tabs found")))) - │ (consult--read tab-list - │ :prompt "Tabs: " - │ :category 'tab)))) - │ (tab-bar-select-tab-by-name tab)) - └──── - - Much nicer! No matter how you define the `my-select-tab-by-name' - command, the first approach with Marginalia and prompt detection has - the following advantages: you get the `tab' category for all the - `tab-bar-*-bar-by-name' commands at once, also, you enhance built-in - commands, instead of defining new ones. - - - [Marginalia] - - [Consult] - - -◊ 3.7.1.2 Defining and configuring a keymap for tab actions - - Let's say we want to offer select, rename and close actions for tabs - (in addition to Embark general actions, such as saving the tab name to - the kill-ring, which you get for free). Then this will do: - - ┌──── - │ (defvar-keymap embark-tab-actions - │ :doc "Keymap for actions for tab-bar tabs (when mentioned by name)." - │ :parent embark-general-map - │ "s" #'tab-bar-select-tab-by-name - │ "r" #'tab-bar-rename-tab-by-name - │ "k" #'tab-bar-close-tab-by-name) - │ - │ (add-to-list 'embark-keymap-alist '(tab . embark-tab-actions)) - └──── - - What if after using this for a while you feel closing the tab without - confirmation is dangerous? You have a couple of options: - - 1. You can keep using the `tab-bar-close-tab-by-name' command, but - have Embark ask you for confirmation: - ┌──── - │ (push #'embark--confirm - │ (alist-get 'tab-bar-close-tab-by-name - │ embark-pre-action-hooks)) - └──── - - 2. You can write your own command that prompts for confirmation and - use that instead of `tab-bar-close-tab-by-name' in the above - keymap: - ┌──── - │ (defun my-confirm-close-tab-by-name (tab) - │ (interactive "sTab to close: ") - │ (when (y-or-n-p (format "Close tab '%s'? " tab)) - │ (tab-bar-close-tab-by-name tab))) - └──── - - Notice that this is a command you can also use directly from `M-x' - independently of Embark. Using it from `M-x' leaves something to be - desired, though, since you don't get completion for the tab names. - You can fix this if you wish as described in the previous section. - - -3.7.2 New target example in regular buffers - short Wikipedia links -╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - - Say you want to teach Embark to treat text of the form - `wikipedia:Garry_Kasparov' in any regular buffer as a link to - Wikipedia, with actions to open the Wikipedia page in eww or an - external browser or to save the URL of the page in the kill-ring. We - can take advantage of the actions that Embark has preconfigured for - URLs, so all we need to do is teach Embark that - `wikipedia:Garry_Kasparov' stands for the URL - `https://en.wikipedia.org/wiki/Garry_Kasparov'. - - You can be as fancy as you want with the recognized syntax. Here, to - keep the example simple, I'll assume the link matches the regexp - `wikipedia:[[:alnum:]_]+'. We will write a function that looks for a - match surrounding point, and returns a dotted list of the form `'(url - URL-OF-THE-PAGE START . END)' where `START' and `END' are the buffer - positions bounding the target, and are used by Embark to highlight it - if you have `embark-highlight-indicator' included in the list - `embark-indicators'. (There are a couple of other options for the - return value of a target finder: the bounding positions are optional - and a single target finder is allowed to return multiple targets; see - the documentation for `embark-target-finders' for details.) - - ┌──── - │ (defun my-short-wikipedia-link () - │ "Target a link at point of the form wikipedia:Page_Name." - │ (save-excursion - │ (let* ((start (progn (skip-chars-backward "[:alnum:]_:") (point))) - │ (end (progn (skip-chars-forward "[:alnum:]_:") (point))) - │ (str (buffer-substring-no-properties start end))) - │ (save-match-data - │ (when (string-match "wikipedia:\\([[:alnum:]_]+\\)" str) - │ `(url - │ ,(format "https://en.wikipedia.org/wiki/%s" - │ (match-string 1 str)) - │ ,start . ,end)))))) - │ - │ (add-to-list 'embark-target-finders 'my-short-wikipedia-link) - └──── - - -4 How does Embark call the actions? -═══════════════════════════════════ - - Embark actions are normal Emacs commands, that is, functions with an - interactive specification. In order to execute an action, Embark calls - the command with `call-interactively', so the command reads user input - exactly as if run directly by the user. For example the command may - open a minibuffer and read a string (`read-from-minibuffer') or open a - completion interface (`completing-read'). If this happens, Embark - takes the target string and inserts it automatically into the - minibuffer, simulating user input this way. After inserting the - string, Embark exits the minibuffer, submitting the input. (The - immediate minibuffer exit can be disabled for specific actions in - order to allow editing the input; this is done by adding the - `embark--allow-edit' function to the appropriate entry of - `embark-target-injection-hooks'). Embark inserts the target string at - the first minibuffer opened by the action command, and if the command - happens to prompt the user for input more than once, the user still - interacts with the second and further prompts in the normal - fashion. Note that if a command does not prompt the user for input in - the minibuffer, Embark still allows you to use it as an action, but of - course, never inserts the target anywhere. (There are plenty of - examples in the default configuration of commands that do not prompt - the user bound to keys in the action maps, most of the region actions, - for instance.) - - This is how Embark manages to reuse normal commands as actions. The - mechanism allows you to use as Embark actions commands that were not - written with Embark in mind (and indeed almost all actions that are - bound by default in Embark's action keymaps are standard Emacs - commands). It also allows you to write new custom actions in such a - way that they are useful even without Embark. - - Staring from version 28.1, Emacs has a variable - `y-or-n-p-use-read-key', which when set to `t' causes `y-or-n-p' to - use `read-key' instead of `read-from-minibuffer'. Setting - `y-or-n-p-use-read-key' to `t' is recommended for Embark users because - it keeps Embark from attempting to insert the target at a `y-or-n-p' - prompt, which would almost never be sensible. Also consider this as a - warning to structure your own action commands so that if they use - `y-or-n-p', they do so only after the prompting for the target. - - Here is a simple example illustrating the various ways of reading - input from the user mentioned above. Bind the following commands to - the `embark-symbol-map' to be used as actions, then put the point on - some symbol and run them with `embark-act': - - ┌──── - │ (defun example-action-command1 () - │ (interactive) - │ (message "The input was `%s'." (read-from-minibuffer "Input: "))) - │ - │ (defun example-action-command2 (arg input1 input2) - │ (interactive "P\nsInput 1: \nsInput 2: ") - │ (message "The first input %swas `%s', and the second was `%s'." - │ (if arg "truly " "") - │ input1 - │ input2)) - │ - │ (defun example-action-command3 () - │ (interactive) - │ (message "Your selection was `%s'." - │ (completing-read "Select: " '("E" "M" "B" "A" "R" "K")))) - │ - │ (defun example-action-command4 () - │ (interactive) - │ (message "I don't prompt you for input and thus ignore the target!")) - │ - │ (keymap-set embark-symbol-map "X 1" #'example-action-command1) - │ (keymap-set embark-symbol-map "X 2" #'example-action-command2) - │ (keymap-set embark-symbol-map "X 3" #'example-action-command3) - │ (keymap-set embark-symbol-map "X 4" #'example-action-command4) - └──── - - Also note that if you are using the key bindings to call actions, you - can pass prefix arguments to actions in the normal way. For example, - you can use `C-u X2' with the above demonstration actions to make the - message printed by `example-action-command2' more emphatic. This - ability to pass prefix arguments to actions is useful for some actions - in the default configuration, such as - `embark-shell-command-on-buffer'. - - -4.1 Non-interactive functions as actions -──────────────────────────────────────── - - Alternatively, Embark does support one other type of action: a - non-interactive function of a single argument. The target is passed as - argument to the function. For example: - - ┌──── - │ (defun example-action-function (target) - │ (message "The target was `%s'." target)) - │ - │ (keymap-set embark-symbol-map "X 4" #'example-action-function) - └──── - - Note that normally binding non-interactive functions in a keymap is - useless, since when attempting to run them using the key binding you - get an error message similar to "Wrong type argument: commandp, - example-action-function". In general it is more flexible to write any - new Embark actions as commands, that is, as interactive functions, - because that way you can also run them directly, without Embark. But - there are a couple of reasons to use non-interactive functions as - actions: - - 1. You may already have the function lying around, and it is - convenient to simply reuse it. - - 2. For command actions the targets can only be simple string, with no - text properties. For certain advanced uses you may want the action - to receive a string /with/ some text properties, or even a - non-string target. - - -5 Embark, Marginalia and Consult -════════════════════════════════ - - Embark cooperates well with the [Marginalia] and [Consult] packages. - Neither of those packages is a dependency of Embark, but both are - highly recommended companions to Embark, for opposite reasons: - Marginalia greatly enhances Embark's usefulness, while Embark can help - enhance Consult. - - In the remainder of this section I'll explain what exactly Marginalia - does for Embark, and what Embark can do for Consult. - - -[Marginalia] - -[Consult] - -5.1 Marginalia -────────────── - - Embark comes with actions for symbols (commands, functions, variables - with actions such as finding the definition, looking up the - documentation, evaluating, etc.) in the `embark-symbol-map' keymap, - and for packages (actions like install, delete, browse url, etc.) in - the `embark-package-keymap'. - - Unfortunately Embark does not automatically offers you these keymaps - when relevant, because many built-in Emacs commands don't report - accurate category metadata. For example, a command like - `describe-package', which reads a package name from the minibuffer, - does not have metadata indicating this fact. - - In an earlier Embark version, there were functions to supply this - missing metadata, but they have been moved to Marginalia, which - augments many Emacs command to report accurate category metadata. - Simply activating `marginalia-mode' allows Embark to offer you the - package and symbol actions when appropriate again. Candidate - annotations in the Embark collect buffer are also provided by the - Marginalia package: - - • If you install Marginalia and activate `marginalia-mode', Embark - Collect buffers will use the Marginalia annotations automatically. - - • If you don't install Marginalia, you will see only the annotations - that come with Emacs (such as key bindings in `M-x', or the unicode - characters in `C-x 8 RET'). - - -5.2 Consult -─────────── - - The excellent Consult package provides many commands that use - minibuffer completion, via the `completing-read' function; plenty of - its commands can be considered enhanced versions of built-in Emacs - commands, and some are completely new functionality. One common - enhancement provided in all commands for which it makes sense is - preview functionality, for example `consult-buffer' will show you a - quick preview of a buffer before you actually switch to it. - - If you use both Consult and Embark you should install the - `embark-consult' package which provides integration between the - two. It provides exporters for several Consult commands and also - tweaks the behavior of many Consult commands when used as actions with - `embark-act' in subtle ways that you may not even notice, but make for - a smoother experience. You need only install it to get these benefits: - Embark will automatically load it after Consult if found. - - The `embark-consult' package provides the following exporters: - - • You can use `embark-export' from `consult-line', `consult-outline', - or `consult-mark' to obtain an `occur-mode' buffer. As with the - built-in `occur' command you use that buffer to jump to a match and - after that, you can then use `next-error' and `previous-error' to - navigate to other matches. You can also press `e' to activate - `occur-edit-mode' and edit the matches in place! - - • You can export from any of the Consult asynchronous search commands, - `consult-grep', `consult-git-grep', or `consult-ripgrep' to get a - `grep-mode' buffer. Here too you can use `next-error' and - `previous-error' to navigate among matches, and, if you install the - [wgrep] package, you can use it to edit the matches in place. - - In both cases, pressing `g' will rerun the Consult command you had - exported from and re-enter the input you had typed (which is similar - to reverting but a little more flexible). You can then proceed to - re-export if that's what you want, but you can also edit the input - changing the search terms or simply cancel if you see you are done - with that search. - - The `embark-consult' also contains some candidates collectors that - allow you to run `embark-live' to get a live-updating table of - contents for your buffer: - - • `embark-consult-outline-candidates' produces the outline headings of - the current buffer, using `consult-outline'. - • `embark-consult-imenu-candidates' produces the imenu items of the - current buffer, using `consult-imenu'. - • `embark-consult-imenu-or-outline-candidates' is a simple combination - of the two previous functions: it produces imenu items in buffers - deriving from `prog-mode' and otherwise outline headings. - - The way to configure `embark-live' (or `embark-collect' and - `embark-export' for that matter) to use one of these function is to - add it at the end of the `embark-candidate-collectors' list. The - `embark-consult' package by default adds the last one, which seems to - be the most sensible default. - - Besides those exporters and candidate collectors, the `embark-consult' - package provides many subtle tweaks and small integrations between - Embark and Consult. Some examples are: - - • When used as actions, the asynchronous search commands will search - only the files associated to the targets: if the targets /are/ - files, it searches those files; for buffers it will search either - the associated file if there is one, else all files in the buffer's - `default-directory'; for bookmarks it will search the file they - point to, same for Emacs Lisp libraries. This is particularly - powerful when using `embark-act-all' to act on multiple files at - once, for example you can use `consult-find' to search among file - /names/ and then `embark-act-all' and `consult-grep' to search - within the matching files. - - • For all other target types, those that do not have a sensible - notion of associated file, a Consult search command (asynchronous - or not) will search for the text of the target but leave the - minibuffer open so you can interact with the Consult command. - - • `consult-imenu' will search for the target and take you directly to - the location if it matches a unique imenu entry, otherwise it will - leave the minibuffer open so you can navigate among the matches. - - -[wgrep] - - -6 Related Packages -══════════════════ - - There are several packages that offer functionality similar to - Embark's. - - Acting on minibuffer completion candidates - The popular Ivy and Helm packages have support for acting on the - completion candidates of commands written using their APIs, and - there is an extensive ecosystem of packages meant for Helm and - for Ivy (the Ivy ones usually have "counsel" in the name) - providing commands and appropriate actions. - Acting on things at point - The built-in `context-menu-mode' provides a mouse-driven - context-sensitive configurable menu. The `do-at-point' package - by Philip Kaludercic (available on GNU ELPA), on the other hand - is keyboard-driven. - Collecting completion candidates into a buffer - The Ivy package has the command `ivy-occur' which is similar to - `embark-collect'. As with Ivy actions, `ivy-occur' only works - for commands written using the Ivy API. - - -7 Resources -═══════════ - - If you want to learn more about how others have used Embark here are - some links to read: - - • [Fifteen ways to use Embark], a blog post by Karthik Chikmagalur. - • [Protesilaos Stavrou's dotemacs], look for the section called - "Extended minibuffer actions and more (embark.el and - prot-embark.el)" - - And some videos to watch: - - • [Embark and my extras] by Protesilaos Stavrou. - • [Embark – Key features and tweaks] by Raoul Comninos on the - Emacs-Elements YouTube channel. - • [Livestreamed: Adding an Embark context action to send a stream - message] by Sacha Chua. - • [System Crafters Live! - The Many Uses of Embark] by David Wilson. - • [Using Emacs Episode 80 - Vertico, Marginalia, Consult and Embark] - by Mike Zamansky. - - -[Fifteen ways to use Embark] - - -[Protesilaos Stavrou's dotemacs] - -[Embark and my extras] - - -[Embark – Key features and tweaks] - -[Livestreamed: Adding an Embark context action to send a stream message] - - -[System Crafters Live! - The Many Uses of Embark] - - -[Using Emacs Episode 80 - Vertico, Marginalia, Consult and Embark] - - - -8 Contributions -═══════════════ - - Contributions to Embark are very welcome. There is a [wish list] for - actions, target finders, candidate collectors and exporters. For other - ideas you have for Embark, feel free to open an issue on the [issue - tracker]. Any neat configuration tricks you find might be a good fit - for the [wiki]. - - Code contributions are very welcome too, but since Embark is now on - GNU ELPA, copyright assignment to the FSF is required before you can - contribute code. - - -[wish list] - -[issue tracker] - -[wiki] - - -9 Acknowledgments -═════════════════ - - While I, Omar Antolín Camarena, have written most of the Embark code - and remain very stubborn about some of the design decisions, Embark - has received substantial help from a number of other people which this - document has neglected to mention for far too long. In particular, - Daniel Mendler has been absolutely invaluable, implementing several - important features, and providing a lot of useful advice. - - Code contributions: - - • [Daniel Mendler] - • [Clemens Radermacher] - • [José Antonio Ortega Ruiz] - • [Itai Y. Efrat] - • [a13] - • [jakanakaevangeli] - • [mihakam] - • [Brian Leung] - • [Karthik Chikmagalur] - • [Roshan Shariff] - • [condy0919] - • [Damien Cassou] - • [JimDBh] - - Advice and useful discussions: - - • [Daniel Mendler] - • [Protesilaos Stavrou] - • [Clemens Radermacher] - • [Howard Melman] - • [Augusto Stoffel] - • [Bruce d'Arcus] - • [JD Smith] - • [Karthik Chikmagalur] - • [jakanakaevangeli] - • [Itai Y. Efrat] - • [Mohsin Kaleem] - - -[Daniel Mendler] - -[Clemens Radermacher] - -[José Antonio Ortega Ruiz] - -[Itai Y. Efrat] - -[a13] - -[jakanakaevangeli] - -[mihakam] - -[Brian Leung] - -[Karthik Chikmagalur] - -[Roshan Shariff] - -[condy0919] - -[Damien Cassou] - -[JimDBh] - -[Protesilaos Stavrou] - -[Howard Melman] - -[Augusto Stoffel] - -[Bruce d'Arcus] - -[JD Smith] - -[Mohsin Kaleem] blob - a010932c153e26f07bde44b61c8c9456caab9761 (mode 644) blob + /dev/null --- elpa/embark-1.0/README.org +++ /dev/null @@ -1,1206 +0,0 @@ -#+TITLE: Embark: Emacs Mini-Buffer Actions Rooted in Keymaps -#+OPTIONS: d:nil -#+EXPORT_FILE_NAME: embark.texi -#+TEXINFO_DIR_CATEGORY: Emacs misc features -#+TEXINFO_DIR_TITLE: Embark: (embark). -#+TEXINFO_DIR_DESC: Emacs Mini-Buffer Actions Rooted in Keymaps - -#+html: GNU ELPA -#+html: GNU-devel ELPA -#+html: MELPA -#+html: MELPA Stable - -* Overview - -Embark makes it easy to choose a command to run based on what is near -point, both during a minibuffer completion session (in a way familiar -to Helm or Counsel users) and in normal buffers. Bind the command -=embark-act= to a key and it acts like prefix-key for a keymap of -/actions/ (commands) relevant to the /target/ around point. With point on -an URL in a buffer you can open the URL in a browser or eww or -download the file it points to. If while switching buffers you spot an -old one, you can kill it right there and continue to select another. -Embark comes preconfigured with over a hundred actions for common -types of targets such as files, buffers, identifiers, s-expressions, -sentences; and it is easy to add more actions and more target types. -Embark can also collect all the candidates in a minibuffer to an -occur-like buffer or export them to a buffer in a major-mode specific -to the type of candidates, such as dired for a set of files, ibuffer -for a set of buffers, or customize for a set of variables. - -** Acting on targets - -You can think of =embark-act= as a keyboard-based version of a -right-click contextual menu. The =embark-act= command (which you should -bind to a convenient key), acts as a prefix for a keymap offering you -relevant /actions/ to use on a /target/ determined by the context: - -- In the minibuffer, the target is the current top completion - candidate. -- In the =*Completions*= buffer the target is the completion at point. -- In a regular buffer, the target is the region if active, or else the - file, symbol, URL, s-expression or defun at point. - -Multiple targets can be present at the same location and you can cycle -between them by repeating the =embark-act= key binding. The type of -actions offered depend on the type of the target. Here is a sample of -a few of the actions offered in the default configuration: - -- For files you get offered actions like deleting, copying, - renaming, visiting in another window, running a shell command on the - file, etc. -- For buffers the actions include switching to or killing the buffer. -- For package names the actions include installing, removing or - visiting the homepage. -- For Emacs Lisp symbols the actions include finding the definition, - looking up documentation, evaluating (which for a variable - immediately shows the value, but for a function lets you pass it - some arguments first). There are some actions specific to variables, - such as setting the value directly or though the customize system, - and some actions specific to commands, such as binding it to a key. - -By default when you use =embark-act= if you don't immediately select an -action, after a short delay Embark will pop up a buffer showing a list -of actions and their corresponding key bindings. If you are using -=embark-act= outside the minibuffer, Embark will also highlight the -current target. These behaviors are configurable via the variable -=embark-indicators=. Instead of selecting an action via its key binding, -you can select it by name with completion by typing =C-h= after -=embark-act=. - -Everything is easily configurable: determining the current target, -classifying it, and deciding which actions are offered for each type -in the classification. The above introduction just mentions part of -the default configuration. - -Configuring which actions are offered for a type is particularly easy -and requires no programming: the variable =embark-keymap-alist= -associates target types with variables containing keymaps, and those -keymaps containing bindings for the actions. (To examine the available -categories and their associated keymaps, you can use =C-h v -embark-keymap-alist= or customize that variable.) For example, in the -default configuration the type =file= is associated with the symbol -=embark-file-map=. That symbol names a keymap with single-letter key -bindings for common Emacs file commands, for instance =c= is bound to -=copy-file=. This means that if you are in the minibuffer after running -a command that prompts for a file, such as =find-file= or =rename-file=, -you can copy a file by running =embark-act= and then pressing =c=. - -These action keymaps are very convenient but not strictly necessary -when using =embark-act=: you can use any command that reads from the -minibuffer as an action and the target of the action will be inserted -at the first minibuffer prompt. After running =embark-act= all of your -key bindings and even =execute-extended-command= can be used to run a -command. For example, if you want to replace all occurrences of the -symbol at point, just use =M-%= as the action, there is no need to bind -=query-replace= in one of Embark's keymaps. Also, those action keymaps -are normal Emacs keymaps and you should feel free to bind in them -whatever commands you find useful as actions and want to be available -through convenient bindings. - -The actions in =embark-general-map= are available no matter what type -of completion you are in the middle of. By default this includes -bindings to save the current candidate in the kill ring and to insert -the current candidate in the previously selected buffer (the buffer -that was current when you executed a command that opened up the -minibuffer). - -Emacs's minibuffer completion system includes metadata indicating the -/category/ of what is being completed. For example, =find-file='s -metadata indicates a category of =file= and =switch-to-buffer='s metadata -indicates a category of =buffer=. Embark has the related notion of the -/type/ of a target for actions, and by default when category metadata -is present it is taken to be the type of minibuffer completion -candidates when used as targets. Emacs commands often do not set -useful category metadata so the [[https://github.com/minad/marginalia][Marginalia]] package, which supplies -this missing metadata, is highly recommended for use with Embark. - -Embark's default configuration has actions for the following target -types: files, buffers, symbols, packages, URLs, bookmarks, and as a -somewhat special case, actions for when the region is active. You can -read about the [[https://github.com/oantolin/embark/wiki/Default-Actions][default actions and their key bindings]] on the GitHub -project wiki. - -** The default action on a target - -Embark has a notion of default action for a target: - -- If the target is a minibuffer completion candidate, then the default - action is whatever command opened the minibuffer in the first place. - For example if you run =kill-buffer=, then the default action will be - to kill buffers. -- If the target comes from a regular buffer (i.e., not a minibuffer), - then the default action is whatever is bound to =RET= in the keymap of - actions for that type of target. For example, in Embark's default - configuration for a URL found at point the default action is - =browse-url=, because =RET= is bound to =browse-url= in the =embark-url-map= - keymap. - -To run the default action you can press =RET= after running =embark-act=. -Note that if there are several different targets at a given location, -each has its own default action, so first cycle to the target you want -and then press =RET= to run the corresponding default action. - -There is also =embark-dwim= which runs the default action for the first -target found. It's pretty handy in non-minibuffer buffers: with -Embark's default configuration it will: - -- Open the file at point. -- Open the URL at point in a web browser (using the =browse-url= - command). -- Compose a new email to the email address at point. -- In an Emacs Lisp buffer, if point is on an opening parenthesis or - right after a closing one, it will evaluate the corresponding - expression. -- Go to the definition of an Emacs Lisp function, variable or macro at - point. -- Find the file corresponding to an Emacs Lisp library at point. - -** Working with sets of possible targets - -Besides acting individually on targets, Embark lets you work -collectively on a set of target /candidates/. For example, while you are -in the minibuffer the candidates are simply the possible completions -of your input. Embark provides three main commands to work on candidate -sets: - -- The =embark-act-all= command runs the same action on each of the - current candidates. It is just like using =embark-act= on each - candidate in turn. (Because you can easily act on many more - candidates than you meant to, by default Embark asks you to confirm - uses of =embark-act-all=; you can turn this off by setting the user - option =embark-confirm-act-all= to =nil=.) - -- The =embark-collect= command produces a buffer listing all the current - candidates, for you to peruse and run actions on at your leisure. - The candidates are displayed as a list showing additional - annotations. If any of the candidates contain newlines, then - horizontal lines are used to separate candidates. - - The Embark Collect buffer is somewhat "dired-like": you can select - and deselect candidates through =embark-select= (available as an - action in =embark-act=, bound to =SPC=; but you could also give it a - global key binding). In an Embark Collect buffer =embark-act= is bound - to =a= and =embark-act-all= is bound to =A=; =embark-act-all= will act on - all currently marked candidates if there any, and will act on all - candidates if none are marked. In particular, this means that =a SPC= - will toggle whether the candidate at point is selected, and =A SPC= - will select all candidates if none are selected, or deselect all - selected candidates if there are some. - -- The =embark-export= command tries to open a buffer in an appropriate - major mode for the set of candidates. If the candidates are files - export produces a Dired buffer; if they are buffers, you get an - Ibuffer buffer; and if they are packages you get a buffer in - package menu mode. - - If you use the grepping commands from the [[https://github.com/minad/consult/][Consult]] package, - =consult-grep=, =consult-git-grep= or =consult-ripgrep=, then you should - install the =embark-consult= package, which adds support for exporting a - list of grep results to an honest grep-mode buffer, on which you can - even use [[https://github.com/mhayashi1120/Emacs-wgrep][wgrep]] if you wish. - -When in doubt choosing between exporting and collecting, a good rule -of thumb is to always prefer =embark-export= since when an exporter to a -special major mode is available for a given type of target, it will be -more featureful than an Embark collect buffer, and if no such exporter -is configured the =embark-export= command falls back to the generic -=embark-collect=. - -These commands are always available as "actions" (although they do not -act on just the current target but on all candidates) for =embark-act= -and are bound to =A=, =S= (for "snapshot"), and =E=, respectively, in -=embark-general-map=. This means that you do not have to bind your own -key bindings for these (although you can, of course!), just a key -binding for =embark-act=. - -In Embark Collect or Embark Export buffers that were obtained by -running =embark-collect= or =embark-export= from within a minibuffer -completion session, =g= is bound to a command that restarts the -completion session, that is, the command that opened the minibuffer is -run again and the minibuffer contents restored. You can then interact -normally with the command, perhaps editing the minibuffer contents, -and, if you wish, you can rerun =embark-collect= or =embark-export= to get -an updated buffer. - -*** Selecting some targets to make an ad hoc candidate set - -The commands for working with sets of candidates just described, -namely =embark-act-all=, =embark-export= and =embark-collect= by default -work with all candidates defined in the current context. For example, -in the minibuffer they operate on all currently completion candidates, -or in a dired buffer they work on all marked files (or all files if -none are marked). Embark also has a notion of /selection/, where you can -accumulate an ad hoc list of targets for these commands to work on. - -The selection is controlled by using the =embark-select= action, bound -to =SPC= in =embark-general-map= so that it is always available (you can -also give =embark-select= a global key binding if you wish; when called -directly, not as an action for =embark-act=, it will select the first -target at point). Calling this action on a target toggles its -membership in the current buffer's Embark selection; that is, it adds -it to selection if not selected and removes it from the selection if -it was selected. Whenever the selection for a buffer is non-empty, the -commands =embark-act-all=, =embark-export= and =embark-collect= will act on -the selection. - -To deselect all selected targets, you can use the =embark-select= action -through =embark-act-all=, since this will run =embark-select= on each -member of the current selection. Similarly if no targets are selected -and you are in a minibuffer completion session, running =embark-select= -from =embark-act-all= will select all the current completion candidates. - -By default, whenever some targets are selected in the current buffer, -a count of selected targets appears in the mode line. This can be -turned off or customized through the =embark-selection-indicator= user -option. - -The selection functionality is supported in every buffer: - -- In the minibuffer this gives a convenient way to act on several - completion candidates that don't follow any simple pattern: just go - through the completions selecting the ones you want, then use - =embark-act-all=. For example, you could attach several files at once - to an email. -- For Embark Collect buffers this functionality enables a dired-like - workflow, in which you mark various candidates and apply an action - to all at once. (It supersedes a previous ad hoc dired-like - interface that was implemented only in Embark Collect buffers, with - a slightly different interface.) -- In a eww buffer you could use this to select various links you wish - to follow up on, and then collect them into a buffer. Similarly, - while reading Emacs's info manual you could select some symbols you - want to read more about and export them to an =apropos-mode= buffer. -- You can use selections in regular text or programming buffers to do - complex editing operations. For example, if you have three - paragraphs scattered over a file and you want to bring them - together, you can select each one, insert them all somewhere and - finally delete all of them (from their original locations). - -*** =embark-live= a live-updating variant of =embark-collect= - -Finally, there is also an =embark-live= variant of the =embark-collect= -command which automatically updates the collection after each change -in the source buffer. Users of a completion UI that automatically -updates and displays the candidate list (such as Vertico, Icomplete, -Fido-mode, or MCT) will probably not want to use -=embark-live= from the minibuffer as they will then have two live -updating displays of the completion candidates! - -A more likely use of =embark-live= is to be called from a regular buffer -to display a sort of live updating "table of contents" for the buffer. -This depends on having appropriate candidate collectors configured in -=embark-candidate-collectors=. There are not many in Embark's default -configuration, but you can try this experiment: open a dired buffer in -a directory that has very many files, mark a few, and run =embark-live=. -You'll get an Embark Collect buffer containing only the marked files, -which updates as you mark or unmark files in dired. To make -=embark-live= genuinely useful other candidate collectors are required. -The =embark-consult= package (documented near the end of this manual) -contains a few: one for imenu items and one for outline headings as -used by =outline-minor-mode=. Those collectors really do give -=embark-live= a table-of-contents feel. - -** Switching to a different command without losing what you've typed - -Embark also has the =embark-become= command which is useful for when -you run a command, start typing at the minibuffer and realize you -meant a different command. The most common case for me is that I run -=switch-to-buffer=, start typing a buffer name and realize I haven't -opened the file I had in mind yet! I'll use this situation as a -running example to illustrate =embark-become=. When this happens I can, -of course, press =C-g= and then run =find-file= and open the file, but -this requires retyping the portion of the file name you already -typed. This process can be streamlined with =embark-become=: while still -in the =switch-to-buffer= you can run =embark-become= and effectively -make the =switch-to-buffer= command become =find-file= for this run. - -You can bind =embark-become= to a key in =minibuffer-local-map=, but it is -also available as an action under the letter =B= (uppercase), so you -don't need a binding if you already have one for =embark-act=. So, -assuming I have =embark-act= bound to, say, =C-.=, once I realize I -haven't open the file I can type =C-. B C-x C-f= to have -=switch-to-buffer= become =find-file= without losing what I have already -typed in the minibuffer. - -But for even more convenience, =embark-become= offers shorter key -bindings for commands you are likely to want the current command to -become. When you use =embark-become= it looks for the current command in -all keymaps named in the list =embark-become-keymaps= and then activates -all keymaps that contain it. For example, the default value of -=embark-become-keymaps= contains a keymap =embark-become-file+buffer-map= -with bindings for several commands related to files and buffers, in -particular, it binds =switch-to-buffer= to =b= and =find-file= to =f=. So when -I accidentally try to switch to a buffer for a file I haven't opened -yet, =embark-become= finds that the command I ran, =switch-to-buffer=, is -in the keymap =embark-become-file+buffer-map=, so it activates that -keymap (and any others that also contain a binding for -=switch-to-buffer=). The end result is that I can type =C-. B f= to -switch to =find-file=. - -* Quick start - -The easiest way to install Embark is from GNU ELPA, just run =M-x -package-install RET embark RET=. (It is also available on MELPA.) It is -highly recommended to also install [[https://github.com/minad/marginalia][Marginalia]] (also available on GNU -ELPA), so that Embark can offer you preconfigured actions in more -contexts. For =use-package= users, the following is a very reasonable -starting configuration: - -#+begin_src emacs-lisp - (use-package marginalia - :ensure t - :config - (marginalia-mode)) - - (use-package embark - :ensure t - - :bind - (("C-." . embark-act) ;; pick some comfortable binding - ("C-;" . embark-dwim) ;; good alternative: M-. - ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' - - :init - - ;; Optionally replace the key help with a completing-read interface - (setq prefix-help-command #'embark-prefix-help-command) - - ;; Show the Embark target at point via Eldoc. You may adjust the Eldoc - ;; strategy, if you want to see the documentation from multiple providers. - (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target) - ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) - - :config - - ;; Hide the mode line of the Embark live/completions buffers - (add-to-list 'display-buffer-alist - '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" - nil - (window-parameters (mode-line-format . none))))) - - ;; Consult users will also want the embark-consult package. - (use-package embark-consult - :ensure t ; only need to install it, embark loads it after consult if found - :hook - (embark-collect-mode . consult-preview-at-point-mode)) -#+end_src - -About the suggested key bindings for =embark-act= and =embark-dwim=: -- Those key bindings are unlikely to work in the terminal, but - terminal users are probably well aware of this and will know to - select different bindings. -- The suggested =C-.= binding is used by default in (at least some - installations of) GNOME to input emojis, and Emacs doesn't even get - a chance to respond to the binding. You can select a different key - binding for =embark-act= or use =ibus-setup= to change the shortcut for - emoji insertion (Emacs 29 will likely use =C-x 8 e e=, in case you - want to set the same one system-wide). -- The suggested alternative of =M-.= for =embark-dwim= is bound by default - to =xref-find-definitions=. That is a very useful command but - overwriting it with =embark-dwim= is sensible since in Embark's - default configuration, =embark-dwim= will also find the definition of - the identifier at point. (Note that =xref-find-definitions= with a - prefix argument prompts you for an identifier, =embark-dwim= does not - cover this case). - -Other Embark commands such as =embark-act-all=, =embark-become=, -=embark-collect=, and =embark-export= can be run through =embark-act= as -actions bound to =A=, =B=, =S= (for "snapshot"), and =E= respectively, and -thus don't really need a dedicated key binding, but feel free to bind -them directly if you so wish. If you do choose to bind them directly, -you'll probably want to bind them in =minibuffer-local-map=, since they -are most useful in the minibuffer (in fact, =embark-become= only works -in the minibuffer). - -The command =embark-dwim= executes the default action at point. Another good -keybinding for =embark-dwim= is =M-.= since =embark-dwim= acts like -=xref-find-definitions= on the symbol at point. =C-.= can be seen as a -right-click context menu at point and =M-.= acts like left-click. The -keybindings are mnemonic, both act at the point (=.=). - -Embark needs to know what your minibuffer completion system considers -to be the list of candidates and which one is the current candidate. -Embark works out of the box if you use Emacs's default tab completion, -the built-in =icomplete-mode= or =fido-mode=, or the third-party packages -[[https://github.com/minad/vertico][Vertico]] or [[https://github.com/abo-abo/swiper][Ivy]]. - -If you are a [[https://emacs-helm.github.io/helm/][Helm]] or [[https://github.com/abo-abo/swiper][Ivy]] user you are unlikely to want Embark since -those packages include comprehensive functionality for acting on -minibuffer completion candidates. (Embark does come with Ivy -integration despite this.) - -* Advanced configuration -** Showing information about available targets and actions - -By default, if you run =embark-act= and do not immediately select an -action, after a short delay Embark will pop up a buffer called =*Embark -Actions*= containing a list of available actions with their key -bindings. You can scroll that buffer with the mouse of with the usual -commands =scroll-other-window= and =scroll-other-window-down= (bound by -default to =C-M-v= and =C-M-S-v=). - -That functionality is provided by the =embark-mixed-indicator=, but -Embark has other indicators that can provide information about the -target and its type, what other targets you can cycle to, and which -actions have key bindings in the action map for the current type of -target. Any number of indicators can be active at once and the user -option =embark-indicators= should be set to a list of the desired -indicators. - -Embark comes with the following indicators: - -- =embark-minimal-indicator=: shows a messages in the echo area or - minibuffer prompt showing the current target and the types of all - targets starting with the current one; this one is on by default. - -- =embark-highlight-indicator=: highlights the target at point; - also on by default. - -- =embark-verbose-indicator=: displays a table of actions and their key - bindings in a buffer; this is not on by default, in favor of the - mixed indicator described next. - -- =embark-mixed-indicator=: starts out by behaving as the minimal - indicator but after a short delay acts as the verbose indicator; - this is on by default. - -- =embark-isearch-highlight-indicator=: this only does something when - the current target is the symbol at point, in which case it - lazily highlights all occurrences of that symbol in the current - buffer, like isearch; also on by default. - -Users of the popular [[https://github.com/justbur/emacs-which-key][which-key]] package may prefer to use the -=embark-which-key-indicator= from the [[https://github.com/oantolin/embark/wiki/Additional-Configuration#use-which-key-like-a-key-menu-prompt][Embark wiki]]. Just copy its -definition from the wiki into your configuration and customize the -=embark-indicators= user option to exclude the mixed and verbose -indicators and to include =embark-which-key-indicator=. - -** Selecting commands via completions instead of key bindings - -As an alternative to reading the list of actions in the verbose or -mixed indicators (see the previous section for a description of -these), you can press the =embark-help-key=, which is =C-h= by default -(but you may prefer =?= to free up =C-h= for use as a prefix) after -running =embark-act=. Pressing the help key will prompt you for the name -of an action with completion (but feel free to enter a command that is -not among the offered candidates!), and will also remind you of the -key bindings. You can press =embark-keymap-prompter-key=, which is =@= by -default, at the prompt and then one of the key bindings to enter the -name of the corresponding action. - -You may think that with the =*Embark Actions*= buffer popping up to -remind you of the key bindings you'd never want to use completion to -select an action by name, but personally I find that typing a small -portion of the action name to narrow down the list of candidates feels -significantly faster than visually scanning the entire list of actions. - -If you find you prefer entering actions that way, you can configure -embark to always prompt you for actions by setting the variable -=embark-prompter= to =embark-completing-read-prompter=. - -** Quitting the minibuffer after an action - -By default, if you call =embark-act= from the minibuffer it quits the -minibuffer after performing the action. You can change this by setting -the user option =embark-quit-after-action= to =nil=. Having =embark-act= /not/ -quit the minibuffer can be useful to turn commands into little "thing -managers". For example, you can use =find-file= as a little file manager -or =describe-package= as a little package manager: you can run those -commands, perform a series of actions, and then quit the command. - -If you want to control the quitting behavior in a fine-grained manner -depending on the action, you can set =embark-quit-after-action= to an -alist, associating commands to either =t= for quitting or =nil= for not -quitting. When using an alist, you can use the special key =t= to -specify the default behavior. For example, to specify that by default -actions should not quit the minibuffer but that using =kill-buffer= as -an action should quit, you can use the following configuration: - -#+begin_src emacs-lisp - (setq embark-quit-after-action '((kill-buffer . t) (t . nil))) -#+end_src - -The variable =embark-quit-after-action= only specifies a default, that -is, it only controls whether or not =embark-act= quits the minibuffer -when you call it without a prefix argument, and you can select the -opposite behavior to what the variable says by calling =embark-act= with -=C-u=. Also note that both the variable =embark-quit-after-action= and =C-u= -have no effect when you call =embark-act= outside the minibuffer. - -If you find yourself using the quitting and non-quitting variants of -=embark-act= about equally often, independently of the action, you may -prefer to simply have separate commands for them instead of a single -command that you call with =C-u= half the time. You could, for example, -keep the default exiting behavior of =embark-act= and define a -non-quitting version as follows: - -#+begin_src emacs-lisp - (defun embark-act-noquit () - "Run action but don't quit the minibuffer afterwards." - (interactive) - (let ((embark-quit-after-action nil)) - (embark-act))) -#+end_src - -** Running some setup after injecting the target - -You can customize what happens after the target is inserted at the -minibuffer prompt of an action. There are -=embark-target-injection-hooks=, that are run by default after injecting -the target into the minibuffer. The variable -=embark-target-injection-hooks= is an alist associating commands to -their setup hooks. There are two special keys: if no setup hook is -specified for a given action, the hook associated to =t= is run; and the -hook associated to =:always= is run regardless of the action. (This -variable used to have the less explicit name of -=embark-setup-action-hooks=, so please update your configuration.) - -For example, consider using =shell-command= as an action during file -completion. It would be useful to insert a space before the target -file name and to leave the point at the beginning, so you can -immediately type the shell command to run on that file. That's why in -Embark's default configuration there is an entry in -=embark-target-injection-hooks= associating =shell-command= to a hook that -includes =embark--shell-prep=, a simple helper function that quotes all -the spaces in the file name, inserts an extra space at the beginning -of the line and leaves point to the left of it. - -Now, the preparation that =embark--shell-prep= does would be useless if -Embark did what it normally does after it inserts the target of the -action at the minibuffer prompt, which is to "press =RET=" for you, -accepting the target as is; if Embark did that for =shell-command= you -wouldn't get a chance to type in the command to execute! That is why -in Embark's default configuration the entry for =shell-command= in -=embark-target-injection-hooks= also contains the function -=embark--allow-edit=. - -Embark used to have a dedicated variable =embark-allow-edit-actions= to -which you could add commands for which Embark should forgo pressing -=RET= for you after inserting the target. Since its effect can also be -achieved via the general =embark-target-injection-hooks= mechanism, that -variable has been removed to simplify Embark. Be sure to update your -configuration; if you had something like: - -#+begin_src emacs-lisp - (add-to-list 'embark-allow-edit-actions 'my-command) -#+end_src - -you should replace it with: - -#+begin_src emacs-lisp - (push 'embark--allow-edit - (alist-get 'my-command embark-target-injection-hooks)) -#+end_src - - -Also note that while you could abuse =embark--allow-edit= so that you -have to confirm "dangerous" actions such as =delete-file=, it is better -to implement confirmation by adding the =embark--confirm= function to -the appropriate entry of a different hook alist, namely, -=embark-pre-action-hooks=. - -Besides =embark--allow-edit=, Embark comes with another function that is -of general utility in action setup hooks: =embark--ignore-target=. Use -it for commands that do prompt you in the minibuffer but for which -inserting the target would be inappropriate. This is not a common -situation but does occasionally arise. For example it is used by -default for =shell-command-on-region=: that command is used as an action -for region targets, and it prompts you for a shell command; you -typically do /not/ want the target, that is the contents of the region, -to be entered at that prompt! - -** Running hooks before, after or around an action - -Embark has three variables, =embark-pre-action-hooks=, -=embark-post-action-hooks= and =embark-around-action-hooks=, which are -alists associating commands to hooks that should run before or after -or as around advice for the command when used as an action. As with -=embark-target-injection-hooks=, there are two special keys for the -alists: =t= designates the default hook to run when no specific hook is -specified for a command; and the hook associated to =:always= runs -regardless. - -The default values of those variables are fairly extensive, adding -creature comforts to make running actions a smooth experience. Embark -comes with several functions intended to be added to these hooks, and -used in the default values of =embark-pre-action-hooks=, -=embark-post-action-hooks= and =embark-around-action-hooks=. - -For pre-action hooks: - -- =embark--confirm= :: Prompt the user for confirmation before executing - the action. This is used be default for commands deemed "dangerous", - or, more accurately, hard to undo, such as =delete-file= and - =kill-buffer=. - -- =embark--unmark-target= :: Unmark the active region. Use this for - commands you want to act on the region contents but without the - region being active. The default configuration uses this function as - a pre-action hook for =occur= and =query-replace=, for example, so that - you can use them as actions with region targets to search the whole - buffer for the text contained in the region. Without this pre-action - hook using =occur= as an action for a region target would be - pointless: it would search for the the region contents /in the - region/, (typically, due to the details of regexps) finding only one - match! - -- =embark--beginning-of-target= :: Move to the beginning of the target - (for targets that report bounds). This is used by default for - backward motion commands such as =backward-sexp=, so that they don't - accidentally leave you on the current target. - -- =embark--end-of-target= :: Move to the end of the target. This is used - similarly to the previous function, but also for commands that act - on the last s-expression like =eval-last-sexp=. This allow you to act - on an s-expression from anywhere inside it and still use - =eval-last-sexp= as an action. - -- =embark--xref-push-markers= :: Push the current location on the xref - marker stack. Use this for commands that take you somewhere and for - which you'd like to be able to come back to where you were using - =xref-pop-marker-stack=. This is used by default for =find-library=. - -For post-action hooks: - -- =embark--restart= :: Restart the command currently prompting in the - minibuffer, so that the list of completion candidates is updated. - This is useful as a post action hook for commands that delete or - rename a completion candidate; for example the default value of - =embark-post-action-hooks= uses it for =delete-file=, =kill-buffer=, - =rename-file=, =rename-buffer=, etc. - -For around-action hooks: - -- =embark--mark-target= :: Save existing mark and point location, mark - the target and run the action. Most targets at point outside the - minibuffer report which region of the buffer they correspond to - (this is the information used by =embark-highlight-indicator= to - know what portion of the buffer to highlight); this function marks - that region. It is useful as an around action hook for commands that - expect a region to be marked, for example, it is used by default for - =indent-region= so that it works on s-expression targets, or for - =fill-region= so that it works on paragraph targets. - -- =embark--cd= :: Run the action with =default-directory= set to the - directory associated to the current target. The target should be of - type =file=, =buffer=, =bookmark= or =library=, and the associated directory - is what you'd expect in each case. - -- =embark--narrow-to-target= :: Run the action with buffer narrowed to - current target. Use this as an around hook to localize the effect of - actions that don't already work on just the region. In the default - configuration it is used for =repunctuate-sentences=. - -- =embark--save-excursion= :: Run the action restoring point at the end. - The current default configuration doesn't use this but it is - available for users. - -** Creating your own keymaps - -All internal keymaps are defined with the standard helper macro -=defvar-keymap=. For example a simple version of the file action keymap -could be defined as follows: - -#+BEGIN_SRC emacs-lisp - (defvar-keymap embark-file-map - :doc "Example keymap with a few file actions" - :parent embark-general-map - "d" #'delete-file - "r" #'rename-file - "c" #'copy-file) -#+END_SRC - -These action keymaps are perfectly normal Emacs -keymaps. You may want to inherit from the =embark-general-map= if you -want to access the default Embark actions. Note that =embark-collect= -and =embark-export= are also made available via =embark-general-map=. - -** Defining actions for new categories of targets - -It is easy to configure Embark to provide actions for new types of -targets, either in the minibuffer or outside it. I present below two -very detailed examples of how to do this. At several points I'll -explain more than one way to proceed, typically with the easiest -option first. I include the alternative options since there will be -similar situations where the easiest option is not available. - -*** New minibuffer target example - tab-bar tabs - -As an example, take the new [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Tab-Bars.html][tab bars]] from Emacs 27. I'll explain how -to configure Embark to offer tab-specific actions when you use the -tab-bar-mode commands that mention tabs by name. The configuration -explained here is now built-in to Embark (and Marginalia), but it's -still a good self-contained example. In order to setup up tab actions -you would need to: (1) make sure Embark knows those commands deal with -tabs, (2) define a keymap for tab actions and configure Embark so it -knows that's the keymap you want. - -**** Telling Embark about commands that prompt for tabs by name - -For step (1), it would be great if the =tab-bar-mode= commands reported -the completion category =tab= when asking you for a tab with -completion. (All built-in Emacs commands that prompt for file names, -for example, do have metadata indicating that they want a =file=.) They -do not, unfortunately, and I will describe a couple of ways to deal -with this. - -Maybe the easiest thing is to configure [[https://github.com/minad/marginalia][Marginalia]] to enhance those -commands. All of the =tab-bar-*-tab-by-name= commands have the words -"tab by name" in the minibuffer prompt, so you can use: - -#+begin_src emacs-lisp - (add-to-list 'marginalia-prompt-categories '("tab by name" . tab)) -#+end_src - -That's it! But in case you are ever in a situation where you don't -already have commands that prompt for the targets you want, I'll -describe how writing your own command with appropriate =category= -metadata looks: - -#+begin_src emacs-lisp - (defun my-select-tab-by-name (tab) - (interactive - (list - (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) - (tab-bar-tabs)) - (user-error "No tabs found")))) - (completing-read - "Tabs: " - (lambda (string predicate action) - (if (eq action 'metadata) - '(metadata (category . tab)) - (complete-with-action - action tab-list string predicate))))))) - (tab-bar-select-tab-by-name tab)) -#+end_src - -As you can see, the built-in support for setting the category -meta-datum is not very easy to use or pretty to look at. To help with -this I recommend the =consult--read= function from the excellent -[[https://github.com/minad/consult/][Consult]] package. With that function we can rewrite the command as -follows: - -#+begin_src emacs-lisp - (defun my-select-tab-by-name (tab) - (interactive - (list - (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) - (tab-bar-tabs)) - (user-error "No tabs found")))) - (consult--read tab-list - :prompt "Tabs: " - :category 'tab)))) - (tab-bar-select-tab-by-name tab)) -#+end_src - -Much nicer! No matter how you define the =my-select-tab-by-name= -command, the first approach with Marginalia and prompt detection has -the following advantages: you get the =tab= category for all the -=tab-bar-*-bar-by-name= commands at once, also, you enhance built-in -commands, instead of defining new ones. - -**** Defining and configuring a keymap for tab actions - - Let's say we want to offer select, rename and close actions for tabs - (in addition to Embark general actions, such as saving the tab name to - the kill-ring, which you get for free). Then this will do: - - #+begin_src emacs-lisp - (defvar-keymap embark-tab-actions - :doc "Keymap for actions for tab-bar tabs (when mentioned by name)." - :parent embark-general-map - "s" #'tab-bar-select-tab-by-name - "r" #'tab-bar-rename-tab-by-name - "k" #'tab-bar-close-tab-by-name) - - (add-to-list 'embark-keymap-alist '(tab . embark-tab-actions)) - #+end_src - - What if after using this for a while you feel closing the tab - without confirmation is dangerous? You have a couple of options: - - 1. You can keep using the =tab-bar-close-tab-by-name= command, but have - Embark ask you for confirmation: - #+begin_src emacs-lisp - (push #'embark--confirm - (alist-get 'tab-bar-close-tab-by-name - embark-pre-action-hooks)) - #+end_src - - 2. You can write your own command that prompts for confirmation and - use that instead of =tab-bar-close-tab-by-name= in the above keymap: - #+begin_src emacs-lisp - (defun my-confirm-close-tab-by-name (tab) - (interactive "sTab to close: ") - (when (y-or-n-p (format "Close tab '%s'? " tab)) - (tab-bar-close-tab-by-name tab))) - #+end_src - - Notice that this is a command you can also use directly from =M-x= - independently of Embark. Using it from =M-x= leaves something to be - desired, though, since you don't get completion for the tab names. - You can fix this if you wish as described in the previous section. - -*** New target example in regular buffers - short Wikipedia links - -Say you want to teach Embark to treat text of the form -=wikipedia:Garry_Kasparov= in any regular buffer as a link to Wikipedia, -with actions to open the Wikipedia page in eww or an external browser -or to save the URL of the page in the kill-ring. We can take advantage -of the actions that Embark has preconfigured for URLs, so all we need -to do is teach Embark that =wikipedia:Garry_Kasparov= stands for the URL -=https://en.wikipedia.org/wiki/Garry_Kasparov=. - -You can be as fancy as you want with the recognized syntax. Here, to -keep the example simple, I'll assume the link matches the regexp -=wikipedia:[[:alnum:]_]+=. We will write a function that looks for a -match surrounding point, and returns a dotted list of the form ='(url -URL-OF-THE-PAGE START . END)= where =START= and =END= are the buffer -positions bounding the target, and are used by Embark to highlight it -if you have =embark-highlight-indicator= included in the list -=embark-indicators=. (There are a couple of other options for the return -value of a target finder: the bounding positions are optional and a -single target finder is allowed to return multiple targets; see the -documentation for =embark-target-finders= for details.) - -#+begin_src emacs-lisp - (defun my-short-wikipedia-link () - "Target a link at point of the form wikipedia:Page_Name." - (save-excursion - (let* ((start (progn (skip-chars-backward "[:alnum:]_:") (point))) - (end (progn (skip-chars-forward "[:alnum:]_:") (point))) - (str (buffer-substring-no-properties start end))) - (save-match-data - (when (string-match "wikipedia:\\([[:alnum:]_]+\\)" str) - `(url - ,(format "https://en.wikipedia.org/wiki/%s" - (match-string 1 str)) - ,start . ,end)))))) - - (add-to-list 'embark-target-finders 'my-short-wikipedia-link) -#+end_src - -* How does Embark call the actions? - - Embark actions are normal Emacs commands, that is, functions with an - interactive specification. In order to execute an action, Embark - calls the command with =call-interactively=, so the command reads user - input exactly as if run directly by the user. For example the - command may open a minibuffer and read a string - (=read-from-minibuffer=) or open a completion interface - (=completing-read=). If this happens, Embark takes the target string - and inserts it automatically into the minibuffer, simulating user - input this way. After inserting the string, Embark exits the - minibuffer, submitting the input. (The immediate minibuffer exit can - be disabled for specific actions in order to allow editing the - input; this is done by adding the =embark--allow-edit= function to the - appropriate entry of =embark-target-injection-hooks=). Embark inserts - the target string at the first minibuffer opened by the action - command, and if the command happens to prompt the user for input - more than once, the user still interacts with the second and further - prompts in the normal fashion. Note that if a command does not - prompt the user for input in the minibuffer, Embark still allows you - to use it as an action, but of course, never inserts the target - anywhere. (There are plenty of examples in the default configuration - of commands that do not prompt the user bound to keys in the action - maps, most of the region actions, for instance.) - - This is how Embark manages to reuse normal commands as actions. The - mechanism allows you to use as Embark actions commands that were not - written with Embark in mind (and indeed almost all actions that are - bound by default in Embark's action keymaps are standard Emacs - commands). It also allows you to write new custom actions in such a - way that they are useful even without Embark. - - Staring from version 28.1, Emacs has a variable - =y-or-n-p-use-read-key=, which when set to =t= causes =y-or-n-p= to use - =read-key= instead of =read-from-minibuffer=. Setting - =y-or-n-p-use-read-key= to =t= is recommended for Embark users because - it keeps Embark from attempting to insert the target at a =y-or-n-p= - prompt, which would almost never be sensible. Also consider this as - a warning to structure your own action commands so that if they use - =y-or-n-p=, they do so only after the prompting for the target. - - Here is a simple example illustrating the various ways of reading - input from the user mentioned above. Bind the following commands to - the =embark-symbol-map= to be used as actions, then put the point on - some symbol and run them with =embark-act=: - - #+begin_src emacs-lisp - (defun example-action-command1 () - (interactive) - (message "The input was `%s'." (read-from-minibuffer "Input: "))) - - (defun example-action-command2 (arg input1 input2) - (interactive "P\nsInput 1: \nsInput 2: ") - (message "The first input %swas `%s', and the second was `%s'." - (if arg "truly " "") - input1 - input2)) - - (defun example-action-command3 () - (interactive) - (message "Your selection was `%s'." - (completing-read "Select: " '("E" "M" "B" "A" "R" "K")))) - - (defun example-action-command4 () - (interactive) - (message "I don't prompt you for input and thus ignore the target!")) - - (keymap-set embark-symbol-map "X 1" #'example-action-command1) - (keymap-set embark-symbol-map "X 2" #'example-action-command2) - (keymap-set embark-symbol-map "X 3" #'example-action-command3) - (keymap-set embark-symbol-map "X 4" #'example-action-command4) - #+end_src - - Also note that if you are using the key bindings to call actions, - you can pass prefix arguments to actions in the normal way. For - example, you can use =C-u X2= with the above demonstration actions to - make the message printed by =example-action-command2= more emphatic. - This ability to pass prefix arguments to actions is useful for some - actions in the default configuration, such as - =embark-shell-command-on-buffer=. - -** Non-interactive functions as actions - - Alternatively, Embark does support one other type of action: a - non-interactive function of a single argument. The target is passed - as argument to the function. For example: - - #+begin_src emacs-lisp - (defun example-action-function (target) - (message "The target was `%s'." target)) - - (keymap-set embark-symbol-map "X 4" #'example-action-function) - #+end_src - - Note that normally binding non-interactive functions in a keymap is - useless, since when attempting to run them using the key binding you - get an error message similar to "Wrong type argument: commandp, - example-action-function". In general it is more flexible to write - any new Embark actions as commands, that is, as interactive - functions, because that way you can also run them directly, without - Embark. But there are a couple of reasons to use non-interactive - functions as actions: - - 1. You may already have the function lying around, and it is - convenient to simply reuse it. - - 2. For command actions the targets can only be simple string, with - no text properties. For certain advanced uses you may want the - action to receive a string /with/ some text properties, or even a - non-string target. - -* Embark, Marginalia and Consult - -Embark cooperates well with the [[https://github.com/minad/marginalia][Marginalia]] and [[https://github.com/minad/consult][Consult]] packages. -Neither of those packages is a dependency of Embark, but both are -highly recommended companions to Embark, for opposite reasons: -Marginalia greatly enhances Embark's usefulness, while Embark can help -enhance Consult. - -In the remainder of this section I'll explain what exactly Marginalia -does for Embark, and what Embark can do for Consult. - -** Marginalia - -Embark comes with actions for symbols (commands, functions, variables -with actions such as finding the definition, looking up the -documentation, evaluating, etc.) in the =embark-symbol-map= keymap, and -for packages (actions like install, delete, browse url, etc.) in the -=embark-package-keymap=. - -Unfortunately Embark does not automatically offers you these keymaps -when relevant, because many built-in Emacs commands don't report -accurate category metadata. For example, a command like -=describe-package=, which reads a package name from the minibuffer, -does not have metadata indicating this fact. - -In an earlier Embark version, there were functions to supply this -missing metadata, but they have been moved to Marginalia, which -augments many Emacs command to report accurate category metadata. -Simply activating =marginalia-mode= allows Embark to offer you the -package and symbol actions when appropriate again. Candidate -annotations in the Embark collect buffer are also provided by the -Marginalia package: - -- If you install Marginalia and activate =marginalia-mode=, Embark - Collect buffers will use the Marginalia annotations automatically. - -- If you don't install Marginalia, you will see only the annotations - that come with Emacs (such as key bindings in =M-x=, or the unicode - characters in =C-x 8 RET=). - -** Consult - -The excellent Consult package provides many commands that use -minibuffer completion, via the =completing-read= function; plenty of its -commands can be considered enhanced versions of built-in Emacs -commands, and some are completely new functionality. One common -enhancement provided in all commands for which it makes sense is -preview functionality, for example =consult-buffer= will show you a -quick preview of a buffer before you actually switch to it. - -If you use both Consult and Embark you should install the -=embark-consult= package which provides integration between the two. It -provides exporters for several Consult commands and also tweaks the -behavior of many Consult commands when used as actions with =embark-act= -in subtle ways that you may not even notice, but make for a smoother -experience. You need only install it to get these benefits: Embark -will automatically load it after Consult if found. - -The =embark-consult= package provides the following exporters: - -- You can use =embark-export= from =consult-line=, =consult-outline=, or - =consult-mark= to obtain an =occur-mode= buffer. As with the built-in - =occur= command you use that buffer to jump to a match and after that, - you can then use =next-error= and =previous-error= to navigate to other - matches. You can also press =e= to activate =occur-edit-mode= and edit - the matches in place! - -- You can export from any of the Consult asynchronous search commands, - =consult-grep=, =consult-git-grep=, or =consult-ripgrep= to get a - =grep-mode= buffer. Here too you can use =next-error= and =previous-error= - to navigate among matches, and, if you install the [[http://github.com/mhayashi1120/Emacs-wgrep/raw/master/wgrep.el ][wgrep]] package, - you can use it to edit the matches in place. - -In both cases, pressing =g= will rerun the Consult command you had -exported from and re-enter the input you had typed (which is similar -to reverting but a little more flexible). You can then proceed to -re-export if that's what you want, but you can also edit the input -changing the search terms or simply cancel if you see you are done -with that search. - -The =embark-consult= also contains some candidates collectors that allow -you to run =embark-live= to get a live-updating table of contents for -your buffer: - -- =embark-consult-outline-candidates= produces the outline headings of - the current buffer, using =consult-outline=. -- =embark-consult-imenu-candidates= produces the imenu items of - the current buffer, using =consult-imenu=. -- =embark-consult-imenu-or-outline-candidates= is a simple combination - of the two previous functions: it produces imenu items in buffers - deriving from =prog-mode= and otherwise outline headings. - -The way to configure =embark-live= (or =embark-collect= and =embark-export= -for that matter) to use one of these function is to add it at the end -of the =embark-candidate-collectors= list. The =embark-consult= package by -default adds the last one, which seems to be the most sensible -default. - -Besides those exporters and candidate collectors, the =embark-consult= -package provides many subtle tweaks and small integrations between -Embark and Consult. Some examples are: - -- When used as actions, the asynchronous search commands will search - only the files associated to the targets: if the targets /are/ files, - it searches those files; for buffers it will search either the - associated file if there is one, else all files in the buffer's - =default-directory=; for bookmarks it will search the file they point - to, same for Emacs Lisp libraries. This is particularly powerful - when using =embark-act-all= to act on multiple files at once, for - example you can use =consult-find= to search among file /names/ and then - =embark-act-all= and =consult-grep= to search within the matching files. - - - For all other target types, those that do not have a sensible - notion of associated file, a Consult search command (asynchronous - or not) will search for the text of the target but leave the - minibuffer open so you can interact with the Consult command. - -- =consult-imenu= will search for the target and take you directly to - the location if it matches a unique imenu entry, otherwise it will - leave the minibuffer open so you can navigate among the matches. - -* Related Packages - -There are several packages that offer functionality similar -to Embark's. - -- Acting on minibuffer completion candidates :: The popular Ivy and - Helm packages have support for acting on the completion candidates - of commands written using their APIs, and there is an extensive - ecosystem of packages meant for Helm and for Ivy (the Ivy ones - usually have "counsel" in the name) providing commands and - appropriate actions. -- Acting on things at point :: The built-in =context-menu-mode= provides - a mouse-driven context-sensitive configurable menu. The =do-at-point= - package by Philip Kaludercic (available on GNU ELPA), on the other - hand is keyboard-driven. -- Collecting completion candidates into a buffer :: The Ivy package - has the command =ivy-occur= which is similar to =embark-collect=. As - with Ivy actions, =ivy-occur= only works for commands written using - the Ivy API. - -* Resources - -If you want to learn more about how others have used Embark here are -some links to read: - -- [[https://karthinks.com/software/fifteen-ways-to-use-embark/][Fifteen ways to use Embark]], a blog post by Karthik Chikmagalur. -- [[https://protesilaos.com/dotemacs/][Protesilaos Stavrou's dotemacs]], look for the section called - "Extended minibuffer actions and more (embark.el and - prot-embark.el)" - -And some videos to watch: - -- [[https://protesilaos.com/codelog/2021-01-09-emacs-embark-extras/][Embark and my extras]] by Protesilaos Stavrou. -- [[https://youtu.be/qpoQiiinCtY][Embark -- Key features and tweaks]] by Raoul Comninos on the - Emacs-Elements YouTube channel. -- [[https://youtu.be/WsxXr1ncukY][Livestreamed: Adding an Embark context action to send a stream - message]] by Sacha Chua. -- [[https://youtu.be/qk2Is_sC8Lk][System Crafters Live! - The Many Uses of Embark]] by David Wilson. -- [[https://youtu.be/5ffb2at2d7w][Using Emacs Episode 80 - Vertico, Marginalia, Consult and Embark]] by - Mike Zamansky. - -* Contributions - -Contributions to Embark are very welcome. There is a [[https://github.com/oantolin/embark/issues/95][wish list]] for -actions, target finders, candidate collectors and exporters. For other -ideas you have for Embark, feel free to open an issue on the [[https://github.com/oantolin/embark/issues][issue -tracker]]. Any neat configuration tricks you find might be a good fit -for the [[https://github.com/oantolin/embark/wiki][wiki]]. - -Code contributions are very welcome too, but since Embark is now on -GNU ELPA, copyright assignment to the FSF is required before you can -contribute code. - -* Acknowledgments - -While I, Omar Antolín Camarena, have written most of the Embark code -and remain very stubborn about some of the design decisions, Embark -has received substantial help from a number of other people which this -document has neglected to mention for far too long. In particular, -Daniel Mendler has been absolutely invaluable, implementing several -important features, and providing a lot of useful advice. - -Code contributions: - -- [[https://github.com/minad][Daniel Mendler]] -- [[https://github.com/clemera/][Clemens Radermacher]] -- [[https://codeberg.org/jao/][José Antonio Ortega Ruiz]] -- [[https://github.com/iyefrat][Itai Y. Efrat]] -- [[https://github.com/a13][a13]] -- [[https://github.com/jakanakaevangeli][jakanakaevangeli]] -- [[https://github.com/mihakam][mihakam]] -- [[https://github.com/leungbk][Brian Leung]] -- [[https://github.com/karthink][Karthik Chikmagalur]] -- [[https://github.com/roshanshariff][Roshan Shariff]] -- [[https://github.com/condy0919][condy0919]] -- [[https://github.com/DamienCassou][Damien Cassou]] -- [[https://github.com/JimDBh][JimDBh]] - -Advice and useful discussions: - -- [[https://github.com/minad][Daniel Mendler]] -- [[https://gitlab.com/protesilaos/][Protesilaos Stavrou]] -- [[https://github.com/clemera/][Clemens Radermacher]] -- [[https://github.com/hmelman/][Howard Melman]] -- [[https://github.com/astoff][Augusto Stoffel]] -- [[https://github.com/bdarcus][Bruce d'Arcus]] -- [[https://github.com/jdtsmith][JD Smith]] -- [[https://github.com/karthink][Karthik Chikmagalur]] -- [[https://github.com/jakanakaevangeli][jakanakaevangeli]] -- [[https://github.com/iyefrat][Itai Y. Efrat]] -- [[https://github.com/mohkale][Mohsin Kaleem]] blob - 818863058651d778ade67038f9bcad5661f59022 (mode 644) blob + /dev/null --- elpa/embark-1.0/dir +++ /dev/null @@ -1,18 +0,0 @@ -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 -* Embark: (embark). Emacs Mini-Buffer Actions Rooted in Keymaps. blob - 165da0d54976d5401104d99bf46e99e7962d8788 (mode 644) blob + /dev/null --- elpa/embark-1.0/embark-autoloads.el +++ /dev/null @@ -1,211 +0,0 @@ -;;; embark-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 embark.el - -(defun embark--record-this-command nil "\ -Record command which opened the minibuffer. -We record this because it will be the default action. -This function is meant to be added to `minibuffer-setup-hook'." (setq-local embark--command this-command)) -(add-hook 'minibuffer-setup-hook #'embark--record-this-command) -(autoload 'embark-eldoc-first-target "embark" "\ -Eldoc function reporting the first Embark target at point. -This function uses the eldoc REPORT callback and is meant to be -added to `eldoc-documentation-functions'. - -(fn REPORT &rest _)") -(autoload 'embark-eldoc-target-types "embark" "\ -Eldoc function reporting the types of all Embark targets at point. -This function uses the eldoc REPORT callback and is meant to be -added to `eldoc-documentation-functions'. - -(fn REPORT &rest _)") -(autoload 'embark-bindings-in-keymap "embark" "\ -Explore command key bindings in KEYMAP with `completing-read'. -The selected command will be executed. Interactively, prompt the -user for a KEYMAP variable. - -(fn KEYMAP)" t) -(autoload 'embark-bindings "embark" "\ -Explore current command key bindings with `completing-read'. -The selected command will be executed. - -This shows key bindings from minor mode maps and the local -map (usually set by the major mode), but also less common keymaps -such as those from a text property or overlay, or the overriding -maps: `overriding-terminal-local-map' and `overriding-local-map'. - -Additionally, if GLOBAL is non-nil (interactively, if called with -a prefix argument), this command includes global key bindings. - -(fn GLOBAL)" t) -(autoload 'embark-bindings-at-point "embark" "\ -Explore all key bindings at point with `completing-read'. -The selected command will be executed. - -This command lists key bindings found in keymaps specified by the -text properties `keymap' or `local-map', from either buffer text -or an overlay. These are not widely used in Emacs, and when they -are used can be somewhat hard to discover. Examples of locations -that have such a keymap are links and images in `eww' buffers, -attachment links in `gnus' article buffers, and the stash line -in a `vc-dir' buffer." t) -(autoload 'embark-prefix-help-command "embark" "\ -Prompt for and run a command bound in the prefix used for this command. -The prefix described consists of all but the last event of the -key sequence that ran this command. This function is intended to -be used as a value for `prefix-help-command'. - -In addition to using completion to select a command, you can also -type @ and the key binding (without the prefix)." t) -(autoload 'embark-act "embark" "\ -Prompt the user for an action and perform it. -The targets of the action are chosen by `embark-target-finders'. -By default, if called from a minibuffer the target is the top -completion candidate. When called from a non-minibuffer buffer -there can multiple targets and you can cycle among them by using -`embark-cycle' (which is bound by default to the same key -binding `embark-act' is, but see `embark-cycle-key'). - -This command uses `embark-prompter' to ask the user to specify an -action, and calls it injecting the target at the first minibuffer -prompt. - -If you call this from the minibuffer, it can optionally quit the -minibuffer. The variable `embark-quit-after-action' controls -whether calling `embark-act' with nil ARG quits the minibuffer, -and if ARG is non-nil it will do the opposite. Interactively, -ARG is the prefix argument. - -If instead you call this from outside the minibuffer, the first -ARG targets are skipped over (if ARG is negative the skipping is -done by cycling backwards) and cycling starts from the following -target. - -(fn &optional ARG)" t) -(autoload 'embark-act-all "embark" "\ -Prompt the user for an action and perform it on each candidate. -The candidates are chosen by `embark-candidate-collectors'. By -default, if `embark-select' has been used to select some -candidates, then `embark-act-all' will act on those candidates; -otherwise, if the selection is empty and `embark-act-all' is -called from a minibuffer, then the candidates are the completion -candidates. - -This command uses `embark-prompter' to ask the user to specify an -action, and calls it injecting the target at the first minibuffer -prompt. - -If you call this from the minibuffer, it can optionally quit the -minibuffer. The variable `embark-quit-after-action' controls -whether calling `embark-act' with nil ARG quits the minibuffer, -and if ARG is non-nil it will do the opposite. Interactively, -ARG is the prefix argument. - -(fn &optional ARG)" t) -(autoload 'embark-dwim "embark" "\ -Run the default action on the current target. -The target of the action is chosen by `embark-target-finders'. - -If the target comes from minibuffer completion, then the default -action is the command that opened the minibuffer in the first -place, unless overridden by `embark-default-action-overrides'. - -For targets that do not come from minibuffer completion -(typically some thing at point in a regular buffer) and whose -type is not listed in `embark-default-action-overrides', the -default action is given by whatever binding RET has in the action -keymap for the target's type. - -See `embark-act' for the meaning of the prefix ARG. - -(fn &optional ARG)" t) -(autoload 'embark-become "embark" "\ -Make current command become a different command. -Take the current minibuffer input as initial input for new -command. The new command can be run normally using key bindings or -\\[execute-extended-command], but if the current command is found in a keymap in -`embark-become-keymaps', that keymap is activated to provide -convenient access to the other commands in it. - -If FULL is non-nil (interactively, if called with a prefix -argument), the entire minibuffer contents are used as the initial -input of the new command. By default only the part of the -minibuffer contents between the current completion boundaries is -taken. What this means is fairly technical, but (1) usually -there is no difference: the completion boundaries include the -entire minibuffer contents, and (2) the most common case where -these notions differ is file completion, in which case the -completion boundaries single out the path component containing -point. - -(fn &optional FULL)" t) -(autoload 'embark-collect "embark" "\ -Create an Embark Collect buffer. - -To control the display, add an entry to `display-buffer-alist' -with key \"Embark Collect\". - -In Embark Collect buffers `revert-buffer' is remapped to -`embark-rerun-collect-or-export', which has slightly unusual -behavior if the buffer was obtained by running `embark-collect' -from within a minibuffer completion session. In that case -rerunning just restarts the completion session, that is, the -command that opened the minibuffer is run again and the -minibuffer contents restored. You can then interact normally with -the command, perhaps editing the minibuffer contents, and, if you -wish, you can rerun `embark-collect' to get an updated buffer." t) -(autoload 'embark-live "embark" "\ -Create a live-updating Embark Collect buffer. - -To control the display, add an entry to `display-buffer-alist' -with key \"Embark Live\"." t) -(autoload 'embark-export "embark" "\ -Create a type-specific buffer to manage current candidates. -The variable `embark-exporters-alist' controls how to make the -buffer for each type of completion. - -In Embark Export buffers `revert-buffer' is remapped to -`embark-rerun-collect-or-export', which has slightly unusual -behavior if the buffer was obtained by running `embark-export' -from within a minibuffer completion session. In that case -reverting just restarts the completion session, that is, the -command that opened the minibuffer is run again and the -minibuffer contents restored. You can then interact normally -with the command, perhaps editing the minibuffer contents, and, -if you wish, you can rerun `embark-export' to get an updated -buffer." t) -(autoload 'embark-select "embark" "\ -Add or remove the target from the current buffer's selection. -You can act on all selected targets at once with `embark-act-all'. -When called from outside `embark-act' this command will select -the first target at point." t) -(register-definition-prefixes "embark" '("embark-")) - - -;;; Generated autoloads from embark-org.el - -(register-definition-prefixes "embark-org" '("embark-org-")) - - -;;; End of scraped data - -(provide 'embark-autoloads) - -;; Local Variables: -;; version-control: never -;; no-byte-compile: t -;; no-update-autoloads: t -;; no-native-compile: t -;; coding: utf-8-emacs-unix -;; End: - -;;; embark-autoloads.el ends here blob - 1bd53423bb3a844f7e2a7bee6fe3e06fcd968849 (mode 644) blob + /dev/null --- elpa/embark-1.0/embark-org.el +++ /dev/null @@ -1,716 +0,0 @@ -;;; embark-org.el --- Embark targets and actions for Org Mode -*- lexical-binding: t; -*- - -;; Copyright (C) 2022-2023 Free Software Foundation, Inc. - -;; 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: - -;; This package configures the Embark package for use in Org Mode -;; buffers. It teaches Embark a number of Org related targets and -;; appropriate actions. Currently it has table cells, whole tables, -;; source blocks and links. Targets to add: headings (Embark already -;; has generic support for outlines, so we just nee to add Org -;; specific actions), timestamps, etc. - -;;; Code: - -(require 'embark) -(require 'org) -(require 'org-element) - -;;; Basic target finder for Org - -;; There are very many org element and objects types, we'll only -;; recognize those for which there are specific actions we can put in -;; a keymap, or even if there aren't any specific actions, if it's -;; important to be able to kill, delete or duplicate (embark-insert) -;; them conveniently. I'll start conservatively and we can add more -;; later - -(defconst embark-org--types - '( - babel-call - ;; bold - ;; center-block - ;; citation - ;; citation-reference - ;; clock - ;; code - ;; comment - ;; comment-block - ;; diary-sexp - ;; drawer - ;; dynamic-block - ;; entity - ;; example-block - ;; export-block - ;; export-snippet - ;; fixed-width - footnote-definition - footnote-reference - ;; headline ; the bounds include the entire subtree! - ;; horizontal-rule - ;; inline-babel-call - inline-src-block - ;; inlinetask - ;; italic - item - ;; keyword - ;; latex-environment - ;; latex-fragment - ;; line-break - link - ;; macro - ;; node-property - ;; paragraph ; the existing general support seems fine - plain-list - ;; planning - ;; property-drawer - ;; quote-block - ;; radio-target - ;; section - ;; special-block - src-block - ;; statistics-cookie - ;; strike-through - ;; subscript - ;; superscript - table ; supported via a specific target finder - table-cell - ;; table-row ; we'll put row & column actions in the cell map - ;; target ; I think there are no useful actions for radio targets - timestamp - ;; underline - ;; verbatim - ;; verse-block - ) - "Supported Org object and element types.") - -(defun embark-org-target-element-context () - "Target all Org elements or objects around point." - (when (derived-mode-p 'org-mode 'org-agenda-mode) - (cl-loop - for elt = (org-element-lineage (org-element-context) embark-org--types t) - then (org-element-lineage elt embark-org--types) - while elt - ;; clip bounds to narrowed portion of buffer - for begin = (max (org-element-property :begin elt) (point-min)) - for end = (min (org-element-property :end elt) (point-max)) - for target = (buffer-substring begin end) - ;; Adjust table-cell to exclude final |. (Why is that there?) - ;; Note: We are not doing this as an embark transformer because we - ;; want to adjust the bounds too. - ;; TODO? If more adjustments like this become necessary, add a - ;; nice mechanism for doing them. - when (and (eq (car elt) 'table-cell) (string-suffix-p "|" target)) - do (setq target (string-trim (string-remove-suffix "|" target)) - end (1- end)) - collect `(,(intern (format "org-%s" (car elt))) ,target ,begin . ,end)))) - -(if-let (((not (memq 'embark-org-target-element-context embark-target-finders))) - (tail (memq 'embark-target-active-region embark-target-finders))) - (push 'embark-org-target-element-context (cdr tail)) - (push 'embark-org-target-element-context embark-target-finders)) - -;;; Custom Org actions - -(defvar org-export-with-toc) - -(defun embark-org-copy-as-markdown (start end) - "Export the region from START to END to markdown and save on the `kill-ring'." - (interactive "r") - (require 'ox) - (kill-new - (let (org-export-with-toc) - (string-trim - (org-export-string-as (buffer-substring-no-properties start end) 'md t)))) - (deactivate-mark)) - -(add-to-list 'embark-pre-action-hooks - '(embark-org-copy-as-markdown embark--mark-target)) - -(keymap-set embark-region-map "M" #'embark-org-copy-as-markdown) ; good idea? - -;;; Tables - -(dolist (motion '(org-table-move-cell-up org-table-move-cell-down - org-table-move-cell-left org-table-move-cell-right - org-table-move-row org-table-move-column - org-table-move-row-up org-table-move-row-down - org-table-move-column-left org-table-move-column-right)) - (add-to-list 'embark-repeat-actions motion)) - -(dolist (cmd '(org-table-eval-formula org-table-edit-field)) - (push 'embark--ignore-target (alist-get cmd embark-target-injection-hooks))) - -(defvar-keymap embark-org-table-cell-map - :doc "Keymap for actions the current cells, column or row of an Org table." - :parent embark-general-map - "RET" #'org-table-align ; harmless default - "" #'org-table-move-cell-up - "" #'org-table-move-cell-down - "" #'org-table-move-cell-left - "" #'org-table-move-cell-right - "d" #'org-table-kill-row - "c" #'org-table-copy-down - "D" #'org-table-delete-column ; capital = column - "^" #'org-table-move-row-up - "v" #'org-table-move-row-down - "<" #'org-table-move-column-left - ">" #'org-table-move-column-right - "o" #'org-table-insert-row - "O" #'org-table-insert-column ; capital = column - "h" #'org-table-insert-hline - "=" #'org-table-eval-formula - "e" #'org-table-edit-field - "g" #'org-table-recalculate) - -(defvar-keymap embark-org-table-map - :doc "Keymap for actions on entire Org table." - :parent embark-general-map - "RET" #'org-table-align ; harmless default - "=" #'org-table-edit-formulas - "s" #'org-table-sort-lines - "t" #'org-table-transpose-table-at-point - "c" #'org-table-convert - "f" #'org-table-follow-field-mode - "y" #'org-table-paste-rectangle - "d" #'org-table-toggle-formula-debugger - "o" #'org-table-toggle-coordinate-overlays - "g" #'org-table-iterate - "e" #'org-table-export) - -(push 'embark--ignore-target ; prompts for file name - (alist-get 'org-table-export embark-target-injection-hooks)) - -(add-to-list 'embark-keymap-alist '(org-table embark-org-table-map)) - -(add-to-list 'embark-keymap-alist '(org-table-cell embark-org-table-cell-map)) - -;;; Links - -;; The link support has a slightly complicated design in order to -;; achieve the following goals: - -;; 1. RET should simply be org-open-at-point - -;; 2. When the link is to a file, URL, email address or elisp -;; expression or command, we want to offer the user actions for -;; that underlying type. - -;; 3. Even in those cases, we still want some actions to apply to the -;; entire link including description: actions to copy the link as -;; markdown, or just the link description or target. - -;; So the strategy is as follows (illustrated with file links): - -;; - The target will be just the file, without the description and -;; also without the "file:" prefix nor the "::line-number or search" -;; suffix. That way, file actions will correctly apply to it. - -;; - The type will not be 'file, but 'org-file-link; that way we can -;; register a keymap for 'org-file-link that inherits from both -;; embark-org-link-map (with RET bound to org-open-at-point and a -;; few other generic link actions) and embark-file-map. - -;; - The commands to copy the link at point in some format will be -;; written as commands that act on the Org link at point. This way -;; they are independently (plausibly) useful, and we circumvent the -;; problem that the whole Org link is not actually the target (just -;; the inner file is!). - -;; Alternative design I considered: separate each target into two, a -;; whole link target which includes the description and brackets and -;; what not; and an "inner target" which is just the file or URL or -;; whatever. Cons of this approach: much target cycling is required! -;; First of all, an unadorned embark-dwim definitely should be -;; org-open-at-point, which means the whole link target would need -;; priority. That means that any file, URL, etc. actions would require -;; you to cycle first. This sounds very inconvenient, the above -;; slightly more complex design allows both whole-link and inner -;; target actions to work without cycling. - -(defun embark-org-target-link () - "Target Org link at point. -This targets Org links in any buffer, not just buffers in -`org-mode' or `org-agenda-mode'. Org links in any buffer can be -opened with `org-open-at-point-global', which is the default -Embark action for Org links." - (pcase (org-in-regexp org-link-any-re) - (`(,start . ,end) - ;; We won't recognize unadorned http(s) or mailto links, as those - ;; already have target finders (but if these links have either a - ;; description, double brackets or angle brackets, then we do - ;; recognize them as org links) - (unless (save-excursion (goto-char start) (looking-at "http\\|mailto")) - `(org-link ,(buffer-substring start end) ,start . ,end))))) - -(let ((tail (memq 'embark-target-active-region embark-target-finders))) - (cl-pushnew 'embark-org-target-link (cdr tail))) - -(autoload 'org-attach-dir "org-attach") - -(defun embark-org--refine-link-type (_type target) - "Refine type of link TARGET if we have more specific actions available." - (when (string-match org-link-any-re target) - (let ((target (or (match-string-no-properties 2 target) - (match-string-no-properties 0 target)))) - (cond - ((string-prefix-p "http" target) - (cons 'org-url-link target)) - ((string-prefix-p "mailto:" target) - (cons 'org-email-link (string-remove-prefix "mailto:" target))) - ((string-prefix-p "file:" target) - (cons 'org-file-link - (replace-regexp-in-string - "::.*" "" (string-remove-prefix "file:" target)))) - ((string-prefix-p "attachment:" target) - (cons 'org-file-link - (expand-file-name - (replace-regexp-in-string - "::.*" "" (string-remove-prefix "attachment:" target)) - (org-attach-dir)))) - ((string-match-p "^[./]" target) - (cons 'org-file-link (abbreviate-file-name (expand-file-name target)))) - ((string-prefix-p "elisp:(" target) - (cons 'org-expression-link (string-remove-prefix "elisp:" target))) - ((string-prefix-p "elisp:" target) - (cons 'command (string-remove-prefix "elisp:" target))) - (t (cons 'org-link target)))))) - -(add-to-list 'embark-transformer-alist - '(org-link . embark-org--refine-link-type)) - -(defmacro embark-org-define-link-copier (name formula description) - "Define a command that copies the Org link at point according to FORMULA. -The command's name is formed by appending NAME to -embark-org-copy-link. The docstring includes the DESCRIPTION of -what part or in what format the link is copied." - `(defun ,(intern (format "embark-org-copy-link-%s" name)) () - ,(format "Copy to the kill-ring the Org link at point%s." description) - (interactive) - (when (org-in-regexp org-link-any-re) - (let* ((full (match-string-no-properties 0)) - (target (or (match-string-no-properties 2) - (match-string-no-properties 0))) - (description (match-string-no-properties 3)) - (kill ,formula)) - (ignore full target description) - (when kill - (message "Saved '%s'" kill) - (kill-new kill)))))) - -(embark-org-define-link-copier in-full full " in full") -(embark-org-define-link-copier description description "'s description") -(embark-org-define-link-copier target target "'s target") - -(defalias 'embark-org-copy-link-inner-target #'kill-new - "Copy inner part of the Org link at point's target. -For mailto and elisp links, the inner part is the portion of the -target after `mailto:' or `elisp:'. - -For file links the inner part is the file name, without the -`file:' prefix and without `::' suffix (used for line numbers, -IDs or search terms). - -For URLs the inner part is the whole target including the `http:' -or `https:' prefix. For any other type of link the inner part is -also the whole target.") - -(defvar-keymap embark-org-link-copy-map - :doc "Keymap for different ways to copy Org links to the kill-ring. - -You should bind w in this map to your most frequently used link -copying function. The default is for w to copy the \"inner -target\" (see `embark-org-copy-link-inner-target'); which is also -bound to i." - :parent nil - "w" #'embark-org-copy-link-inner-target - "f" #'embark-org-copy-link-in-full - "d" #'embark-org-copy-link-description - "t" #'embark-org-copy-link-target - "i" #'embark-org-copy-link-inner-target - "m" #'embark-org-copy-as-markdown) - -(fset 'embark-org-link-copy-map embark-org-link-copy-map) - -(defvar-keymap embark-org-link-map - :doc "Keymap for actions on Org links." - :parent embark-general-map - "RET" #'org-open-at-point-global - "'" #'org-insert-link - "n" #'org-next-link - "p" #'org-previous-link - "w" #'embark-org-link-copy-map) - -(dolist (motion '(org-next-link org-previous-link)) - (cl-pushnew motion embark-repeat-actions)) - -;; The reason for this is left as an exercise to the reader. -;; Solution: Na ryvfc gnetrg znl cebzcg gur hfre sbe fbzrguvat! -(cl-pushnew 'embark--ignore-target - (alist-get 'org-open-at-point embark-target-injection-hooks)) -(cl-pushnew 'embark--ignore-target - (alist-get 'org-insert-link embark-target-injection-hooks)) - -(add-to-list 'embark-keymap-alist - '(org-link embark-org-link-map)) -(add-to-list 'embark-keymap-alist - '(org-url-link embark-org-link-map embark-url-map)) -(add-to-list 'embark-keymap-alist - '(org-email-link embark-org-link-map embark-email-map)) -(add-to-list 'embark-keymap-alist - '(org-file-link embark-org-link-map embark-file-map)) -(add-to-list 'embark-keymap-alist - '(org-expression-link embark-org-link-map embark-expression-map)) - -;;; Org headings - -(defun embark-org--refine-heading (type target) - "Refine TYPE of heading TARGET in Org buffers." - (cons - (if (derived-mode-p 'org-mode) 'org-heading type) - target)) - -(add-to-list 'embark-transformer-alist '(heading . embark-org--refine-heading)) - -(defvar-keymap embark-org-heading-map - :doc "Keymap for actions on Org headings." - :parent embark-heading-map - "RET" #'org-todo - "TAB" #'org-cycle - "t" #'org-todo - "s" #'org-schedule - "d" #'org-deadline - "," #'org-priority - ":" #'org-set-tags-command - "P" #'org-set-property - "D" #'org-delete-property - "k" #'org-cut-subtree - "N" #'org-narrow-to-subtree - "T" #'org-tree-to-indirect-buffer - "" #'org-do-promote - "" #'org-do-demote - "o" #'org-sort - "r" #'org-refile - "R" #'embark-org-refile-here - "I" #'org-clock-in - "O" #'org-clock-out - "a" #'org-archive-subtree-default-with-confirmation - "h" #'org-insert-heading-respect-content - "H" #'org-insert-todo-heading-respect-content - "l" #'org-store-link - "j" #'embark-org-insert-link-to) - -(dolist (cmd '(org-todo org-metaright org-metaleft org-metaup org-metadown - org-shiftmetaleft org-shiftmetaright org-cycle org-shifttab)) - (cl-pushnew cmd embark-repeat-actions)) - -(dolist (cmd '(org-set-tags-command org-set-property - org-delete-property org-refile org-schedule)) - (cl-pushnew 'embark--ignore-target - (alist-get cmd embark-target-injection-hooks))) - -(add-to-list 'embark-keymap-alist '(org-heading embark-org-heading-map)) - -;;; Source blocks - -(defun embark-org-copy-block-contents () - "Save contents of source block at point to the `kill-ring'." - (interactive) - (when (org-in-src-block-p) - (let ((contents (nth 2 (org-src--contents-area (org-element-at-point))))) - (with-temp-buffer - (insert contents) - (org-do-remove-indentation) - (kill-new (buffer-substring (point-min) (point-max))))))) - -(defvar-keymap embark-org-src-block-map - :doc "Keymap for actions on Org source blocks." - :parent embark-general-map - "RET" #'org-babel-execute-src-block - "C-SPC" #'org-babel-mark-block - "TAB" #'org-indent-block - "c" #'embark-org-copy-block-contents - "h" #'org-babel-check-src-block - "k" #'org-babel-remove-result-one-or-many - "p" #'org-babel-previous-src-block - "n" #'org-babel-next-src-block - "t" #'org-babel-tangle - "s" #'org-babel-switch-to-session - "l" #'org-babel-load-in-session - "'" #'org-edit-special - "/" #'org-babel-demarcate-block - "N" #'org-narrow-to-block) - -(cl-defun embark-org--at-block-head - (&rest rest &key run bounds &allow-other-keys) - "Save excursion and RUN the action at the head of the current block. -If BOUNDS are given, use them to locate the block (useful for -when acting on a selection of blocks). Applies RUN to the REST -of the arguments." - (save-excursion - (when bounds (goto-char (car bounds))) - (org-babel-goto-src-block-head) - (apply run rest))) - -(cl-pushnew #'embark-org--at-block-head - (alist-get 'org-indent-block embark-around-action-hooks)) - -(dolist (motion '(org-babel-next-src-block org-babel-previous-src-block)) - (add-to-list 'embark-repeat-actions motion)) - -(dolist (cmd '(org-babel-execute-maybe - org-babel-lob-execute-maybe - org-babel-execute-src-block - org-babel-execute-src-block-maybe - org-babel-execute-buffer - org-babel-execute-subtree)) - (cl-pushnew #'embark--ignore-target - (alist-get cmd embark-target-injection-hooks))) - -(add-to-list 'embark-keymap-alist '(org-src-block embark-org-src-block-map)) - -;;; Inline source blocks - -(defvar-keymap embark-org-inline-src-block-map - :doc "Keymap for actions on Org inline source blocks." - :parent embark-general-map - "RET" #'org-babel-execute-src-block - "'" #'org-edit-inline-src-code - "k" #'org-babel-remove-inline-result) - -(add-to-list 'embark-keymap-alist - '(org-inline-src-block embark-org-inline-src-block-map)) - -;;; Babel calls - -(defvar-keymap embark-org-babel-call-map - :doc "Keymap for actions on Org babel calls." - :parent embark-general-map - "RET" #'org-babel-lob-execute-maybe - "k" #'org-babel-remove-result) - -(add-to-list 'embark-keymap-alist - '(org-babel-call embark-org-babel-call-map)) - -;;; List items - -(defvar-keymap embark-org-item-map - :doc "Keymap for actions on Org list items." - :parent embark-general-map - "RET" #'org-toggle-checkbox - "c" #'org-toggle-checkbox - "t" #'org-toggle-item - "n" #'org-next-item - "p" #'org-previous-item - "" #'org-outdent-item - "" #'org-indent-item - "" #'org-move-item-up - "" #'org-move-item-down - ">" #'org-indent-item-tree - "<" #'org-outdent-item-tree) - -(dolist (cmd '(org-toggle-checkbox - org-toggle-item - org-next-item - org-previous-item - org-outdent-item - org-indent-item - org-move-item-up - org-move-item-down - org-indent-item-tree - org-outdent-item-tree)) - (add-to-list 'embark-repeat-actions cmd)) - -(add-to-list 'embark-keymap-alist '(org-item embark-org-item-map)) - -;;; Org plain lists - -(defvar-keymap embark-org-plain-list-map - :doc "Keymap for actions on plain Org lists." - :parent embark-general-map - "RET" #'org-list-repair - "r" #'org-list-repair - "s" #'org-sort-list - "b" #'org-cycle-list-bullet - "t" #'org-list-make-subtree - "c" #'org-toggle-checkbox) - -(add-to-list 'embark-repeat-actions 'org-cycle-list-bullet) - -(add-to-list 'embark-keymap-alist '(org-plain-list embark-org-plain-list-map)) - -(cl-defun embark-org--toggle-checkboxes - (&rest rest &key run type &allow-other-keys) - "Around action hook for `org-toggle-checkbox'. -See `embark-around-action-hooks' for the keyword arguments RUN and TYPE. -REST are the remaining arguments." - (apply (if (eq type 'org-plain-list) #'embark--mark-target run) - :type type - rest)) - -(cl-pushnew #'embark-org--toggle-checkboxes - (alist-get 'org-toggle-checkbox embark-around-action-hooks)) - -;;; "Encode" region using Org export in place - -(defvar-keymap embark-org-export-in-place-map - :doc "Keymap for actions which replace the region by an exported version." - :parent embark-general-map - "m" #'org-md-convert-region-to-md - "h" #'org-html-convert-region-to-html - "a" #'org-ascii-convert-region-to-ascii - "l" #'org-latex-convert-region-to-latex) - -(fset 'embark-org-export-in-place-map embark-org-export-in-place-map) - -(keymap-set embark-encode-map "o" 'embark-org-export-in-place-map) - -;;; References to Org headings, such as agenda items - -;; These are targets that represent an org heading but not in the -;; current buffer, instead they have a text property named -;; `org-marker' that points to the actual heading. - -(defun embark-org-target-agenda-item () - "Target Org agenda item at point." - (when (and (derived-mode-p 'org-agenda-mode) - (get-text-property (line-beginning-position) 'org-marker)) - (let ((start (+ (line-beginning-position) (current-indentation))) - (end (line-end-position))) - `(org-heading ,(buffer-substring start end) ,start . ,end)))) - -(let ((tail (memq 'embark-org-target-element-context embark-target-finders))) - (cl-pushnew 'embark-org-target-agenda-item (cdr tail))) - -(cl-defun embark-org--at-heading - (&rest rest &key run target &allow-other-keys) - "RUN the action at the location of the heading TARGET refers to. -The location is given by the `org-marker' text property of -target. Applies RUN to the REST of the arguments." - (if-let ((marker (get-text-property 0 'org-marker target))) - (org-with-point-at marker - (apply run :target target rest)) - (apply run :target target rest))) - -(cl-defun embark-org-goto-heading (&key target &allow-other-keys) - "Jump to the org heading TARGET refers to." - (when-let ((marker (get-text-property 0 'org-marker target))) - (pop-to-buffer (marker-buffer marker)) - (widen) - (goto-char marker) - (org-reveal) - (pulse-momentary-highlight-one-line))) - -(defun embark-org-heading-default-action (target) - "Default action for Org headings. -There are two types of heading TARGETs: the heading at point in a -normal org buffer, and references to org headings in some other -buffer (for example, org agenda items). For references the -default action is to jump to the reference, and for the heading -at point, the default action is whatever is bound to RET in -`embark-org-heading-map', or `org-todo' if RET is unbound." - (if (get-text-property 0 'org-marker target) - (embark-org-goto-heading :target target) - (command-execute - (or (keymap-lookup embark-org-heading-map "RET") #'org-todo)))) - -(defconst embark-org--invisible-jump-to-heading - '(org-tree-to-indirect-buffer - org-refile - org-clock-in - org-clock-out - org-archive-subtree-default-with-confirmation - org-store-link) - "Org heading actions which won't display the heading's buffer.") - -(defconst embark-org--no-jump-to-heading - '(embark-org-insert-link-to embark-org-refile-here) - "Org heading actions which shouldn't be executed with point at the heading.") - -(setf (alist-get 'org-heading embark-default-action-overrides) - #'embark-org-heading-default-action) - -(map-keymap - (lambda (_key cmd) - (unless (or (where-is-internal cmd (list embark-general-map)) - (memq cmd embark-org--no-jump-to-heading) - (memq cmd embark-org--invisible-jump-to-heading)) - (cl-pushnew 'embark-org-goto-heading - (alist-get cmd embark-pre-action-hooks)))) - embark-org-heading-map) - -(dolist (cmd embark-org--invisible-jump-to-heading) - (cl-pushnew 'embark-org--at-heading - (alist-get cmd embark-around-action-hooks))) - -(defun embark-org--in-source-window (target function) - "Call FUNCTION, in the source window, on TARGET's `org-marker'. - -If TARGET does not have an `org-marker' property a `user-error' -is signaled. In case the TARGET comes from an org agenda buffer -and the `other-window-for-scrolling' is an org mode buffer, then -the FUNCTION is called with that other window temporarily -selected; otherwise the FUNCTION is called in the selected -window." - (if-let ((marker (get-text-property 0 'org-marker target))) - (with-selected-window - (or (and (derived-mode-p 'org-agenda-mode) - (let ((window (ignore-errors (other-window-for-scrolling)))) - (with-current-buffer (window-buffer window) - (when (derived-mode-p 'org-mode) window)))) - (selected-window)) - (funcall function marker)) - (user-error "The target is an org heading rather than a reference to one"))) - -(defun embark-org-refile-here (target) - "Refile the heading at point in the source window to TARGET. - -If TARGET is an agenda item and `other-window-for-scrolling' is -displaying an org mode buffer, then that is the source window. -If TARGET is a minibuffer completion candidate, then the source -window is the window selected before the command that opened the -minibuffer ran." - (embark-org--in-source-window target - (lambda (marker) - (org-refile nil nil - ;; The RFLOC argument: - (list - ;; Name - (org-with-point-at marker - (nth 4 (org-heading-components))) - ;; File - (buffer-file-name (marker-buffer marker)) - ;; nil - nil - ;; Position - marker))))) - -(defun embark-org-insert-link-to (target) - "Insert a link to the TARGET in the source window. - -If TARGET is an agenda item and `other-window-for-scrolling' is -displaying an org mode buffer, then that is the source window. -If TARGET is a minibuffer completion candidate, then the source -window is the window selected before the command that opened the -minibuffer ran." - (embark-org--in-source-window target - (lambda (marker) - (org-with-point-at marker (org-store-link nil t)) - (org-insert-all-links 1 "" "")))) - -(provide 'embark-org) -;;; embark-org.el ends here blob - ec7eaa05b37b7d76b93275ade13b381423247f6b (mode 644) blob + /dev/null --- elpa/embark-1.0/embark-pkg.el +++ /dev/null @@ -1,2 +0,0 @@ -;; Generated package description from embark.el -*- no-byte-compile: t -*- -(define-package "embark" "1.0" "Conveniently act on minibuffer completions" '((emacs "27.1") (compat "29.1.4.0")) :commit "47b0c75d4bf4f72a7af839667c877c80bd493cdb" :authors '(("Omar Antolín Camarena" . "omar@matem.unam.mx")) :maintainer '("Omar Antolín Camarena" . "omar@matem.unam.mx") :keywords '("convenience") :url "https://github.com/oantolin/embark") blob - b3dfd768412644ef952832ba9d083cf777db7788 (mode 644) blob + /dev/null --- elpa/embark-1.0/embark.el +++ /dev/null @@ -1,4596 +0,0 @@ -;;; embark.el --- Conveniently act on minibuffer completions -*- lexical-binding: t; -*- - -;; Copyright (C) 2021-2023 Free Software Foundation, Inc. - -;; Author: Omar Antolín Camarena -;; Maintainer: Omar Antolín Camarena -;; Keywords: convenience -;; Version: 1.0 -;; Homepage: https://github.com/oantolin/embark -;; Package-Requires: ((emacs "27.1") (compat "29.1.4.0")) - -;; This file is 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: - -;; This package provides a sort of right-click contextual menu for -;; Emacs, accessed through the `embark-act' command (which you should -;; bind to a convenient key), offering you relevant actions to use on -;; a target determined by the context: - -;; - In the minibuffer, the target is the current best completion -;; candidate. -;; - In the `*Completions*' buffer the target is the completion at point. -;; - In a regular buffer, the target is the region if active, or else the -;; file, symbol or url at point. - -;; The type of actions offered depend on the type of the target: - -;; - For files you get offered actions like deleting, copying, -;; renaming, visiting in another window, running a shell command on the -;; file, etc. -;; - For buffers the actions include switching to or killing the buffer. -;; - For package names the actions include installing, removing or -;; visiting the homepage. - -;; Everything is easily configurable: determining the current target, -;; classifying it, and deciding with actions are offered for each type -;; in the classification. The above introduction just mentions part of -;; the default configuration. - -;; Configuring which actions are offered for a type is particularly -;; easy and requires no programming: the `embark-keymap-alist' -;; variable associates target types with variable containing keymaps, -;; and those keymaps containing binds for the actions. For example, -;; in the default configuration the type `file' is associated with the -;; symbol `embark-file-map'. That symbol names a keymap with -;; single-letter key bindings for common Emacs file commands, for -;; instance `c' is bound to `copy-file'. This means that if while you -;; are in the minibuffer after running a command that prompts for a -;; file, such as `find-file' or `rename-file', you can copy a file by -;; running `embark-act' and then pressing `c'. - -;; These action keymaps are very convenient but not strictly necessary -;; when using `embark-act': you can use any command that reads from the -;; minibuffer as an action and the target of the action will be inserted -;; at the first minibuffer prompt. After running `embark-act' all of your -;; key bindings and even `execute-extended-command' can be used to run a -;; command. The action keymaps are normal Emacs keymaps and you should -;; feel free to bind in them whatever commands you find useful as actions. - -;; The actions in `embark-general-map' are available no matter what -;; type of completion you are in the middle of. By default this -;; includes bindings to save the current candidate in the kill ring -;; and to insert the current candidate in the previously selected -;; buffer (the buffer that was current when you executed a command -;; that opened up the minibuffer). - -;; You can read about the Embark GitHub project wiki: -;; https://github.com/oantolin/embark/wiki/Default-Actions - -;; Besides acting individually on targets, Embark lets you work -;; collectively on a set of target candidates. For example, while -;; you are in the minibuffer the candidates are simply the possible -;; completions of your input. Embark provides three commands to work -;; on candidate sets: - -;; - The `embark-act-all' command runs the same action on each of the -;; current candidates. It is just like using `embark-act' on each -;; candidate in turn. - -;; - The `embark-collect' command produces a buffer listing all -;; candidates, for you to peruse and run actions on at your leisure. -;; The candidates are displayed as a list showing additional -;; annotations. - -;; - The `embark-export' command tries to open a buffer in an -;; appropriate major mode for the set of candidates. If the -;; candidates are files export produces a Dired buffer; if they are -;; buffers, you get an Ibuffer buffer; and if they are packages you -;; get a buffer in package menu mode. - -;; These are always available as "actions" (although they do not act -;; on just the current target but on all candidates) for embark-act -;; and are bound to A, S (for "snapshot") and E, respectively, in -;; embark-general-map. This means that you do not have to bind your -;; own key bindings for these (although you can, of course), just a -;; key binding for `embark-act'. - -;;; Code: - -(require 'compat) -(eval-when-compile (require 'subr-x)) - -(require 'ffap) ; used to recognize file and url targets - -;;; User facing options - -(defgroup embark nil - "Emacs Mini-Buffer Actions Rooted in Keymaps." - :link '(info-link :tag "Info Manual" "(embark)") - :link '(url-link :tag "Homepage" "https://github.com/oantolin/embark") - :link '(emacs-library-link :tag "Library Source" "embark.el") - :group 'minibuffer - :prefix "embark-") - -(defcustom embark-keymap-alist - '((file embark-file-map) - (library embark-library-map) - (environment-variables embark-file-map) ; they come up in file completion - (url embark-url-map) - (email embark-email-map) - (buffer embark-buffer-map) - (tab embark-tab-map) - (expression embark-expression-map) - (identifier embark-identifier-map) - (defun embark-defun-map) - (symbol embark-symbol-map) - (face embark-face-map) - (command embark-command-map) - (variable embark-variable-map) - (function embark-function-map) - (minor-mode embark-command-map) - (unicode-name embark-unicode-name-map) - (package embark-package-map) - (bookmark embark-bookmark-map) - (region embark-region-map) - (sentence embark-sentence-map) - (paragraph embark-paragraph-map) - (kill-ring embark-kill-ring-map) - (heading embark-heading-map) - (flymake embark-flymake-map) - (smerge smerge-basic-map embark-general-map) - (t embark-general-map)) - "Alist of action types and corresponding keymaps. -The special key t is associated with the default keymap to use. -Each value can be either a single symbol whose value is a keymap, -or a list of such symbols." - :type '(alist :key-type (symbol :tag "Target type") - :value-type (choice (variable :tag "Keymap") - (repeat :tag "Keymaps" variable)))) - -(defcustom embark-target-finders - '(embark-target-top-minibuffer-candidate - embark-target-active-region - embark-target-collect-candidate - embark-target-completion-list-candidate - embark-target-text-heading-at-point - embark-target-bug-reference-at-point - embark-target-flymake-at-point - embark-target-smerge-at-point - embark-target-package-at-point - embark-target-email-at-point - embark-target-url-at-point - embark-target-file-at-point - embark-target-custom-variable-at-point - embark-target-identifier-at-point - embark-target-guess-file-at-point - embark-target-expression-at-point - embark-target-sentence-at-point - embark-target-paragraph-at-point - embark-target-defun-at-point - embark-target-prog-heading-at-point) - "List of functions to determine the target in current context. -Each function should take no arguments and return one of: - -1. a cons (TYPE . TARGET) where TARGET is a string and TYPE is a - symbol (which is looked up in `embark-keymap-alist' to - determine which additional keybindings for actions to setup); - -2. a dotted list of the form (TYPE TARGET START . END), where - START and END are the buffer positions bounding TARGET, used - for highlighting; or - -3. a possibly empty list of targets, each of type 1 or 2 (in - particular if a target finder does not find any targets, it - should return nil)." - :type 'hook) - -(defcustom embark-transformer-alist - '((minor-mode . embark--lookup-lighter-minor-mode) - (embark-keybinding . embark--keybinding-command) - (project-file . embark--project-file-full-path) - (package . embark--remove-package-version) - (multi-category . embark--refine-multi-category) - (file . embark--simplify-path)) - "Alist associating type to functions for transforming targets. -Each function should take a type and a target string and return a -pair of the form a `cons' of the new type and the new target." - :type '(alist :key-type symbol :value-type function)) - -(defcustom embark-become-keymaps - '(embark-become-help-map - embark-become-file+buffer-map - embark-become-shell-command-map - embark-become-match-map) - "List of keymaps for `embark-become'. -Each keymap groups a set of related commands that can -conveniently become one another." - :type '(repeat variable)) - -(defcustom embark-prompter 'embark-keymap-prompter - "Function used to prompt the user for actions. -This should be set to a function that prompts the use for an -action and returns the symbol naming the action command. The -default value, `embark-keymap-prompter' activates the type -specific action keymap given in `embark-keymap-alist'. -There is also `embark-completing-read-prompter' which -prompts for an action with completion." - :type '(choice (const :tag "Use action keymaps" embark-keymap-prompter) - (const :tag "Read action with completion" - embark-completing-read-prompter) - (function :tag "Other"))) - -(defcustom embark-keymap-prompter-key "@" - "Key to switch to the keymap prompter from `embark-completing-read-prompter'. - -The key must be either nil or a string. The -string must be accepted by `key-valid-p'." - :type '(choice key (const :tag "None" nil))) - -(defcustom embark-cycle-key nil - "Key used for `embark-cycle'. - -If the key is set to nil it defaults to the global binding of -`embark-act'. The key must be a string which is accepted by -`key-valid-p'." - :type '(choice key (const :tag "Use embark-act key" nil))) - -(defcustom embark-help-key "C-h" - "Key used for help. - -The key must be either nil or a string. The -string must be accepted by `key-valid-p'." - :type '(choice (const "C-h") - (const "?") - (const :tag "None" nil) - key)) - -(defcustom embark-keybinding-repeat - (propertize "*" 'face 'embark-keybinding-repeat) - "Indicator string for repeatable keybindings. -Keybindings are formatted by the `completing-read' prompter and -the verbose indicator." - :type 'string) - -(defface embark-keybinding-repeat - '((t :inherit font-lock-builtin-face)) - "Face used to indicate keybindings as repeatable.") - -(defface embark-keybinding '((t :inherit success)) - "Face used to display key bindings. -Used by `embark-completing-read-prompter' and `embark-keymap-help'.") - -(defface embark-keymap '((t :slant italic)) - "Face used to display keymaps. -Used by `embark-completing-read-prompter' and `embark-keymap-help'.") - -(defface embark-target '((t :inherit highlight)) - "Face used to highlight the target at point during `embark-act'.") - -(defcustom embark-quit-after-action t - "Should `embark-act' quit the minibuffer? -This controls whether calling `embark-act' without a prefix -argument quits the minibuffer or not. You can always get the -opposite behavior to that indicated by this variable by calling -`embark-act' with \\[universal-argument]. - -Note that `embark-act' can also be called from outside the -minibuffer and this variable is irrelevant in that case. - -In addition to t or nil this variable can also be set to an -alist to specify the minibuffer quitting behavior per command. -In the alist case one can additionally use the key t to -prescribe a default for commands not used as alist keys." - :type '(choice (const :tag "Always quit" t) - (const :tag "Never quit" nil) - (alist :tag "Configure per action" - :key-type (choice (function :tag "Action") - (const :tag "All other actions" t)) - :value-type (choice (const :tag "Quit" t) - (const :tag "Do not quit" nil))))) - -(defcustom embark-confirm-act-all t - "Should `embark-act-all' prompt the user for confirmation? -Even if this variable is nil you may still be prompted to confirm -some uses of `embark-act-all', namely, for those actions whose -entry in `embark-pre-action-hooks' includes `embark--confirm'." - :type 'boolean) - -(defcustom embark-default-action-overrides nil - "Alist associating target types with overriding default actions. -When the source of a target is minibuffer completion, the default -action for it is usually the command that opened the minibuffer -in the first place but this can be overridden for a given type by -an entry in this list. - -For example, if you run `delete-file' the default action for its -completion candidates is `delete-file' itself. You may prefer to -make `find-file' the default action for all files, even if they -were obtained from a `delete-file' prompt. In that case you can -configure that by adding an entry to this variable pairing `file' -with `find-file'. - -In addition to target types, you can also use as keys in this alist, -pairs of a target type and a command name. Such a pair indicates that -the override only applies if the target was obtained from minibuffer -completion from that command. For example adding an -entry (cons (cons \\='file \\='delete-file) \\='find-file) to this alist would -indicate that for files at the prompt of the `delete-file' command, -`find-file' should be used as the default action." - :type '(alist :key-type (choice (symbol :tag "Type") - (cons (symbol :tag "Type") - (symbol :tag "Command"))) - :value-type (function :tag "Default action"))) - -(defcustom embark-target-injection-hooks - '((async-shell-command embark--allow-edit embark--shell-prep) - (shell-command embark--allow-edit embark--shell-prep) - (pp-eval-expression embark--eval-prep) - (eval-expression embark--eval-prep) - (package-delete embark--force-complete) - ;; commands evaluating code found in the buffer, which may in turn prompt - (embark-pp-eval-defun embark--ignore-target) - (eval-defun embark--ignore-target) - (eval-last-sexp embark--ignore-target) - (embark-eval-replace embark--ignore-target) - ;; commands which prompt for something that is *not* the target - (write-region embark--ignore-target) - (append-to-file embark--ignore-target) - (append-to-buffer embark--ignore-target) - (shell-command-on-region embark--ignore-target) - (format-encode-region embark--ignore-target) - (format-decode-region embark--ignore-target) - (xref-find-definitions embark--ignore-target) - (xref-find-references embark--ignore-target) - (sort-regexp-fields embark--ignore-target) - (align-regexp embark--ignore-target)) - "Alist associating commands with post-injection setup hooks. -For commands appearing as keys in this alist, run the -corresponding value as a setup hook after injecting the target -into in the minibuffer and before acting on it. The hooks must -accept arbitrary keyword arguments. The :action command, the -:target string and target :type are always present. For actions -at point the target :bounds are passed too. The default pre-action -hook is specified by the entry with key t. Furthermore, hooks with -the key :always are executed always." - :type '(alist :key-type - (choice symbol - (const :tag "Default" t) - (const :tag "Always" :always)) - :value-type hook)) - -(defcustom embark-pre-action-hooks - `(;; commands that need to position point at the beginning or end - (eval-last-sexp embark--end-of-target) - (indent-pp-sexp embark--beginning-of-target) - (backward-up-list embark--beginning-of-target) - (backward-list embark--beginning-of-target) - (forward-list embark--end-of-target) - (forward-sexp embark--end-of-target) - (backward-sexp embark--beginning-of-target) - (raise-sexp embark--beginning-of-target) - (kill-sexp embark--beginning-of-target) - (mark-sexp embark--beginning-of-target) - (transpose-sexps embark--end-of-target) - (transpose-sentences embark--end-of-target) - (transpose-paragraphs embark--end-of-target) - (forward-sentence embark--end-of-target) - (backward-sentence embark--beginning-of-target) - (backward-paragraph embark--beginning-of-target) - (embark-insert embark--end-of-target) - ;; commands we want to be able to jump back from - ;; (embark-find-definition achieves this by calling - ;; xref-find-definitions which pushes the markers itself) - (find-library embark--xref-push-marker) - ;; commands which prompt the user for confirmation before running - (delete-file embark--confirm) - (delete-directory embark--confirm) - (kill-buffer embark--confirm) - (embark-kill-buffer-and-window embark--confirm) - (bookmark-delete embark--confirm) - (package-delete embark--confirm) - (,'tab-bar-close-tab-by-name embark--confirm) ;; Avoid package-lint warning - ;; search for region contents outside said region - (embark-isearch-forward embark--unmark-target) - (embark-isearch-backward embark--unmark-target) - (occur embark--unmark-target) - (query-replace embark--beginning-of-target embark--unmark-target) - (query-replace-regexp embark--beginning-of-target embark--unmark-target) - (replace-string embark--beginning-of-target embark--unmark-target) - (replace-regexp embark--beginning-of-target embark--unmark-target) - ;; mark pseudo-action - (mark embark--mark-target) - ;; shells in new buffers - (shell embark--universal-argument) - (eshell embark--universal-argument)) - "Alist associating commands with pre-action hooks. -The hooks are run right before an action is embarked upon. See -`embark-target-injection-hooks' for information about the hook -arguments and more details." - :type '(alist :key-type - (choice symbol - (const :tag "Default" t) - (const :tag "Always" :always)) - :value-type hook)) - -(defcustom embark-post-action-hooks - `((bookmark-delete embark--restart) - (bookmark-rename embark--restart) - (delete-file embark--restart) - (embark-kill-ring-remove embark--restart) - (embark-recentf-remove embark--restart) - (embark-history-remove embark--restart) - (rename-file embark--restart) - (copy-file embark--restart) - (delete-directory embark--restart) - (make-directory embark--restart) - (kill-buffer embark--restart) - (embark-rename-buffer embark--restart) - (,'tab-bar-rename-tab-by-name embark--restart) ;; Avoid package-lint warning - (,'tab-bar-close-tab-by-name embark--restart) - (package-delete embark--restart)) - "Alist associating commands with post-action hooks. -The hooks are run after an embarked upon action concludes. See -`embark-target-injection-hooks' for information about the hook -arguments and more details." - :type '(alist :key-type - (choice symbol - (const :tag "Default" t) - (const :tag "Always" :always)) - :value-type hook)) - -(defcustom embark-around-action-hooks - '(;; use directory of target as default-directory - (shell embark--cd) - (eshell embark--cd) - ;; mark the target preserving point and previous mark - (kill-region embark--mark-target) - (kill-ring-save embark--mark-target) - (indent-region embark--mark-target) - (ispell-region embark--mark-target) - (fill-region embark--mark-target) - (upcase-region embark--mark-target) - (downcase-region embark--mark-target) - (capitalize-region embark--mark-target) - (count-words-region embark--mark-target) - (count-words embark--mark-target) - (delete-duplicate-lines embark--mark-target) - (shell-command-on-region embark--mark-target) - (delete-region embark--mark-target) - (format-encode-region embark--mark-target) - (format-decode-region embark--mark-target) - (write-region embark--mark-target) - (append-to-file embark--mark-target) - (append-to-buffer embark--mark-target) - (shell-command-on-region embark--mark-target) - (embark-eval-replace embark--mark-target) - (delete-indentation embark--mark-target) - (comment-dwim embark--mark-target) - (insert-parentheses embark--mark-target) - (insert-pair embark--mark-target) - (org-emphasize embark--mark-target) - ;; do the actual work of selecting & deselecting targets - (embark-select embark--select)) - "Alist associating commands with post-action hooks. -The hooks are run instead of the embarked upon action. The hook -can decide whether or not to run the action or it can run it -in some special environment, like inside a let-binding or inside -`save-excursion'. Each hook is called with keyword argument :run -providing a function encapsulating the following around hooks and -the action; the hook additionally receives the keyword arguments -used for other types of action hooks, for more details see -`embark-target-injection-hooks'." - :type '(alist :key-type - (choice symbol - (const :tag "Default" t) - (const :tag "Always" :always)) - :value-type hook)) - -(when (version-list-< (version-to-list emacs-version) '(29 1)) - ;; narrow to target for duration of action - (setf (alist-get 'repunctuate-sentences embark-around-action-hooks) - '(embark--narrow-to-target))) - -(defcustom embark-multitarget-actions '(embark-insert embark-copy-as-kill) - "Commands for which `embark-act-all' should pass a list of targets. -Normally `embark-act-all' runs the same action on each candidate -separately, but when a command included in this variable's value -is used as an action, `embark-act-all' will instead call it -non-interactively with a single argument: the list of all -candidates. For commands on this list `embark-act' behaves -similarly: it calls them non-interactively with a single -argument: a one element list containing the target." - :type '(repeat function)) - -(defcustom embark-repeat-actions - '((mark . region) - ;; outline commands - outline-next-visible-heading outline-previous-visible-heading - outline-forward-same-level outline-backward-same-level - outline-demote outline-promote - outline-show-subtree (outline-mark-subtree . region) - outline-move-subtree-up outline-move-subtree-down - outline-up-heading outline-hide-subtree outline-cycle - ;; org commands (remapped outline commands) - org-forward-heading-same-level org-backward-heading-same-level - org-next-visible-heading org-previous-visible-heading - org-demote-subtree org-promote-subtree - org-show-subtree (org-mark-subtree . region) - org-move-subtree-up org-move-subtree-down - ;; transpose commands - transpose-sexps transpose-sentences transpose-paragraphs - ;; navigation commands - flymake-goto-next-error flymake-goto-prev-error - embark-next-symbol embark-previous-symbol - backward-up-list backward-list forward-list forward-sexp - backward-sexp forward-sentence backward-sentence - forward-paragraph backward-paragraph - ;; smerge commands - smerge-refine smerge-combine-with-next smerge-prev smerge-next) - "List of repeatable actions. -When you use a command on this list as an Embark action from -outside the minibuffer, `embark-act' does not exit but instead -lets you act again on the possibly new target you reach. - -By default, after using one of these actions, when `embark-act' -looks for targets again, it will start the target cycle at the -same type as the previously acted upon target; that is, you -\"don't loose your place in the target cycle\". - -Sometimes, however, you'll want to prioritize a different type of -target to continue acting on. The main example of this that if -you use a marking command as an action, you almost always want to -act on the region next. For those cases, in addition to -commands, you can also place on this list a pair of a command and -the desired starting type for the target cycle for the next -action." - :type '(repeat (choice function - (cons function - (symbol :tag "Next target type"))))) - -;;; Overlay properties - -;; high priority to override both bug reference and the lazy -;; isearch highlights in embark-isearch-highlight-indicator -(put 'embark-target-overlay 'face 'embark-target) -(put 'embark-target-overlay 'priority 1001) -(put 'embark-selected-overlay 'face 'embark-selected) -(put 'embark-selected-overlay 'priority 1001) - -;;; Stashing information for actions in buffer local variables - -(defvar-local embark--type nil - "Cache for the completion type, meant to be set buffer-locally.") - -(defvar-local embark--target-buffer nil - "Cache for the previous buffer, meant to be set buffer-locally.") - -(defvar-local embark--target-window nil - "Cache for the previous window, meant to be set buffer-locally. -Since windows can be reused to display different buffers, this -window should only be used if it displays the buffer stored in -the variable `embark--target-buffer'.") - -(defvar-local embark--command nil - "Command that started the completion session.") - -(defvar-local embark--toggle-quit nil - "Should we toggle the default quitting behavior for the next action?") - -(defun embark--minibuffer-point () - "Return length of minibuffer contents." - (max 0 (- (point) (minibuffer-prompt-end)))) - -(defun embark--default-directory () - "Guess a reasonable default directory for the current candidates." - (if (and (minibufferp) minibuffer-completing-file-name) - (let ((end (minibuffer-prompt-end)) - (contents (minibuffer-contents))) - (expand-file-name - (substitute-in-file-name - (buffer-substring - end - (+ end - (or (cdr - (last - (completion-all-completions - contents - minibuffer-completion-table - minibuffer-completion-predicate - (embark--minibuffer-point)))) - (cl-position ?/ contents :from-end t) - 0)))))) - default-directory)) - -(defun embark--target-buffer () - "Return buffer that should be targeted by Embark actions." - (cond - ((and (minibufferp) minibuffer-completion-table (minibuffer-selected-window)) - (window-buffer (minibuffer-selected-window))) - ((and embark--target-buffer (buffer-live-p embark--target-buffer)) - embark--target-buffer) - (t (current-buffer)))) - -(defun embark--target-window (&optional display) - "Return window which should be selected when Embark actions run. -If DISPLAY is non-nil, call `display-buffer' to produce the -window if necessary." - (cond - ((and (minibufferp) minibuffer-completion-table (minibuffer-selected-window)) - (minibuffer-selected-window)) - ((and embark--target-window - (window-live-p embark--target-window) - (or (not (buffer-live-p embark--target-buffer)) - (eq (window-buffer embark--target-window) embark--target-buffer))) - embark--target-window) - ((and embark--target-buffer (buffer-live-p embark--target-buffer)) - (or (get-buffer-window embark--target-buffer) - (when display (display-buffer embark--target-buffer)))) - (display (selected-window)))) - -(defun embark--cache-info (buffer) - "Cache information needed for actions in variables local to BUFFER. -BUFFER defaults to the current buffer." - (let ((cmd embark--command) - (dir (embark--default-directory)) - (target-buffer (embark--target-buffer)) - (target-window (embark--target-window))) - (with-current-buffer buffer - (setq embark--command cmd - default-directory dir - embark--target-buffer target-buffer - embark--target-window target-window)))) - -(defun embark--cache-info--completion-list () - "Cache information needed for actions in a *Completions* buffer. -Meant to be be added to `completion-setup-hook'." - ;; when completion-setup-hook hook runs, the *Completions* buffer is - ;; available in the variable standard-output - (embark--cache-info standard-output) - (with-current-buffer standard-output - (when (minibufferp completion-reference-buffer) - (setq embark--type - (completion-metadata-get - (with-current-buffer completion-reference-buffer - (embark--metadata)) - 'category))))) - -;; We have to add this *after* completion-setup-function because that's -;; when the buffer is put in completion-list-mode and turning the mode -;; on kills all local variables! So we use a depth of 5. -(add-hook 'completion-setup-hook #'embark--cache-info--completion-list 5) - -;;;###autoload -(progn - (defun embark--record-this-command () - "Record command which opened the minibuffer. -We record this because it will be the default action. -This function is meant to be added to `minibuffer-setup-hook'." - (setq-local embark--command this-command)) - (add-hook 'minibuffer-setup-hook #'embark--record-this-command)) - -;;; Internal variables - -(defvar embark--prompter-history nil - "History used by the `embark-completing-read-prompter'.") - -;;; Core functionality - -(defconst embark--verbose-indicator-buffer " *Embark Actions*") - -(defvar embark--minimal-indicator-overlay nil) - -(defun embark--metadata () - "Return current minibuffer completion metadata." - (completion-metadata - (buffer-substring-no-properties - (minibuffer-prompt-end) - (max (minibuffer-prompt-end) (point))) - minibuffer-completion-table - minibuffer-completion-predicate)) - -(defun embark-target-active-region () - "Target the region if active." - (when (use-region-p) - (let ((start (region-beginning)) - (end (region-end))) - `(region ,(buffer-substring start end) . (,start . ,end))))) - -(autoload 'dired-get-filename "dired") -(declare-function image-dired-original-file-name "image-dired") - -(defun embark-target-guess-file-at-point () - "Target the file guessed by `ffap' at point." - (when-let ((tap-file (thing-at-point 'filename)) - ((not (ffap-url-p tap-file))) ; no URLs, those have a target finder - (bounds (bounds-of-thing-at-point 'filename)) - (file (ffap-file-at-point))) - ;; ffap doesn't make bounds available, so we use - ;; thingatpt bounds, which might be a little off - ;; adjust bounds if thingatpt gobbled punctuation around file - (when (or (string-match (regexp-quote file) tap-file) - (string-match (regexp-quote (file-name-base file)) tap-file)) - (setq bounds (cons (+ (car bounds) (match-beginning 0)) - (- (cdr bounds) (- (length tap-file) - (match-end 0)))))) - `(file ,(abbreviate-file-name (expand-file-name file)) ,@bounds))) - -(defun embark-target-file-at-point () - "Target file at point. -This function mostly relies on `ffap-file-at-point', with the -following exceptions: - -- In `dired-mode', it uses `dired-get-filename' instead. - -- In `imaged-dired-thumbnail-mode', it uses - `image-dired-original-file-name' instead." - (let (file bounds) - (or (and (derived-mode-p 'dired-mode) - (setq file (dired-get-filename t 'no-error-if-not-filep)) - (setq bounds - (cons - (save-excursion (dired-move-to-filename) (point)) - (save-excursion (dired-move-to-end-of-filename) (point))))) - (and (derived-mode-p 'image-dired-thumbnail-mode) - (setq file (image-dired-original-file-name)) - (setq bounds (cons (point) (1+ (point))))) - (when-let ((tap-file (thing-at-point 'filename)) - ((not (equal (file-name-base tap-file) tap-file))) - (guess (embark-target-guess-file-at-point))) - (setq file (cadr guess) bounds (cddr guess)))) - (when file - `(file ,(abbreviate-file-name (expand-file-name file)) ,@bounds)))) - -(defun embark-target-package-at-point () - "Target the package on the current line in a packages buffer." - (when (derived-mode-p 'package-menu-mode) - (when-let ((pkg (get-text-property (point) 'tabulated-list-id))) - `(package ,(symbol-name (package-desc-name pkg)) - ,(line-beginning-position) . ,(line-end-position))))) - -(defun embark-target-email-at-point () - "Target the email address at point." - (when-let ((email (thing-at-point 'email))) - (when (string-prefix-p "mailto:" email) - (setq email (string-remove-prefix "mailto:" email))) - `(email ,email . ,(bounds-of-thing-at-point 'email)))) - -(defun embark-target-url-at-point () - "Target the URL at point." - (if-let ((url (or (get-text-property (point) 'shr-url) - (get-text-property (point) 'image-url)))) - `(url ,url - ,(previous-single-property-change - (min (1+ (point)) (point-max)) 'mouse-face nil (point-min)) - . ,(next-single-property-change - (point) 'mouse-face nil (point-max))) - (when-let ((url (thing-at-point 'url))) - `(url ,url . ,(thing-at-point-bounds-of-url-at-point t))))) - -(declare-function widget-at "wid-edit") - -(defun embark-target-custom-variable-at-point () - "Target the variable corresponding to the customize widget at point." - (when (derived-mode-p 'Custom-mode) - (save-excursion - (beginning-of-line) - (when-let* ((widget (widget-at (point))) - (var (and (eq (car widget) 'custom-visibility) - (plist-get (cdr widget) :parent))) - (sym (and (eq (car var) 'custom-variable) - (plist-get (cdr var) :value)))) - `(variable - ,(symbol-name sym) - ,(point) - . ,(progn - (re-search-forward ":" (line-end-position) 'noerror) - (point))))))) - -;; NOTE: There is also (thing-at-point 'list), however it does -;; not work on strings and requires the point to be inside the -;; parentheses. This version here is slightly more general. -(defun embark-target-expression-at-point () - "Target expression at point." - (cl-flet ((syntax-p (class &optional (delta 0)) - (and (<= (point-min) (+ (point) delta) (point-max)) - (eq (pcase class - ('open 4) ('close 5) ('prefix 6) ('string 7)) - (syntax-class (syntax-after (+ (point) delta))))))) - (when-let - ((start - (pcase-let ((`(_ ,open _ ,string _ _ _ _ ,start _ _) (syntax-ppss))) - (ignore-errors ; set start=nil if delimiters are unbalanced - (cond - (string start) - ((or (syntax-p 'open) (syntax-p 'prefix)) - (save-excursion (backward-prefix-chars) (point))) - ((syntax-p 'close -1) - (save-excursion - (backward-sexp) (backward-prefix-chars) (point))) - ((syntax-p 'string) (point)) - ((syntax-p 'string -1) (scan-sexps (point) -1)) - (t open))))) - (end (ignore-errors (scan-sexps start 1)))) - (unless (eq start (car (bounds-of-thing-at-point 'defun))) - `(expression ,(buffer-substring start end) ,start . ,end))))) - -(defmacro embark-define-overlay-target (name prop &optional pred type target) - "Define a target finder for NAME that targets overlays with property PROP. -The function defined is named embark-target-NAME-at-point and it -returns Embark targets based on the overlays around point. An -overlay provides a target if its property named PROP is non-nil. - -If the optional PRED argument is given, it should be an -expression and it further restricts the targets to only those -overlays for which PRED evaluates to non-nil. - -The target finder returns target type NAME or optional symbol -TYPE if given. - -The target finder returns the substring of the buffer covered by -the overlay as the target string or the result of evaluating the -optional TARGET expression if given. - -PRED and TARGET are expressions (not functions) and when evaluated the -symbols `%o' and `%p' are bound to the overlay and the overlay's -property respectively." - `(defun ,(intern (format "embark-target-%s-at-point" name)) () - ,(format "Target %s at point." name) - (when-let ((%o (seq-find - (lambda (%o) - (when-let ((%p (overlay-get %o ',prop))) - (ignore %p) - ,(or pred t))) - (overlays-in (max (point-min) (1- (point))) - (min (point-max) (1+ (point)))))) - (%p (overlay-get %o ',prop))) - (ignore %p) - (cons ',(or type name) - (cons ,(or target `(buffer-substring-no-properties - (overlay-start %o) (overlay-end %o))) - (cons (overlay-start %o) (overlay-end %o))))))) - -(embark-define-overlay-target flymake flymake-diagnostic) -(embark-define-overlay-target bug-reference bug-reference-url nil url %p) -(embark-define-overlay-target smerge smerge (eq %p 'conflict)) - -(defmacro embark-define-thingatpt-target (thing &rest modes) - "Define a target finder for THING using the thingatpt library. -The function defined is named embark-target-NAME-at-point and it -uses (thing-at-point 'THING) to find its targets. - -If any MODES are given, the target finder only applies to buffers -in one of those major modes." - (declare (indent 1)) - `(defun ,(intern (format "embark-target-%s-at-point" thing)) () - ,(format "Target %s at point." thing) - (when ,(if modes `(derived-mode-p ,@(mapcar (lambda (m) `',m) modes)) t) - (when-let (bounds (bounds-of-thing-at-point ',thing)) - (cons ',thing (cons - (buffer-substring (car bounds) (cdr bounds)) - bounds)))))) - -(embark-define-thingatpt-target defun) -(embark-define-thingatpt-target sentence - text-mode help-mode Info-mode man-common) -(embark-define-thingatpt-target paragraph - text-mode help-mode Info-mode man-common) - -(defmacro embark-define-regexp-target - (name regexp &optional type target bounds limit) - "Define a target finder for matches of REGEXP around point. -The function defined is named embark-target-NAME-at-point and it -uses (thing-at-point-looking-at REGEXP) to find its targets. - -The target finder returns target type NAME or optional symbol -TYPE if given. - -The target finder returns the substring of the buffer matched by -REGEXP as the target string or the result of evaluating the -optional TARGET expression if given. In the expression TARGET -you can use `match-string' to recover the match of the REGEXP or -of any sub-expressions it has. - -BOUNDS is an optional expression to compute the bounds of the -target and defaults to (cons (match-beginning 0) (match-end 0)). - -The optional LIMIT is the number of characters before and after -point to limit the search to. If LIMIT is nil, search a little -more than the current line (more precisely, the smallest interval -centered at point that includes the current line)." - `(defun ,(intern (format "embark-target-%s-at-point" name)) () - ,(format "Target %s at point." name) - (save-match-data - (when (thing-at-point-looking-at - ,regexp - ,(or limit '(max (- (pos-eol) (point)) (- (point) (pos-bol))))) - (cons ',(or type name) - (cons ,(or target '(match-string 0)) - ,(or bounds - '(cons (match-beginning 0) (match-end 0))))))))) - -(defun embark--identifier-types (identifier) - "Return list of target types appropriate for IDENTIFIER." - (let ((symbol (intern-soft identifier))) - (if (not - (or (derived-mode-p 'emacs-lisp-mode 'inferior-emacs-lisp-mode) - (and (not (derived-mode-p 'prog-mode)) - symbol - (or (boundp symbol) (fboundp symbol) (symbol-plist symbol))))) - '(identifier) - (let* ((library (ffap-el-mode identifier)) - (types - (append - (and (commandp symbol) '(command)) - (and symbol (boundp symbol) (not (keywordp symbol)) '(variable)) - (and (fboundp symbol) (not (commandp symbol)) '(function)) - (and (facep symbol) '(face)) - (and library '(library)) - (and (featurep 'package) (embark--package-desc symbol) - '(package))))) - (when (and library - (looking-back "\\(?:require\\|use-package\\).*" - (line-beginning-position))) - (setq types (embark--rotate types (cl-position 'library types)))) - (or types '(symbol)))))) - -(defun embark-target-identifier-at-point () - "Target identifier at point. - -In Emacs Lisp and IELM buffers the identifier is promoted to a -symbol, for which more actions are available. Identifiers are -also promoted to symbols if they are interned Emacs Lisp symbols -and found in a buffer in a major mode that is not derived from -`prog-mode' (this is intended for when you might be reading or -writing about Emacs). - -As a convenience, in Org Mode an initial ' or surrounding == or -~~ are removed." - (when-let (bounds (bounds-of-thing-at-point 'symbol)) - (let ((name (buffer-substring (car bounds) (cdr bounds)))) - (when (derived-mode-p 'org-mode) - (cond ((string-prefix-p "'" name) - (setq name (substring name 1)) - (cl-incf (car bounds))) - ((string-match-p "^\\([=~]\\).*\\1$" name) - (setq name (substring name 1 -1)) - (cl-incf (car bounds)) - (cl-decf (cdr bounds))))) - (mapcar (lambda (type) `(,type ,name . ,bounds)) - (embark--identifier-types name))))) - -(defun embark-target-heading-at-point () - "Target the outline heading at point." - (let ((beg (line-beginning-position)) - (end (line-end-position))) - (when (save-excursion - (goto-char beg) - (and (bolp) - (looking-at - ;; default definition from outline.el - (or (bound-and-true-p outline-regexp) "[*\^L]+")))) - (require 'outline) ;; Ensure that outline commands are available - `(heading ,(buffer-substring beg end) ,beg . ,end)))) - -(defun embark-target-text-heading-at-point () - "Target the outline heading at point in text modes." - (when (derived-mode-p 'text-mode) - (embark-target-heading-at-point))) - -(defun embark-target-prog-heading-at-point () - "Target the outline heading at point in programming modes." - (when (derived-mode-p 'prog-mode) - (embark-target-heading-at-point))) - -(defun embark-target-top-minibuffer-candidate () - "Target the top completion candidate in the minibuffer. -Return the category metadatum as the type of the target. - -This target finder is meant for the default completion UI and -completion UI highly compatible with it, like Icomplete. -Many completion UIs can still work with Embark but will need -their own target finder. See for example -`embark--vertico-selected'." - (when (and (minibufferp) minibuffer-completion-table) - (pcase-let* ((`(,category . ,candidates) (embark-minibuffer-candidates)) - (contents (minibuffer-contents)) - (top (if (test-completion contents - minibuffer-completion-table - minibuffer-completion-predicate) - contents - (let ((completions (completion-all-sorted-completions))) - (if (null completions) - contents - (concat - (substring contents - 0 (or (cdr (last completions)) 0)) - (car completions))))))) - (cons category (or (car (member top candidates)) top))))) - -(defun embark-target-collect-candidate () - "Target the collect candidate at point." - (when (derived-mode-p 'embark-collect-mode) - (when-let ((button - (pcase (get-text-property (point) 'tabulated-list-column-name) - ("Candidate" (button-at (point))) - ("Annotation" (previous-button (point))))) - (start (button-start button)) - (end (button-end button)) - (candidate (tabulated-list-get-id))) - `(,embark--type - ,(if (eq embark--type 'file) - (abbreviate-file-name (expand-file-name candidate)) - candidate) - ,start . ,end)))) - -(defun embark-target-completion-list-candidate () - "Return the completion candidate at point in a completions buffer." - (when (derived-mode-p 'completion-list-mode) - (if (not (get-text-property (point) 'mouse-face)) - (user-error "No completion here") - ;; this fairly delicate logic is taken from `choose-completion' - (let (beg end) - (cond - ((and (not (eobp)) (get-text-property (point) 'mouse-face)) - (setq end (point) beg (1+ (point)))) - ((and (not (bobp)) - (get-text-property (1- (point)) 'mouse-face)) - (setq end (1- (point)) beg (point))) - (t (user-error "No completion here"))) - (setq beg (previous-single-property-change beg 'mouse-face)) - (setq end (or (next-single-property-change end 'mouse-face) - (point-max))) - (let ((raw (or (get-text-property beg 'completion--string) - (buffer-substring beg end)))) - `(,embark--type - ,(if (eq embark--type 'file) - (abbreviate-file-name (expand-file-name raw)) - raw) - ,beg . ,end)))))) - -(defun embark--cycle-key () - "Return the key to use for `embark-cycle'." - (if embark-cycle-key - (if (key-valid-p embark-cycle-key) - (key-parse embark-cycle-key) - (error "`embark-cycle-key' is invalid")) - (car (where-is-internal #'embark-act)))) - -(defun embark--raw-action-keymap (type) - "Return raw action map for targets of given TYPE. -This does not take into account the default action, help key or -cycling bindings, just what's registered in -`embark-keymap-alist'." - (make-composed-keymap - (mapcar #'symbol-value - (let ((actions (or (alist-get type embark-keymap-alist) - (alist-get t embark-keymap-alist)))) - (ensure-list actions))))) - -(defun embark--action-keymap (type cycle) - "Return action keymap for targets of given TYPE. -If CYCLE is non-nil bind `embark-cycle'." - (make-composed-keymap - (let ((map (make-sparse-keymap)) - (default-action (embark--default-action type))) - (define-key map [13] default-action) - (when-let ((cycle-key (and cycle (embark--cycle-key)))) - (define-key map cycle-key #'embark-cycle)) - (when embark-help-key - (keymap-set map embark-help-key #'embark-keymap-help)) - map) - (embark--raw-action-keymap type))) - -(defun embark--truncate-target (target) - "Truncate TARGET string." - (unless (stringp target) - (setq target (format "%s" target))) - (if-let (pos (string-match-p "\n" target)) - (concat (car (split-string target "\n" 'omit-nulls "\\s-*")) "…") - target)) - -;;;###autoload -(defun embark-eldoc-first-target (report &rest _) - "Eldoc function reporting the first Embark target at point. -This function uses the eldoc REPORT callback and is meant to be -added to `eldoc-documentation-functions'." - (when-let (((not (minibufferp))) - (target (car (embark--targets)))) - (funcall report - (format "Embark on %s ‘%s’" - (plist-get target :type) - (embark--truncate-target (plist-get target :target)))))) - -;;;###autoload -(defun embark-eldoc-target-types (report &rest _) - "Eldoc function reporting the types of all Embark targets at point. -This function uses the eldoc REPORT callback and is meant to be -added to `eldoc-documentation-functions'." - (when-let (((not (minibufferp))) - (targets (embark--targets))) - (funcall report - (format "Embark target types: %s" - (mapconcat - (lambda (target) (symbol-name (plist-get target :type))) - targets - ", "))))) - -(defun embark--format-targets (target shadowed-targets rep) - "Return a formatted string indicating the TARGET of an action. - -This is used internally by the minimal indicator and for the -targets section of the verbose indicator. The string will also -mention any SHADOWED-TARGETS. A non-nil REP indicates we are in -a repeating sequence of actions." - (let ((act (propertize - (cond - ((plist-get target :multi) "∀ct") - (rep "Rep") - (t "Act")) - 'face 'highlight))) - (cond - ((eq (plist-get target :type) 'embark-become) - (propertize "Become" 'face 'highlight)) - ((and (minibufferp) - (not (eq 'embark-keybinding - (completion-metadata-get - (embark--metadata) - 'category)))) - ;; we are in a minibuffer but not from the - ;; completing-read prompter, use just "Act" - act) - ((plist-get target :multi) - (format "%s on %s %ss" - act - (plist-get target :multi) - (plist-get target :type))) - (t (format - "%s on %s%s ‘%s’" - act - (plist-get target :type) - (if shadowed-targets - (format (propertize "(%s)" 'face 'shadow) - (mapconcat - (lambda (target) (symbol-name (plist-get target :type))) - shadowed-targets - ", ")) - "") - (embark--truncate-target (plist-get target :target))))))) - -(defun embark-minimal-indicator () - "Minimal indicator, appearing in the minibuffer prompt or echo area. -This indicator displays a message showing the types of all -targets, starting with the current target, and the value of the -current target. The message is displayed in the echo area, or if -the minibuffer is open, the message is added to the prompt." - (lambda (&optional keymap targets _prefix) - (if (null keymap) - (when embark--minimal-indicator-overlay - (delete-overlay embark--minimal-indicator-overlay) - (setq-local embark--minimal-indicator-overlay nil)) - (let ((indicator (embark--format-targets - (car targets) (cdr targets) - (eq (lookup-key keymap [13]) #'embark-done)))) - (if (not (minibufferp)) - (message "%s" indicator) - (unless embark--minimal-indicator-overlay - (setq-local embark--minimal-indicator-overlay - (make-overlay (point-min) (point-min) - (current-buffer) t t))) - (overlay-put embark--minimal-indicator-overlay - 'before-string (concat indicator - (if (<= (length indicator) - (* 0.4 (frame-width))) - " " - "\n")))))))) - -(defun embark--read-key-sequence (update) - "Read key sequence, call UPDATE function with prefix keys." - (let (timer prefix) - (unwind-protect - (progn - (when (functionp update) - (setq timer (run-at-time - 0.05 0.05 - (lambda () - (let ((new-prefix (this-single-command-keys))) - (unless (equal prefix new-prefix) - (setq prefix new-prefix) - (when (/= (length prefix) 0) - (funcall update prefix)))))))) - (read-key-sequence-vector nil nil nil t 'cmd-loop)) - (when timer - (cancel-timer timer))))) - -(defvar embark-indicators) ; forward declaration - -(defun embark-keymap-prompter (keymap update) - "Let the user choose an action using the bindings in KEYMAP. -Besides the bindings in KEYMAP, the user is free to use all their -key bindings and even \\[execute-extended-command] to select a command. -UPDATE is the indicator update function." - (let* ((keys (let ((overriding-terminal-local-map keymap)) - (embark--read-key-sequence update))) - (cmd (let ((overriding-terminal-local-map keymap)) - (key-binding keys 'accept-default)))) - ;; Set last-command-event as it would be from the command loop. - ;; Previously we only set it locally for digit-argument and for - ;; the mouse scroll commands handled in this function. But other - ;; commands can need it too! For example, electric-pair-mode users - ;; may wish to bind ( to self-insert-command in embark-region-map. - ;; Also, as described in issue #402, there are circumstances where - ;; you might run consult-narrow through the embark-keymap-prompter. - (setq last-command-event (aref keys (1- (length keys)))) - (pcase cmd - ((or 'embark-keymap-help - (and 'nil ; cmd is nil but last key is help-char - (guard (eq help-char (aref keys (1- (length keys))))))) - (let ((embark-indicators - (cl-set-difference embark-indicators - '(embark-verbose-indicator - embark-mixed-indicator))) - (prefix-map - (if (eq cmd 'embark-keymap-help) - keymap - (let ((overriding-terminal-local-map keymap)) - (key-binding (seq-take keys (1- (length keys))) - 'accept-default)))) - (prefix-arg prefix-arg)) ; preserve prefix arg - (when-let ((win (get-buffer-window embark--verbose-indicator-buffer - 'visible))) - (quit-window 'kill-buffer win)) - (embark-completing-read-prompter prefix-map update))) - ((or 'universal-argument 'universal-argument-more - 'negative-argument 'digit-argument 'embark-toggle-quit) - ;; prevent `digit-argument' from modifying the overriding map - (let ((overriding-terminal-local-map overriding-terminal-local-map)) - (command-execute cmd)) - (embark-keymap-prompter - (make-composed-keymap universal-argument-map keymap) - update)) - ((or 'minibuffer-keyboard-quit 'abort-recursive-edit 'abort-minibuffers) - nil) - ((guard (let ((def (lookup-key keymap keys))) ; if directly - ; bound, then obey - (and def (not (numberp def))))) ; number means "invalid prefix" - cmd) - ((and (pred symbolp) - (guard (string-suffix-p "self-insert-command" (symbol-name cmd)))) - (minibuffer-message "Not an action") - (embark-keymap-prompter keymap update)) - ((or 'scroll-other-window 'scroll-other-window-down) - (let ((minibuffer-scroll-window - ;; NOTE: Here we special case the verbose indicator! - (or (get-buffer-window embark--verbose-indicator-buffer 'visible) - minibuffer-scroll-window))) - (ignore-errors (command-execute cmd))) - (embark-keymap-prompter keymap update)) - ((or 'scroll-bar-toolkit-scroll 'mwheel-scroll 'mac-mwheel-scroll) - (funcall cmd last-command-event) - (embark-keymap-prompter keymap update)) - ('execute-extended-command - (let ((prefix-arg prefix-arg)) ; preserve prefix arg - (intern-soft (read-extended-command)))) - ((or 'keyboard-quit 'keyboard-escape-quit) - nil) - (_ cmd)))) - -(defun embark--command-name (cmd) - "Return an appropriate name for CMD. -If CMD is a symbol, use its symbol name; for lambdas, use the -first line of the documentation string; for keyboard macros use -`key-description'; otherwise use the word \"unnamed\"." - (concat ; fresh copy, so we can freely add text properties - (cond - ((or (stringp cmd) (vectorp cmd)) (key-description cmd)) - ((stringp (car-safe cmd)) (car cmd)) - ((eq (car-safe cmd) 'menu-item) (eval (cadr cmd))) - ((keymapp cmd) - (propertize (if (symbolp cmd) (format "+%s" cmd) "") - 'face 'embark-keymap)) - ((symbolp cmd) - (let ((name (symbol-name cmd))) - (if (string-prefix-p "embark-action--" name) ; direct action mode - (format "(%s)" (string-remove-prefix "embark-action--" name)) - name))) - ((when-let (doc (and (functionp cmd) (ignore-errors (documentation cmd)))) - (save-match-data - (when (string-match "^\\(.*\\)$" doc) - (match-string 1 doc))))) - (t "")))) - -;; Taken from Marginalia, needed by the verbose indicator. -;; We cannot use the completion annotators in this case. -(defconst embark--advice-regexp - (rx bos - (1+ (seq (? "This function has ") - (or ":before" ":after" ":around" ":override" - ":before-while" ":before-until" ":after-while" - ":after-until" ":filter-args" ":filter-return") - " advice: " (0+ nonl) "\n")) - "\n") - "Regexp to match lines about advice in function documentation strings.") - -;; Taken from Marginalia, needed by the verbose indicator. -;; We cannot use the completion annotators in this case. -(defun embark--function-doc (sym) - "Documentation string of function SYM." - (let ((vstr (and (symbolp sym) (keymapp sym) (boundp sym) - (eq (symbol-function sym) (symbol-value sym)) - (documentation-property sym 'variable-documentation)))) - (when-let (str (or (ignore-errors (documentation sym)) vstr)) - ;; Replace standard description with variable documentation - (when (and vstr (string-match-p "\\`Prefix command" str)) - (setq str vstr)) - (save-match-data - (if (string-match embark--advice-regexp str) - (substring str (match-end 0)) - str))))) - -(defun embark--action-repeatable-p (action) - "Is ACTION repeatable? -When the return value is non-nil it will be the desired starting -point of the next target cycle or t to indicate the default, -namely that the target cycle for the next action should begin at -the type of the current target." - (or (cdr (assq action embark-repeat-actions)) - (and (memq action embark-repeat-actions) t))) - -(defun embark--formatted-bindings (keymap &optional nested) - "Return the formatted keybinding of KEYMAP. -The keybindings are returned in their order of appearance. -If NESTED is non-nil subkeymaps are not flattened." - (let* ((commands - (cl-loop for (key . def) in (embark--all-bindings keymap nested) - for name = (embark--command-name def) - for cmd = (keymap--menu-item-binding def) - unless (memq cmd '(nil embark-keymap-help - negative-argument digit-argument)) - collect (list name cmd key - (concat - (if (eq (car-safe def) 'menu-item) - "menu-item" - (key-description key)))))) - (width (cl-loop for (_name _cmd _key desc) in commands - maximize (length desc))) - (default) - (candidates - (cl-loop for item in commands - for (name cmd key desc) = item - for desc-rep = - (concat - (propertize desc 'face 'embark-keybinding) - (and (embark--action-repeatable-p cmd) - embark-keybinding-repeat)) - for formatted = - (propertize - (concat desc-rep - (make-string (- width (length desc-rep) -1) ?\s) - name) - 'embark-command cmd) - when (equal key [13]) - do (setq default formatted) - collect (cons formatted item)))) - (cons candidates default))) - -(defun embark--with-category (category candidates) - "Return completion table for CANDIDATES of CATEGORY with sorting disabled." - (lambda (string predicate action) - (if (eq action 'metadata) - `(metadata (display-sort-function . identity) - (cycle-sort-function . identity) - (category . ,category)) - (complete-with-action - action candidates string predicate)))) - -(defun embark-completing-read-prompter (keymap update &optional no-default) - "Prompt via completion for a command bound in KEYMAP. -If NO-DEFAULT is t, no default value is passed to`completing-read'. - -UPDATE is the indicator update function. It is not used directly -here, but if the user switches to `embark-keymap-prompter', the -UPDATE function is passed to it." - (let* ((candidates+def (embark--formatted-bindings keymap)) - (candidates (car candidates+def)) - (def (and (not no-default) (cdr candidates+def))) - (buf (current-buffer)) - (choice - (catch 'choice - (minibuffer-with-setup-hook - (lambda () - (let ((map (make-sparse-keymap))) - (define-key map "\M-q" - (lambda () - (interactive) - (with-current-buffer buf - (embark-toggle-quit)))) - (when-let (cycle (embark--cycle-key)) - ;; Rebind `embark-cycle' in order allow cycling - ;; from the `completing-read' prompter. Additionally - ;; `embark-cycle' can be selected via - ;; `completing-read'. The downside is that this breaks - ;; recursively acting on the candidates of type - ;; embark-keybinding in the `completing-read' prompter. - (define-key map cycle - (cond - ((eq (lookup-key keymap cycle) 'embark-cycle) - (lambda () - (interactive) - (throw 'choice 'embark-cycle))) - ((null embark-cycle-key) - (lambda () - (interactive) - (minibuffer-message - "No cycling possible; press `%s' again to act." - (key-description cycle)) - (define-key map cycle #'embark-act)))))) - (when embark-keymap-prompter-key - (keymap-set map embark-keymap-prompter-key - (lambda () - (interactive) - (message "Press key binding") - (let ((cmd (embark-keymap-prompter keymap update))) - (if (null cmd) - (user-error "Unknown key") - (throw 'choice cmd)))))) - (use-local-map - (make-composed-keymap map (current-local-map))))) - (completing-read - "Command: " - (embark--with-category 'embark-keybinding candidates) - nil nil nil 'embark--prompter-history def))))) - (pcase (assoc choice candidates) - (`(,_formatted ,_name ,cmd ,key ,_desc) - ;; Set last-command-event as it would be from the command loop. - (setq last-command-event (aref key (1- (length key)))) - cmd) - ('nil (intern-soft choice))))) - -;;; Verbose action indicator - -(defgroup embark-indicators nil - "Indicators display information about actions and targets." - :group 'embark) - -(defcustom embark-indicators - '(embark-mixed-indicator - embark-highlight-indicator - embark-isearch-highlight-indicator) - "Indicator functions to use when acting or becoming. -The indicator functions are called from both `embark-act' and -from `embark-become' and should display information about this to -the user, such as: which of those two commands is running; a -description of the key bindings that are available for actions or -commands to become; and, in the case of `embark-act', the type -and value of the targets, and whether other targets are available -via `embark-cycle'. The indicator function is free to display as -much or as little of this information as desired and can use any -Emacs interface elements to do so. - -Embark comes with five such indicators: - -- `embark-minimal-indicator', which does not display any - information about keybindings, but does display types and - values of action targets in the echo area or minibuffer prompt, - -- `embark-verbose-indicator', which pops up a buffer containing - detailed information including key bindings and the first line - of the docstring of the commands they run, and - -- `embark-mixed-indicator', which combines the minimal and the - verbose indicator: the minimal indicator is shown first and the - verbose popup is shown after `embark-mixed-indicator-delay' - seconds. - -- `embark-highlight-indicator', which highlights the target - at point. - -- `embark-isearch-highlight-indicator', which when the target at - point is an identifier or symbol, lazily highlights all - occurrences of it. - -The protocol for indicator functions is as follows: - -When called from `embark-act', an indicator function is called -without arguments. The indicator function should then return a -closure, which captures the indicator state. The returned -closure must accept up to three optional arguments, the action -keymap, the targets (plists as returned by `embark--targets') and -the prefix keys typed by the user so far. The keymap, targets -and prefix keys may be updated when cycling targets at point -resulting in multiple calls to the closure. When called from -`embark-become', the indicator closure will be called with the -keymap of commands to become, a fake target list containing a -single target of type `embark-become' and whose value is the -minibuffer input, and the prefix set to nil. Note, in -particular, that if an indicator function wishes to distinguish -between `embark-act' and `embark-become' it should check whether -the `car' of the first target is `embark-become'. - -After the action has been performed the indicator closure is -called without arguments, such that the indicator can perform the -necessary cleanup work. For example, if the indicator adds -overlays, it should remove these overlays. The indicator should -be written in a way that it is safe to call it for cleanup more -than once, in fact, it should be able to handle any sequence of -update and cleanup calls ending in a call for cleanup. - -NOTE: Experience shows that the indicator calling convention may -change again in order to support more action features. The -calling convention should currently be considered unstable. -Please keep this in mind when writing a custom indicator -function, or when using the `which-key' indicator function from -the wiki." - :type '(repeat - (choice - (const :tag "Verbose indicator" embark-verbose-indicator) - (const :tag "Minimal indicator" embark-minimal-indicator) - (const :tag "Mixed indicator" embark-mixed-indicator) - (const :tag "Highlight target" embark-highlight-indicator) - (const :tag "Highlight all occurrences" - embark-isearch-highlight-indicator) - (function :tag "Other")))) - -(defface embark-verbose-indicator-documentation - '((t :inherit completions-annotations)) - "Face used by the verbose action indicator to display binding descriptions. -Used by `embark-verbose-indicator'.") - -(defface embark-verbose-indicator-title '((t :height 1.1 :weight bold)) - "Face used by the verbose action indicator for the title. -Used by `embark-verbose-indicator'.") - -(defface embark-verbose-indicator-shadowed '((t :inherit shadow)) - "Face used by the verbose action indicator for the shadowed targets. -Used by `embark-verbose-indicator'.") - -(defcustom embark-verbose-indicator-display-action - '(display-buffer-reuse-window) - "Parameters added to `display-buffer-alist' to show the actions buffer. -See the docstring of `display-buffer' for information on what -display actions and parameters are available." - :type `(choice - (const :tag "Reuse some window" - (display-buffer-reuse-window)) - (const :tag "Below target buffer" - (display-buffer-below-selected - (window-height . fit-window-to-buffer))) - (const :tag "Bottom of frame (fixed-size)" - (display-buffer-at-bottom)) - (const :tag "Bottom of frame (resizes during cycling)" - (display-buffer-at-bottom - (window-height . fit-window-to-buffer))) - (const :tag "Side window on the right" - (display-buffer-in-side-window (side . right))) - (const :tag "Side window on the left" - (display-buffer-in-side-window (side . left))) - (sexp :tag "Other"))) - -(defcustom embark-verbose-indicator-excluded-actions nil - "Commands not displayed by `embark-verbose-indicator'. -This variable should be set to a list of symbols and regexps. -The verbose indicator will exclude from its listing any commands -matching an element of this list." - :type '(choice - (const :tag "Exclude nothing" nil) - (const :tag "Exclude Embark general actions" - (embark-collect embark-live embark-export - embark-cycle embark-act-all embark-keymap-help - embark-become embark-isearch-forward - embark-isearch-backward)) - (repeat :tag "Other" (choice regexp symbol)))) - -(defcustom embark-verbose-indicator-buffer-sections - `(target "\n" shadowed-targets " " cycle "\n" bindings) - "List of sections to display in the verbose indicator buffer, in order. -You can use either a symbol designating a concrete section (one -of the keywords below, but without the colon), a string literal -or a function returning a string or list of strings to insert and -that accepts the following keyword arguments: - -- `:target', the target as a cons of type and value, -- `:shadowed-targets', a list of conses for the other targets, -- `:bindings' a list returned by `embark--formatted-bindings', and -- `:cycle', a string describing the key binding of `embark-cycle'." - :type '(repeat - (choice (const :tag "Current target name" target) - (const :tag "List of other shadowed targets" shadowed-targets) - (const :tag "Key bindings" bindings) - (const :tag "Cycle indicator" cycle) - (string :tag "Literal string") - (function :tag "Custom function")))) - -(defcustom embark-verbose-indicator-nested t - "Whether the verbose indicator should use nested keymap navigation. -When this variable is non-nil the actions buffer displayed by -`embark-verbose-indicator' will include any prefix keys found in -the keymap it is displaying, and will update to show what is -bound under the prefix if the prefix is pressed. If this -variable is nil, then the actions buffer will contain a flat list -of all full key sequences bound in the keymap." - :type 'boolean) - -(defun embark--verbose-indicator-excluded-p (cmd) - "Return non-nil if CMD should be excluded from the verbose indicator." - (seq-find (lambda (x) - (if (symbolp x) - (eq cmd x) - (string-match-p x (symbol-name cmd)))) - embark-verbose-indicator-excluded-actions)) - -(cl-defun embark--verbose-indicator-section-target - (&key targets bindings &allow-other-keys) - "Format the TARGETS section for the indicator buffer. -BINDINGS is the formatted list of keybindings." - (let ((result (embark--format-targets - (car targets) - nil ; the shadowed targets section deals with these - (cl-find 'embark-done bindings :key #'caddr :test #'eq)))) - (add-face-text-property 0 (length result) - 'embark-verbose-indicator-title - 'append - result) - result)) - -(cl-defun embark--verbose-indicator-section-cycle - (&key cycle shadowed-targets &allow-other-keys) - "Format the CYCLE key section for the indicator buffer. -SHADOWED-TARGETS is the list of other targets." - (concat - (and cycle (propertize (format "(%s to cycle)" cycle) - 'face 'embark-verbose-indicator-shadowed)) - (and shadowed-targets "\n"))) - -(cl-defun embark--verbose-indicator-section-shadowed-targets - (&key shadowed-targets &allow-other-keys) - "Format the SHADOWED-TARGETS section for the indicator buffer." - (when shadowed-targets - (propertize (format "Shadowed targets at point: %s" - (string-join shadowed-targets ", ")) - 'face 'embark-verbose-indicator-shadowed))) - -(cl-defun embark--verbose-indicator-section-bindings - (&key bindings &allow-other-keys) - "Format the BINDINGS section for the indicator buffer." - (let* ((max-width (apply #'max (cons 0 (mapcar (lambda (x) - (string-width (car x))) - bindings)))) - (fmt (format "%%-%ds" (1+ max-width))) - (result nil)) - (dolist (binding bindings (string-join (nreverse result))) - (let ((cmd (caddr binding))) - (unless (embark--verbose-indicator-excluded-p cmd) - (let ((keys (format fmt (car binding))) - (doc (embark--function-doc cmd))) - (push (format "%s%s\n" keys - (propertize - (car (split-string (or doc "") "\n")) - 'face 'embark-verbose-indicator-documentation)) - result))))))) - -(defun embark--verbose-indicator-update (keymap targets) - "Update verbose indicator buffer. -The arguments are the new KEYMAP and TARGETS." - (with-current-buffer (get-buffer-create embark--verbose-indicator-buffer) - (let* ((inhibit-read-only t) - (bindings - (embark--formatted-bindings keymap embark-verbose-indicator-nested)) - (bindings (car bindings)) - (shadowed-targets (mapcar - (lambda (x) (symbol-name (plist-get x :type))) - (cdr targets))) - (cycle (let ((ck (where-is-internal #'embark-cycle keymap))) - (and ck (key-description (car ck)))))) - (setq-local cursor-type nil) - (setq-local truncate-lines t) - (setq-local buffer-read-only t) - (erase-buffer) - (dolist (section embark-verbose-indicator-buffer-sections) - (insert - (if (stringp section) - section - (or (funcall - (let ((prefixed (intern (format - "embark--verbose-indicator-section-%s" - section)))) - (cond - ((fboundp prefixed) prefixed) - ((fboundp section) section) - (t (error "Undefined verbose indicator section `%s'" - section)))) - :targets targets :shadowed-targets shadowed-targets - :bindings bindings :cycle cycle) - "")))) - (goto-char (point-min))))) - -(defun embark-verbose-indicator () - "Indicator that displays a table of key bindings in a buffer. -The default display includes the type and value of the current -target, the list of other target types, and a table of key -bindings, actions and the first line of their docstrings. - -The order and formatting of these items is completely -configurable through the variable -`embark-verbose-indicator-buffer-sections'. - -If the keymap being shown contains prefix keys, the table of key -bindings can either show just the prefixes and update once the -prefix is pressed, or it can contain a flat list of all full key -sequences bound in the keymap. This is controlled by the -variable `embark-verbose-indicator-nested'. - -To reduce clutter in the key binding table, one can set the -variable `embark-verbose-indicator-excluded-actions' to a list -of symbols and regexps matching commands to exclude from the -table. - -To configure how a window is chosen to display this buffer, see -the variable `embark-verbose-indicator-display-action'." - (lambda (&optional keymap targets prefix) - (if (not keymap) - (when-let ((win (get-buffer-window embark--verbose-indicator-buffer - 'visible))) - (quit-window 'kill-buffer win)) - (embark--verbose-indicator-update - (if (and prefix embark-verbose-indicator-nested) - ;; Lookup prefix keymap globally if not found in action keymap - (let ((overriding-terminal-local-map keymap)) - (key-binding prefix 'accept-default)) - keymap) - targets) - (let ((display-buffer-alist - `(,@display-buffer-alist - (,(regexp-quote embark--verbose-indicator-buffer) - ,@embark-verbose-indicator-display-action)))) - (display-buffer embark--verbose-indicator-buffer))))) - -(defcustom embark-mixed-indicator-delay 0.5 - "Time in seconds after which the verbose indicator is shown. -The mixed indicator starts by showing the minimal indicator and -after this delay shows the verbose indicator." - :type '(choice (const :tag "No delay" 0) - (number :tag "Delay in seconds"))) - -(defcustom embark-mixed-indicator-both nil - "Show both indicators, even after the verbose indicator appeared." - :type 'boolean) - -(defun embark-mixed-indicator () - "Mixed indicator showing keymap and targets. -The indicator shows the `embark-minimal-indicator' by default. -After `embark-mixed-indicator-delay' seconds, the -`embark-verbose-indicator' is shown. This which-key-like approach -ensures that Embark stays out of the way for quick actions. The -helpful keybinding reminder still pops up automatically without -further user intervention." - (let ((vindicator (embark-verbose-indicator)) - (mindicator (embark-minimal-indicator)) - vindicator-active - vtimer) - (lambda (&optional keymap targets prefix) - ;; Always cancel the timer. - ;; 1. When updating, cancel timer, since the user has pressed - ;; a key before the timer elapsed. - ;; 2. For cleanup, the timer must also be canceled. - (when vtimer - (cancel-timer vtimer) - (setq vtimer nil)) - (if (not keymap) - (progn - (funcall vindicator) - (when mindicator - (funcall mindicator))) - (when mindicator - (funcall mindicator keymap targets prefix)) - (if vindicator-active - (funcall vindicator keymap targets prefix) - (setq vtimer - (run-at-time - embark-mixed-indicator-delay nil - (lambda () - (when (and (not embark-mixed-indicator-both) mindicator) - (funcall mindicator) - (setq mindicator nil)) - (setq vindicator-active t) - (funcall vindicator keymap targets prefix))))))))) - -;;;###autoload -(defun embark-bindings-in-keymap (keymap) - "Explore command key bindings in KEYMAP with `completing-read'. -The selected command will be executed. Interactively, prompt the -user for a KEYMAP variable." - (interactive - (list - (symbol-value - (intern-soft - (completing-read - "Keymap: " - (embark--with-category - 'variable - (cl-loop for x being the symbols - if (and (boundp x) (keymapp (symbol-value x))) - collect (symbol-name x))) - nil t nil 'variable-name-history - (let ((major-mode-map - (concat (symbol-name major-mode) "-map"))) - (when (intern-soft major-mode-map) major-mode-map))))))) - (when-let (command (embark-completing-read-prompter keymap nil 'no-default)) - (call-interactively command))) - -;;;###autoload -(defun embark-bindings (global) - "Explore current command key bindings with `completing-read'. -The selected command will be executed. - -This shows key bindings from minor mode maps and the local -map (usually set by the major mode), but also less common keymaps -such as those from a text property or overlay, or the overriding -maps: `overriding-terminal-local-map' and `overriding-local-map'. - -Additionally, if GLOBAL is non-nil (interactively, if called with -a prefix argument), this command includes global key bindings." - (interactive "P") - (embark-bindings-in-keymap - (make-composed-keymap - (let ((all-maps (current-active-maps t))) - (if global all-maps (remq global-map all-maps)))))) - -;;;###autoload -(defun embark-bindings-at-point () - "Explore all key bindings at point with `completing-read'. -The selected command will be executed. - -This command lists key bindings found in keymaps specified by the -text properties `keymap' or `local-map', from either buffer text -or an overlay. These are not widely used in Emacs, and when they -are used can be somewhat hard to discover. Examples of locations -that have such a keymap are links and images in `eww' buffers, -attachment links in `gnus' article buffers, and the stash line -in a `vc-dir' buffer." - (interactive) - (if-let ((keymaps (delq nil (list (get-char-property (point) 'keymap) - (get-char-property (point) 'local-map))))) - (embark-bindings-in-keymap (make-composed-keymap keymaps)) - (user-error "No key bindings found at point"))) - -;;;###autoload -(defun embark-prefix-help-command () - "Prompt for and run a command bound in the prefix used for this command. -The prefix described consists of all but the last event of the -key sequence that ran this command. This function is intended to -be used as a value for `prefix-help-command'. - -In addition to using completion to select a command, you can also -type @ and the key binding (without the prefix)." - (interactive) - (when-let ((keys (this-command-keys-vector)) - (prefix (seq-take keys (1- (length keys)))) - (keymap (key-binding prefix 'accept-default))) - (minibuffer-with-setup-hook - (lambda () - (let ((pt (- (minibuffer-prompt-end) 2))) - (overlay-put (make-overlay pt pt) 'before-string - (format " under %s" (key-description prefix))))) - (embark-bindings-in-keymap keymap)))) - -(defun embark--prompt (indicators keymap targets) - "Call the prompter with KEYMAP and INDICATORS. -The TARGETS are displayed for actions outside the minibuffer." - (mapc (lambda (i) (funcall i keymap targets)) indicators) - (condition-case nil - (minibuffer-with-setup-hook - (lambda () - ;; if the prompter opens its own minibuffer, show - ;; the indicator there too - (let ((inner-indicators (mapcar #'funcall embark-indicators))) - (mapc (lambda (i) (funcall i keymap targets)) inner-indicators) - (add-hook 'minibuffer-exit-hook - (lambda () (mapc #'funcall inner-indicators)) - nil t))) - (let ((enable-recursive-minibuffers t)) - (funcall embark-prompter keymap - (lambda (prefix) - (mapc (lambda (i) (funcall i keymap targets prefix)) - indicators))))) - (quit nil))) - -(defvar embark--run-after-command-functions nil - "Abnormal hook, used by `embark--run-after-command'.") - -(defun embark--run-after-command (fn &rest args) - "Call FN with ARGS after the current commands finishes. -If multiple functions are queued with this function during the -same command, they will be called in the order from the one -queued most recently to the one queued least recently." - ;; We don't simply add FN to `post-command-hook' because FN may recursively - ;; call this function. In that case, FN would modify `post-command-hook' - ;; from within post-command-hook, which doesn't behave properly in our case. - ;; We use our own abnormal hook and run it from PCH in a way that it is OK to - ;; modify it from within its own functions. - (unless embark--run-after-command-functions - (let (pch timer has-run) - (setq pch - (lambda () - (remove-hook 'post-command-hook pch) - (cancel-timer timer) - (unless has-run - (setq has-run t) - (while embark--run-after-command-functions - ;; The following funcall may recursively call - ;; `embark--run-after-command', modifying - ;; `embark--run-after-command-functions'. This is why this - ;; loop has to be implemented carefully. We have to pop the - ;; function off the hook before calling it. Using `dolist' - ;; on the hook would also be incorrect, because it wouldn't - ;; take modifications of this hook into account. - (with-demoted-errors "embark PCH: %S" - (condition-case nil - (funcall (pop embark--run-after-command-functions)) - (quit (message "Quit")))))))) - (add-hook 'post-command-hook pch 'append) - ;; Generally we prefer `post-command-hook' because it plays well with - ;; keyboard macros. In some cases, `post-command-hook' isn't run after - ;; exiting a recursive edit, so set up the following timer as a backup. - (setq timer (run-at-time 0 nil pch)))) - - ;; Keep the default-directory alive, since this is often overwritten, - ;; for example by Consult commands. - ;; TODO it might be necessary to add more dynamically bound variables - ;; here. What we actually want are functions `capture-dynamic-scope' - ;; and `eval-in-dynamic-scope', but this does not exist? - (let ((dir default-directory)) - (push (lambda () - (let ((default-directory dir)) - (apply fn args))) - embark--run-after-command-functions))) - -(defun embark--quit-and-run (fn &rest args) - "Quit the minibuffer and then call FN with ARGS. -If called outside the minibuffer, simply apply FN to ARGS." - (if (not (minibufferp)) - (apply fn args) - (apply #'embark--run-after-command fn args) - (embark--run-after-command #'set 'ring-bell-function ring-bell-function) - (setq ring-bell-function #'ignore) - (if (fboundp 'minibuffer-quit-recursive-edit) - (minibuffer-quit-recursive-edit) - (abort-recursive-edit)))) - -(defun embark--run-action-hooks (hooks action target quit) - "Run HOOKS for ACTION. -The HOOKS argument must be alist. The keys t and :always are -treated specially. The :always hooks are executed always and the -t hooks are the default hooks, for when there are no -command-specific hooks for ACTION. The QUIT, ACTION and TARGET -arguments are passed to the hooks as keyword arguments." - (mapc (lambda (h) (apply h :action action :quit quit target)) - (or (alist-get action hooks) - (alist-get t hooks))) - (mapc (lambda (h) (apply h :action action :quit quit target)) - (alist-get :always hooks))) - -(defun embark--run-around-action-hooks - (action target quit &optional non-interactive) - "Run the `embark-around-action-hooks' for ACTION. -All the applicable around hooks are composed in the order they -are present in `embark-around-action-hooks'. The keys t and -:always in `embark-around-action-hooks' are treated specially. -The :always hooks are executed always (outermost) and the t hooks -are the default hooks, for when there are no command-specific -hooks for ACTION. The QUIT, ACTION and TARGET arguments are -passed to the hooks as keyword arguments. - -The optional argument NON-INTERACTIVE controls whether the action -is run with `command-execute' or with `funcall' passing the -target as argument." - (apply - (seq-reduce - (lambda (fn hook) - (lambda (&rest args) (apply hook (plist-put args :run fn)))) - (let ((hooks embark-around-action-hooks)) - (reverse - (append (or (alist-get action hooks) (alist-get t hooks)) - (alist-get :always hooks)))) - (if non-interactive - (lambda (&rest args) - (funcall (plist-get args :action) - (or (plist-get args :candidates) (plist-get args :target)))) - (lambda (&rest args) - (command-execute (plist-get args :action))))) - :action action :quit quit target)) - -(defun embark--act (action target &optional quit) - "Perform ACTION injecting the TARGET. -If called from a minibuffer with non-nil QUIT, quit the -minibuffer before executing the action." - (if (memq action '(embark-become ; these actions should run in - embark-collect ; the current buffer, not the - embark-live ; target buffer - embark-export - embark-select - embark-act-all)) - (progn - (embark--run-action-hooks embark-pre-action-hooks action target quit) - (unwind-protect (embark--run-around-action-hooks action target quit) - (embark--run-action-hooks embark-post-action-hooks - action target quit))) - (let* ((command embark--command) - (prefix prefix-arg) - (action-window (embark--target-window t)) - (directory default-directory) - (inject - (lambda () - (let ((contents (minibuffer-contents))) - (delete-minibuffer-contents) - (insert - (propertize - (substring-no-properties (plist-get target :target)) - 'embark--initial-input contents))) - (if (memq 'ivy--queue-exhibit post-command-hook) - ;; Ivy has special needs: (1) for file names - ;; ivy-immediate-done is not equivalent to - ;; exit-minibuffer, (2) it needs a chance to run - ;; its post command hook first, so use depth 10 - (add-hook 'post-command-hook 'ivy-immediate-done 10 t) - (add-hook 'post-command-hook #'exit-minibuffer nil t)) - (embark--run-action-hooks embark-target-injection-hooks - action target quit))) - (dedicate (and (derived-mode-p 'embark-collect-mode) - (not (window-dedicated-p)) - (selected-window))) - (multi (memq action embark-multitarget-actions)) - (run-action - (if (and (commandp action) (not multi)) - (lambda () - (let (final-window) - (when dedicate (set-window-dedicated-p dedicate t)) - (unwind-protect - (with-selected-window action-window - (let ((enable-recursive-minibuffers t) - (embark--command command) - (prefix-arg prefix) - ;; the next two avoid mouse dialogs - (use-dialog-box nil) - (last-nonmenu-event 13) - (default-directory directory)) - (embark--run-action-hooks embark-pre-action-hooks - action target quit) - (minibuffer-with-setup-hook inject - ;; pacify commands that use (this-command-keys) - (when (= (length (this-command-keys)) 0) - (set--this-command-keys - (if (characterp last-command-event) - (string last-command-event) - "\r"))) - (setq this-command action) - (embark--run-around-action-hooks - action target quit))) - (setq final-window (selected-window))) - (embark--run-action-hooks embark-post-action-hooks - action target quit) - (when dedicate (set-window-dedicated-p dedicate nil))) - (unless (eq final-window action-window) - (select-window final-window)))) - (let ((target - (if (and multi (null (plist-get target :candidates))) - (plist-put - target :candidates (list (plist-get target :target))) - target))) - (lambda () - (with-selected-window action-window - (embark--run-action-hooks embark-pre-action-hooks - action target quit) - (unwind-protect - (let ((current-prefix-arg prefix) - (default-directory directory)) - (embark--run-around-action-hooks - action target quit :non-interactive)) - (embark--run-action-hooks embark-post-action-hooks - action target quit)))))))) - (setq prefix-arg nil) - (if quit (embark--quit-and-run run-action) (funcall run-action))))) - -(defun embark--refine-multi-category (_type target) - "Refine `multi-category' TARGET to its actual type." - (or (get-text-property 0 'multi-category target) - (cons 'general target))) - -(defun embark--simplify-path (_type target) - "Simplify and '//' or '~/' in the TARGET file path." - (cons 'file (substitute-in-file-name target))) - -(defun embark--keybinding-command (_type target) - "Treat an `embark-keybinding' TARGET as a command." - (when-let ((cmd (get-text-property 0 'embark-command target))) - (cons 'command (format "%s" cmd)))) - -(defun embark--lookup-lighter-minor-mode (_type target) - "If TARGET is a lighter, look up its minor mode. - -The `describe-minor-mode' command has as completion candidates -both minor-modes and their lighters. This function replaces the -lighters by their minor modes, so actions expecting a function -work on them." - (cons 'minor-mode - (let ((symbol (intern-soft target))) - (if (and symbol (boundp symbol)) - target - (symbol-name (lookup-minor-mode-from-indicator target)))))) - -(declare-function project-current "project") -(declare-function project-roots "project") -(declare-function project-root "project") - -(defun embark--project-file-full-path (_type target) - "Get full path of project file TARGET." - ;; TODO project-find-file can be called from outside all projects in - ;; which case it prompts for a project first; we don't support that - ;; case yet, since there is no current project. - (cons 'file - (if-let ((project (project-current)) - (root (if (fboundp 'project-root) - (project-root project) - (with-no-warnings - (car (project-roots project)))))) - (expand-file-name target root) - target))) - -(defun embark--remove-package-version (_type target) - "Remove version number from a versioned package TARGET." - (cons 'package (replace-regexp-in-string "-[0-9.]+$" "" target))) - -(defun embark--targets () - "Retrieve current targets. - -An initial guess at the current targets and their types is -determined by running the functions in `embark-target-finders'. -Each function should either return nil, a pair of a type symbol -and target string or a triple of a type symbol, target string and -target bounds. - -In the minibuffer only the first target finder returning non-nil -is taken into account. When finding targets at point in other -buffers, all target finder functions are executed. - -For each target, the type is then looked up as a key in the -variable `embark-transformer-alist'. If there is a transformer -for the type, it is called with the type and target, and must -return a `cons' of the transformed type and transformed target. - -The return value of `embark--targets' is a list of plists. Each -plist concerns one target, and has keys `:type', `:target', -`:orig-type', `:orig-target' and `:bounds'." - (let (targets) - (run-hook-wrapped - 'embark-target-finders - (lambda (fun) - (dolist (found (when-let (result (funcall fun)) - (if (consp (car result)) result (list result)))) - (let* ((type (or (car found) 'general)) - (target+bounds (cdr found)) - (target (if (consp target+bounds) - (car target+bounds) - target+bounds)) - (bounds (and (consp target+bounds) (cdr target+bounds))) - (full-target - (append - (list :orig-type type :orig-target target :bounds bounds) - (if-let (transform (alist-get type embark-transformer-alist)) - (let ((trans (funcall transform type target))) - (list :type (car trans) :target (cdr trans))) - (list :type type :target target))))) - (push full-target targets))) - (and targets (minibufferp)))) - (nreverse - (cl-delete-duplicates ; keeps last duplicate, but we reverse - targets - :test (lambda (t1 t2) - (and (equal (plist-get t1 :target) (plist-get t2 :target)) - (eq (plist-get t1 :type) (plist-get t2 :type)))))))) - -(defun embark--default-action (type) - "Return default action for the given TYPE of target. -The most common case is that the target comes from minibuffer -completion, in which case the default action is the command that -opened the minibuffer in the first place. This can be overridden -by `embark-default-action-overrides'. - -For targets that do not come from minibuffer completion -\(typically some thing at point in a regular buffer) and whose -type is not listed in `embark-default-action-overrides', the -default action is given by whatever binding RET has in the action -keymap for the given type." - (or (alist-get (cons type embark--command) embark-default-action-overrides - nil nil #'equal) - (alist-get type embark-default-action-overrides) - (alist-get t embark-default-action-overrides) - embark--command - (lookup-key (embark--raw-action-keymap type) "\r"))) - -(defun embark--rotate (list k) - "Rotate LIST by K elements and return the rotated list." - (setq k (mod k (length list))) - (append (seq-drop list k) (seq-take list k))) - -(defun embark--orig-target (target) - "Convert TARGET to original target." - (plist-put - (plist-put - (copy-sequence target) - :target (plist-get target :orig-target)) - :type (plist-get target :orig-type))) - -(defun embark--quit-p (action) - "Determine whether to quit the minibuffer after ACTION. -This function consults `embark-quit-after-action' to decide -whether or not the user wishes to quit the minibuffer after -performing the ACTION, assuming this is done from a minibuffer." - (let* ((cfg embark-quit-after-action) - (quit (if (consp cfg) (alist-get action cfg (alist-get t cfg)) cfg))) - (when embark--toggle-quit (setq quit (not quit))) - (setq embark--toggle-quit nil) - quit)) - -;;;###autoload -(defun embark-act (&optional arg) - "Prompt the user for an action and perform it. -The targets of the action are chosen by `embark-target-finders'. -By default, if called from a minibuffer the target is the top -completion candidate. When called from a non-minibuffer buffer -there can multiple targets and you can cycle among them by using -`embark-cycle' (which is bound by default to the same key -binding `embark-act' is, but see `embark-cycle-key'). - -This command uses `embark-prompter' to ask the user to specify an -action, and calls it injecting the target at the first minibuffer -prompt. - -If you call this from the minibuffer, it can optionally quit the -minibuffer. The variable `embark-quit-after-action' controls -whether calling `embark-act' with nil ARG quits the minibuffer, -and if ARG is non-nil it will do the opposite. Interactively, -ARG is the prefix argument. - -If instead you call this from outside the minibuffer, the first -ARG targets are skipped over (if ARG is negative the skipping is -done by cycling backwards) and cycling starts from the following -target." - (interactive "P") - (let* ((targets (or (embark--targets) (user-error "No target found"))) - (indicators (mapcar #'funcall embark-indicators)) - (default-done nil)) - (when arg - (if (minibufferp) - (embark-toggle-quit) - (setq targets (embark--rotate targets (prefix-numeric-value arg))))) - (unwind-protect - (while - (let* ((target (car targets)) - (action - (or (embark--prompt - indicators - (let ((embark-default-action-overrides - (if default-done - `((t . ,default-done)) - embark-default-action-overrides))) - (embark--action-keymap (plist-get target :type) - (cdr targets))) - targets) - (user-error "Canceled"))) - (default-action (or default-done - (embark--default-action - (plist-get target :type))))) - (cond - ;; When acting twice in the minibuffer, do not restart - ;; `embark-act'. Otherwise the next `embark-act' will - ;; find a target in the original buffer. - ((eq action #'embark-act) - (message "Press an action key")) - ((eq action #'embark-cycle) - (setq targets (embark--rotate - targets (prefix-numeric-value prefix-arg)))) - (t - ;; if the action is non-repeatable, cleanup indicator now - (let ((repeat (embark--action-repeatable-p action))) - (unless repeat (mapc #'funcall indicators)) - (condition-case err - (embark--act - action - (if (and (eq action default-action) - (eq action embark--command) - (not (memq action embark-multitarget-actions))) - (embark--orig-target target) - target) - (embark--quit-p action)) - (user-error - (funcall (if repeat #'message #'user-error) - "%s" (cadr err)))) - (when-let (new-targets (and repeat (embark--targets))) - ;; Terminate repeated prompter on default action, - ;; when repeating. Jump to the region type if the - ;; region is active after the action, or else to the - ;; current type again. - (setq default-done #'embark-done - targets - (embark--rotate - new-targets - (or (cl-position-if - (let ((desired-type - (if (eq repeat t) - (plist-get (car targets) :type) - repeat))) - (lambda (x) - (eq (plist-get x :type) desired-type))) - new-targets) - 0))))))))) - (mapc #'funcall indicators)))) - -(defun embark--maybe-transform-candidates () - "Collect candidates and see if they all transform to the same type. -Return a plist with keys `:type', `:orig-type', `:candidates', and -`:orig-candidates'." - (pcase-let* ((`(,type . ,candidates) - (run-hook-with-args-until-success 'embark-candidate-collectors)) - (bounds (mapcar #'cdr-safe candidates))) - (setq candidates - (mapcar (lambda (x) (if (consp x) (car x) x)) candidates)) - (when (eq type 'file) - (let ((dir (embark--default-directory))) - (setq candidates - (mapcar (lambda (cand) - (abbreviate-file-name - (expand-file-name (substitute-in-file-name cand) dir))) - candidates)))) - ;; TODO more systematic approach to applying substitute-in-file-name - (append - (list :orig-type type :orig-candidates candidates :bounds bounds) - (or (when candidates - (when-let ((transformer (alist-get type embark-transformer-alist))) - (pcase-let* ((`(,new-type . ,first-cand) - (funcall transformer type (car candidates)))) - (let ((new-candidates (list first-cand))) - (when (cl-every - (lambda (cand) - (pcase-let ((`(,t-type . ,t-cand) - (funcall transformer type cand))) - (when (eq t-type new-type) - (push t-cand new-candidates) - t))) - (cdr candidates)) - (list :type new-type - :candidates (nreverse new-candidates))))))) - (list :type type :candidates candidates))))) - -;;;###autoload -(defun embark-act-all (&optional arg) - "Prompt the user for an action and perform it on each candidate. -The candidates are chosen by `embark-candidate-collectors'. By -default, if `embark-select' has been used to select some -candidates, then `embark-act-all' will act on those candidates; -otherwise, if the selection is empty and `embark-act-all' is -called from a minibuffer, then the candidates are the completion -candidates. - -This command uses `embark-prompter' to ask the user to specify an -action, and calls it injecting the target at the first minibuffer -prompt. - -If you call this from the minibuffer, it can optionally quit the -minibuffer. The variable `embark-quit-after-action' controls -whether calling `embark-act' with nil ARG quits the minibuffer, -and if ARG is non-nil it will do the opposite. Interactively, -ARG is the prefix argument." - (interactive "P") - (let* ((transformed (embark--maybe-transform-candidates)) - (type (plist-get transformed :type)) - (orig-type (plist-get transformed :orig-type)) - (candidates - (or (cl-mapcar - (lambda (cand orig-cand bounds) - (list :type type :target cand - :bounds (when bounds - (cons (copy-marker (car bounds)) - (copy-marker (cdr bounds)))) - :orig-type orig-type :orig-target orig-cand)) - (plist-get transformed :candidates) - (plist-get transformed :orig-candidates) - (plist-get transformed :bounds)) - (user-error "No candidates to act on"))) - (indicators (mapcar #'funcall embark-indicators))) - (when arg (embark-toggle-quit)) - (unwind-protect - (let* ((action - (or (embark--prompt - indicators (embark--action-keymap type nil) - (list (list :type type :multi (length candidates)))) - (user-error "Canceled"))) - (prefix prefix-arg) - (act (lambda (candidate) - (cl-letf (((symbol-function 'embark--restart) #'ignore) - ((symbol-function 'embark--confirm) #'ignore)) - (let ((prefix-arg prefix)) - (when-let ((bounds (plist-get candidate :bounds))) - (goto-char (car bounds))) - (embark--act action candidate))))) - (quit (embark--quit-p action))) - (when (and (eq action (embark--default-action type)) - (eq action embark--command)) - (setq candidates (mapcar #'embark--orig-target candidates))) - (when (or (not (or embark-confirm-act-all - (memq 'embark--confirm - (alist-get action embark-pre-action-hooks)))) - (y-or-n-p (format "Run %s on %d %ss? " - action (length candidates) type))) - (if (memq action embark-multitarget-actions) - (let ((prefix-arg prefix)) - (embark--act action transformed quit)) - (save-excursion - (if quit - (embark--quit-and-run #'mapc act candidates) - (mapc act candidates)))) - (when (and (not quit) - (memq 'embark--restart - (alist-get action embark-post-action-hooks))) - (embark--restart)))) - (dolist (cand candidates) - (when-let ((bounds (plist-get cand :bounds))) - (set-marker (car bounds) nil) ; yay, manual memory management! - (set-marker (cdr bounds) nil))) - (setq prefix-arg nil) - (mapc #'funcall indicators)))) - -(defun embark-highlight-indicator () - "Action indicator highlighting the target at point." - (let (overlay) - (lambda (&optional keymap targets _prefix) - (let ((bounds (plist-get (car targets) :bounds))) - (when (and overlay (or (not keymap) (not bounds))) - (delete-overlay overlay) - (setq overlay nil)) - (when bounds - (if overlay - (move-overlay overlay (car bounds) (cdr bounds)) - (setq overlay (make-overlay (car bounds) (cdr bounds))) - (overlay-put overlay 'category 'embark-target-overlay)) - (overlay-put overlay 'window (selected-window))))))) - -(defun embark-isearch-highlight-indicator () - "Action indicator highlighting all occurrences of the identifier at point. -This indicator only does something for targets which are -identifiers or symbols. For those it uses `isearch''s lazy -highlighting feature to highlight all occurrences of the target in -the buffer. This indicator is best used in conjunction with -`embark-highlight-indicator': by using them both you get the -target and the other occurrences of it highlighted in different -colors." - (lambda (&optional _keymap targets _prefix) - (if (and (not (minibufferp)) - (memq (plist-get (car targets) :orig-type) '(symbol identifier))) - (let ((isearch-string (plist-get (car targets) :target)) - (isearch-regexp-function #'isearch-symbol-regexp)) - (isearch-lazy-highlight-new-loop)) - (setq isearch-lazy-highlight-last-string nil) - (lazy-highlight-cleanup t)))) - -(defun embark-cycle (_arg) - "Cycle over the next ARG targets at point. -If ARG is negative, cycle backwards." - (interactive "p") - (user-error "Not meant to be called directly")) - -(defun embark-done () - "Terminate sequence of repeated actions." - (interactive)) - -;;;###autoload -(defun embark-dwim (&optional arg) - "Run the default action on the current target. -The target of the action is chosen by `embark-target-finders'. - -If the target comes from minibuffer completion, then the default -action is the command that opened the minibuffer in the first -place, unless overridden by `embark-default-action-overrides'. - -For targets that do not come from minibuffer completion -\(typically some thing at point in a regular buffer) and whose -type is not listed in `embark-default-action-overrides', the -default action is given by whatever binding RET has in the action -keymap for the target's type. - -See `embark-act' for the meaning of the prefix ARG." - (interactive "P") - (if-let ((targets (embark--targets))) - (let* ((target - (or (nth - (if (or (null arg) (minibufferp)) - 0 - (mod (prefix-numeric-value arg) (length targets))) - targets))) - (type (plist-get target :type)) - (default-action (embark--default-action type)) - (action (or (command-remapping default-action) default-action))) - (unless action - (user-error "No default action for %s targets" type)) - (when (and arg (minibufferp)) (setq embark--toggle-quit t)) - (embark--act action - (if (and (eq default-action embark--command) - (not (memq default-action - embark-multitarget-actions))) - (embark--orig-target target) - target) - (embark--quit-p action))) - (user-error "No target found"))) - -(defun embark--become-keymap () - "Return keymap of commands to become for current command." - (let ((map (make-composed-keymap - (cl-loop for keymap-name in embark-become-keymaps - for keymap = (symbol-value keymap-name) - when (where-is-internal embark--command (list keymap)) - collect keymap)))) - (when embark-help-key - (keymap-set map embark-help-key #'embark-keymap-help)) - map)) - -;;;###autoload -(defun embark-become (&optional full) - "Make current command become a different command. -Take the current minibuffer input as initial input for new -command. The new command can be run normally using key bindings or -\\[execute-extended-command], but if the current command is found in a keymap in -`embark-become-keymaps', that keymap is activated to provide -convenient access to the other commands in it. - -If FULL is non-nil (interactively, if called with a prefix -argument), the entire minibuffer contents are used as the initial -input of the new command. By default only the part of the -minibuffer contents between the current completion boundaries is -taken. What this means is fairly technical, but (1) usually -there is no difference: the completion boundaries include the -entire minibuffer contents, and (2) the most common case where -these notions differ is file completion, in which case the -completion boundaries single out the path component containing -point." - (interactive "P") - (unless (minibufferp) - (user-error "Not in a minibuffer")) - (let* ((target (embark--display-string ; remove invisible portions - (if full - (minibuffer-contents) - (pcase-let ((`(,beg . ,end) (embark--boundaries))) - (substring (minibuffer-contents) beg - (+ end (embark--minibuffer-point))))))) - (keymap (embark--become-keymap)) - (targets `((:type embark-become :target ,target))) - (indicators (mapcar #'funcall embark-indicators)) - (become (unwind-protect - (embark--prompt indicators keymap targets) - (mapc #'funcall indicators)))) - (unless become - (user-error "Canceled")) - (embark--become-command become target))) - -(defun embark--become-command (command input) - "Quit current minibuffer and start COMMAND with INPUT." - (embark--quit-and-run - (lambda () - (minibuffer-with-setup-hook - (lambda () - (delete-minibuffer-contents) - (insert input)) - (let ((use-dialog-box nil) ;; avoid mouse dialogs - (last-nonmenu-event 13)) - (setq this-command command) - (command-execute command)))))) - -;;; Embark collect - -(defgroup embark-collect nil - "Buffers for acting on collected Embark targets." - :group 'embark) - -(defcustom embark-candidate-collectors - '(embark-selected-candidates - embark-minibuffer-candidates - embark-completion-list-candidates - embark-dired-candidates - embark-ibuffer-candidates - embark-embark-collect-candidates - embark-custom-candidates) - "List of functions that collect all candidates in a given context. -These are used to fill an Embark Collect buffer. Each function -should return either nil (to indicate it found no candidates) or -a list whose first element is a symbol indicating the type of -candidates and whose `cdr' is the list of candidates, each of -which should be either a string or a dotted list of the -form (TARGET START . END), where START and END are the buffer -positions bounding the TARGET string." - :type 'hook) - -(defcustom embark-exporters-alist - '((buffer . embark-export-ibuffer) - (file . embark-export-dired) - (package . embark-export-list-packages) - (bookmark . embark-export-bookmarks) - (variable . embark-export-customize-variable) - (face . embark-export-customize-face) - (symbol . embark-export-apropos) - (minor-mode . embark-export-apropos) - (function . embark-export-apropos) - (command . embark-export-apropos) - (t . embark-collect)) - "Alist associating completion types to export functions. -Each function should take a list of strings which are candidates -for actions and make a buffer appropriate to manage them. For -example, the default is to make a Dired buffer for files, and an -ibuffer for buffers. - -The key t is also allowed in the alist, and the corresponding -value indicates the default function to use for other types. The -default is `embark-collect'" - :type '(alist :key-type symbol :value-type function)) - -(defcustom embark-after-export-hook nil - "Hook run after `embark-export' in the newly created buffer." - :type 'hook) - -(defface embark-collect-candidate '((t :inherit default)) - "Face for candidates in Embark Collect buffers.") - -(defface embark-collect-group-title - '((t :inherit shadow :slant italic)) - "Face for group titles in Embark Collect buffers.") - -(defface embark-collect-group-separator - '((t :inherit shadow :strike-through t italic)) - "Face for group titles in Embark Collect buffers.") - -(defcustom embark-collect-group-format - (concat - (propertize " " 'face 'embark-collect-group-separator) - (propertize " %s " 'face 'embark-collect-group-title) - (propertize " " 'face 'completions-group-separator - 'display '(space :align-to right))) - "Format string used for the group title in Embark Collect buffers." - :type 'string) - -(defface embark-collect-annotation '((t :inherit completions-annotations)) - "Face for annotations in Embark Collect. -This is only used for annotation that are not already fontified.") - -(defvar-local embark--rerun-function nil - "Function to rerun the collect or export that made the current buffer.") - -(autoload 'package-delete "package") -(declare-function package--from-builtin "package") -(declare-function package-desc-extras "package") -(declare-function package-desc-name "package") -(defvar package--builtins) -(defvar package-alist) -(defvar package-archive-contents) -(defvar package--initialized) - -(defun embark--package-desc (pkg) - "Return the description structure for package PKG." - (or ; found this in `describe-package-1' - (car (alist-get pkg package-alist)) - (if-let ((built-in (assq pkg package--builtins))) - (package--from-builtin built-in) - (car (alist-get pkg package-archive-contents))))) - -(defun embark-minibuffer-candidates () - "Return all current completion candidates from the minibuffer." - (when (minibufferp) - (let* ((all (completion-all-completions - (minibuffer-contents) - minibuffer-completion-table - minibuffer-completion-predicate - (embark--minibuffer-point))) - (last (last all))) - (when last (setcdr last nil)) - (cons - (completion-metadata-get (embark--metadata) 'category) - all)))) - -(defun embark-sorted-minibuffer-candidates () - "Return a sorted list of current minibuffer completion candidates. -This using the same sort order that `icomplete' and -`minibuffer-force-complete' use. The intended usage is that you -replace `embark-minibuffer-candidates' with this function in the -list `embark-candidate-collectors'." - (when (minibufferp) - (cons - (completion-metadata-get (embark--metadata) 'category) - (nconc (cl-copy-list (completion-all-sorted-completions)) nil)))) - -(declare-function dired-get-marked-files "dired") -(declare-function dired-move-to-filename "dired") -(declare-function dired-move-to-end-of-filename "dired") - -(defun embark-dired-candidates () - "Return marked or all files shown in Dired buffer. -If any buffer is marked, return marked buffers; otherwise, return -all buffers." - (when (derived-mode-p 'dired-mode) - (cons 'file - (or - ;; dired-get-marked-files returns the file on the current - ;; line if no marked files are found; and when the fourth - ;; argument is non-nil, the "no marked files" case is - ;; distinguished from the "single marked file" case by - ;; returning (list t marked-file) in the latter - (let ((marked (dired-get-marked-files t nil nil t))) - (and (cdr marked) - (if (eq (car marked) t) (cdr marked) marked))) - (save-excursion - (goto-char (point-min)) - (let (files) - (while (not (eobp)) - (when-let (file (dired-get-filename t t)) - (push `(,file - ,(progn (dired-move-to-filename) (point)) - . ,(progn (dired-move-to-end-of-filename t) (point))) - files)) - (forward-line)) - (nreverse files))))))) - -(autoload 'ibuffer-marked-buffer-names "ibuffer") -(declare-function ibuffer-map-lines-nomodify "ibuffer") - -(defun embark-ibuffer-candidates () - "Return marked or all buffers listed in ibuffer buffer. -If any buffer is marked, return marked buffers; otherwise, return -all buffers." - (when (derived-mode-p 'ibuffer-mode) - (cons 'buffer - (or (ibuffer-marked-buffer-names) - (let (buffers) - (ibuffer-map-lines-nomodify - (lambda (buffer _mark) - (push (buffer-name buffer) buffers))) - (nreverse buffers)))))) - -(defun embark-embark-collect-candidates () - "Return candidates in Embark Collect buffer. -This makes `embark-export' work in Embark Collect buffers." - (when (derived-mode-p 'embark-collect-mode) - (cons embark--type - (save-excursion - (goto-char (point-min)) - (let (all) - (when-let ((cand (embark-target-collect-candidate))) - (push (cdr cand) all)) - (while (forward-button 1 nil nil t) - (when-let ((cand (embark-target-collect-candidate))) - (push (cdr cand) all))) - (nreverse all)))))) - -(defun embark-completion-list-candidates () - "Return all candidates in a completions buffer." - (when (derived-mode-p 'completion-list-mode) - (cons - embark--type - (save-excursion - (goto-char (point-min)) - (next-completion 1) - (let (all) - (while (not (eobp)) - (push (cdr (embark-target-completion-list-candidate)) all) - (next-completion 1)) - (nreverse all)))))) - -(defun embark-custom-candidates () - "Return all variables and faces listed in this `Custom-mode' buffer." - (when (derived-mode-p 'Custom-mode) - (cons 'symbol ; gets refined to variable or face when acted upon - (save-excursion - (goto-char (point-min)) - (let (symbols) - (while (not (eobp)) - (when-let (widget (widget-at (point))) - (when (eq (car widget) 'custom-visibility) - (push - `(,(symbol-name - (plist-get (cdr (plist-get (cdr widget) :parent)) - :value)) - ,(point) - . ,(progn - (re-search-forward ":" (line-end-position) 'noerror) - (point))) - symbols))) - (forward-line)) - (nreverse symbols)))))) - - -(defun embark-collect--target () - "Return the Embark Collect candidate at point. -This takes into account `embark-transformer-alist'." - (let ((embark-target-finders '(embark-target-collect-candidate))) - (car (embark--targets)))) - -(defun embark--action-command (action) - "Turn an ACTION into a command to perform the action. -Returns the name of the command." - (let ((name (intern (format "embark-action--%s" - (embark--command-name action))))) - (fset name (lambda (arg) - (interactive "P") - (when-let (target (embark-collect--target)) - (let ((prefix-arg arg)) - (embark--act action target))))) - (when (fboundp action) - (put name 'function-documentation (documentation action))) - name)) - -(defun embark--all-bindings (keymap &optional nested) - "Return an alist of all bindings in KEYMAP. -If NESTED is non-nil subkeymaps are not flattened." - (let (bindings maps) - (map-keymap - (lambda (key def) - (cond - ((keymapp def) - (if nested - (push (cons (vector key) def) maps) - (dolist (bind (embark--all-bindings def)) - (push (cons (vconcat (vector key) (car bind)) (cdr bind)) - maps)))) - (def (push (cons (vector key) def) bindings)))) - (keymap-canonicalize keymap)) - (nconc (nreverse bindings) (nreverse maps)))) - -(defun embark-collect--direct-action-map (type) - "Return a direct action keymap for targets of given TYPE." - (let* ((actions (embark--action-keymap type nil)) - (map (make-sparse-keymap))) - (set-keymap-parent map button-map) - (pcase-dolist (`(,key . ,cmd) (embark--all-bindings actions)) - (unless (or (equal key [13]) - (memq cmd '(digit-argument negative-argument))) - (define-key map key (if (eq cmd 'embark-keymap-help) - #'embark-bindings-at-point - (embark--action-command cmd))))) - map)) - -(define-minor-mode embark-collect-direct-action-minor-mode - "Bind type-specific actions directly (without need for `embark-act')." - :init-value nil - :lighter " Act" - (unless (derived-mode-p 'embark-collect-mode) - (user-error "Not in an Embark Collect buffer")) - (save-excursion - (goto-char (point-min)) - (let ((inhibit-read-only t) maps) - (while (progn - (when (tabulated-list-get-id) - (put-text-property - (point) (button-end (point)) 'keymap - (if embark-collect-direct-action-minor-mode - (when-let ((target (embark-collect--target)) - (type (plist-get target :type))) - (or (alist-get type maps) - (setf (alist-get type maps) - (embark-collect--direct-action-map type))))))) - (forward-button 1 nil nil t)))))) - -(define-button-type 'embark-collect-entry - 'face 'embark-collect-candidate - 'action 'embark-collect-choose) - -(declare-function outline-toggle-children "outline") -(define-button-type 'embark-collect-group - 'face 'embark-collect-group-title - 'action (lambda (_) (outline-toggle-children))) - -(defun embark--boundaries () - "Get current minibuffer completion boundaries." - (let ((contents (minibuffer-contents)) - (pt (embark--minibuffer-point))) - (completion-boundaries - (substring contents 0 pt) - minibuffer-completion-table - minibuffer-completion-predicate - (substring contents pt)))) - -(defun embark-collect-choose (entry) - "Run default action on Embark Collect ENTRY." - (pcase-let ((`(,type ,text ,start . ,end) - (save-excursion - (goto-char entry) - (embark-target-collect-candidate)))) - (embark--act (embark--default-action type) - (list :target text - :type type - :bounds (cons start end))))) - -(defvar-keymap embark-collect-mode-map - :doc "Keymap for Embark collect mode." - :parent tabulated-list-mode-map - "a" #'embark-act - "A" #'embark-act-all - "M-a" #'embark-collect-direct-action-minor-mode - "E" #'embark-export - "s" #'isearch-forward - "n" #'forward-button - "p" #'backward-button - "}" 'outline-next-heading - "{" 'outline-previous-heading - " " 'outline-next-heading - " " 'outline-previous-heading - " " #'embark-rerun-collect-or-export) - -(defconst embark-collect--outline-string (string #x210000) - "Special string used for outline headings in Embark Collect buffers. -Chosen to be extremely unlikely to appear in a candidate.") - -(define-derived-mode embark-collect-mode tabulated-list-mode "Embark Collect" - "List of candidates to be acted on. -The command `embark-act' is bound `embark-collect-mode-map', but -you might prefer to change the key binding to match your other -key binding for it. Or alternatively you might want to enable the -embark collect direct action minor mode by adding the function -`embark-collect-direct-action-minor-mode' to -`embark-collect-mode-hook'. - -Reverting an Embark Collect buffer has slightly unusual behavior -if the buffer was obtained by running `embark-collect' from -within a minibuffer completion session. In that case reverting -just restarts the completion session, that is, the command that -opened the minibuffer is run again and the minibuffer contents -restored. You can then interact normally with the command, -perhaps editing the minibuffer contents, and, if you wish, you -can rerun `embark-collect' to get an updated buffer." - :interactive nil :abbrev-table nil :syntax-table nil) - -(defun embark-collect--metadatum (type metadatum) - "Get METADATUM for current buffer's candidates. -For non-minibuffers, assume candidates are of given TYPE." - (if (minibufferp) - (or (completion-metadata-get (embark--metadata) metadatum) - (plist-get completion-extra-properties - (intern (format ":%s" metadatum)))) - ;; otherwise fake some metadata for Marginalia users's benefit - (completion-metadata-get `((category . ,type)) metadatum))) - -(defun embark-collect--affixator (type) - "Get affixation function for current buffer's candidates. -For non-minibuffers, assume candidates are of given TYPE." - (or (embark-collect--metadatum type 'affixation-function) - (let ((annotator - (or (embark-collect--metadatum type 'annotation-function) - (lambda (_) "")))) - (lambda (candidates) - (mapcar (lambda (c) - (if-let (a (funcall annotator c)) (list c "" a) c)) - candidates))))) - -(defun embark--display-string (str) - ;; Note: Keep in sync with vertico--display-string - "Return display STR without display and invisible properties." - (let ((end (length str)) (pos 0) chunks) - (while (< pos end) - (let ((nextd (next-single-property-change pos 'display str end)) - (disp (get-text-property pos 'display str))) - (if (stringp disp) - (let ((face (get-text-property pos 'face str))) - (when face - (add-face-text-property - 0 (length disp) face t (setq disp (concat disp)))) - (setq pos nextd chunks (cons disp chunks))) - (while (< pos nextd) - (let ((nexti - (next-single-property-change pos 'invisible str nextd))) - (unless (or (get-text-property pos 'invisible str) - (and (= pos 0) (= nexti end))) ;; full=>no allocation - (push (substring str pos nexti) chunks)) - (setq pos nexti)))))) - (if chunks (apply #'concat (nreverse chunks)) str))) - -(defconst embark--hline - (propertize - (concat "\n" (propertize - " " 'display '(space :align-to right) - 'face '(:inherit completions-group-separator :height 0.01) - 'cursor-intangible t 'intangible t))) - "Horizontal line used to separate multiline collect entries.") - -(defun embark-collect--format-entries (candidates grouper) - "Format CANDIDATES for `tabulated-list-mode' grouped by GROUPER. -The GROUPER is either nil or a function like the `group-function' -completion metadatum, that is, a function of two arguments, the -first of which is a candidate and the second controls what is -computed: if nil, the title of the group the candidate belongs -to, and if non-nil, a rewriting of the candidate (useful to -simplify the candidate so it doesn't repeat the group title, for -example)." - (let ((max-width 0) - (transform - (if grouper (lambda (cand) (funcall grouper cand t)) #'identity))) - (setq - tabulated-list-entries - (mapcan - (lambda (group) - (let ((multiline (seq-some (lambda (x) (string-match-p "\n" (car x))) - candidates))) - (cons - `(nil [(,(concat (propertize embark-collect--outline-string - 'invisible t) - (format embark-collect-group-format (car group))) - type embark-collect-group) - ("" skip t)]) - (mapcar - (pcase-lambda (`(,cand ,prefix ,annotation)) - (let* ((display (embark--display-string (funcall transform cand))) - (length (length annotation)) - (faces (text-property-not-all - 0 length 'face nil annotation))) - (setq max-width (max max-width (+ (string-width prefix) - (string-width display)))) - (when faces - (add-face-text-property 0 length 'default t annotation)) - `(,cand - [(,(propertize - (if multiline (concat display embark--hline) display) - 'line-prefix prefix) - type embark-collect-entry) - (,annotation - skip t - ,@(unless faces - '(face embark-collect-annotation)))]))) - (cdr group))))) - (if grouper - (seq-group-by (lambda (item) (funcall grouper (car item) nil)) - candidates) - (list (cons "" candidates))))) - (if (null grouper) - (pop tabulated-list-entries) - (setq-local outline-regexp embark-collect--outline-string) - (outline-minor-mode)) - (setq tabulated-list-format - `[("Candidate" ,max-width t) ("Annotation" 0 t)]))) - -(defun embark-collect--update-candidates (buffer) - "Update candidates for Embark Collect BUFFER." - (let* ((transformed (embark--maybe-transform-candidates)) - (type (plist-get transformed :orig-type)) ; we need the originals for - (candidates (plist-get transformed :orig-candidates)) ; default action - (bounds (plist-get transformed :bounds)) - (affixator (embark-collect--affixator type)) - (grouper (embark-collect--metadatum type 'group-function))) - (when (eq type 'file) - (let ((dir (buffer-local-value 'default-directory buffer))) - (setq candidates - (mapcar (lambda (cand) - (let ((rel (file-relative-name cand dir))) - (if (string-prefix-p "../" rel) cand rel))) - candidates)))) - (if (seq-some #'identity bounds) - (cl-loop for cand in candidates and (start . _end) in bounds - when start - do (add-text-properties - 0 1 `(embark--location ,(copy-marker start)) cand))) - (setq candidates (funcall affixator candidates)) - (with-current-buffer buffer - (setq embark--type type) - (unless embark--command - (setq embark--command #'embark--goto)) - (embark-collect--format-entries candidates grouper)) - candidates)) - -(defun embark--goto (target) - "Jump to the original location of TARGET. -This function is used as a default action in Embark Collect -buffers when the candidates were a selection from a regular -buffer." - ;; TODO: ensure the location jumped to is visible - ;; TODO: remove duplication with embark-org-goto-heading - (when-let ((marker (get-text-property 0 'embark--location target))) - (pop-to-buffer (marker-buffer marker)) - (widen) - (goto-char marker) - (pulse-momentary-highlight-one-line))) - -(defun embark--collect (buffer-name) - "Create an Embark Collect buffer named BUFFER-NAME. - -The function `generate-new-buffer-name' is used to ensure the -buffer has a unique name." - (let ((buffer (generate-new-buffer buffer-name)) - (rerun (embark--rerun-function #'embark-collect))) - (with-current-buffer buffer - ;; we'll run the mode hooks once the buffer is displayed, so - ;; the hooks can make use of the window - (delay-mode-hooks (embark-collect-mode))) - - (embark--cache-info buffer) - (unless (embark-collect--update-candidates buffer) - (user-error "No candidates to collect")) - - (with-current-buffer buffer - (setq tabulated-list-use-header-line nil ; default to no header - header-line-format nil - tabulated-list--header-string nil) - (setq embark--rerun-function rerun)) - - (let ((window (display-buffer buffer))) - (with-selected-window window - (run-mode-hooks) - (tabulated-list-revert)) - (set-window-dedicated-p window t) - buffer))) - -(defun embark--descriptive-buffer-name (type) - "Return a descriptive name for an Embark collect or export buffer. -TYPE should be either `collect' or `export'." - (format "*Embark %s: %s*" - (capitalize (symbol-name type)) - (if (minibufferp) - (format "%s - %s" embark--command - (minibuffer-contents-no-properties)) - (buffer-name)))) - -;;;###autoload -(defun embark-collect () - "Create an Embark Collect buffer. - -To control the display, add an entry to `display-buffer-alist' -with key \"Embark Collect\". - -In Embark Collect buffers `revert-buffer' is remapped to -`embark-rerun-collect-or-export', which has slightly unusual -behavior if the buffer was obtained by running `embark-collect' -from within a minibuffer completion session. In that case -rerunning just restarts the completion session, that is, the -command that opened the minibuffer is run again and the -minibuffer contents restored. You can then interact normally with -the command, perhaps editing the minibuffer contents, and, if you -wish, you can rerun `embark-collect' to get an updated buffer." - (interactive) - (let ((buffer (embark--collect (embark--descriptive-buffer-name 'collect)))) - (when (minibufferp) - (embark--run-after-command #'pop-to-buffer buffer) - (embark--quit-and-run #'message nil)))) - -;;;###autoload -(defun embark-live () - "Create a live-updating Embark Collect buffer. - -To control the display, add an entry to `display-buffer-alist' -with key \"Embark Live\"." - (interactive) - (let ((live-buffer (embark--collect - (format "*Embark Live: %s*" - (if (minibufferp) - (format "M-x %s" embark--command) - (buffer-name))))) - (run-collect (make-symbol "run-collect")) - (stop-collect (make-symbol "stop-collect")) - timer) - (setf (symbol-function stop-collect) - (lambda () - (remove-hook 'change-major-mode-hook stop-collect t) - (remove-hook 'after-change-functions run-collect t))) - (setf (symbol-function run-collect) - (lambda (_1 _2 _3) - (unless timer - (setq timer - (run-with-idle-timer - 0.05 nil - (lambda () - (if (not (buffer-live-p live-buffer)) - (funcall stop-collect) - (embark-collect--update-candidates live-buffer) - (with-current-buffer live-buffer - ;; TODO figure out why I can't restore point - (tabulated-list-print t t)) - (setq timer nil)))))))) - (add-hook 'after-change-functions run-collect nil t) - (when (minibufferp) - (add-hook 'change-major-mode-hook stop-collect nil t)))) - -(defun embark--rerun-function (kind) - "Return a rerun function for an export or collect buffer in this context. -The parameter KIND should be either `embark-export' or `embark-collect'." - (let ((buffer (or embark--target-buffer (embark--target-buffer))) - (command embark--command)) - (cl-flet ((rerunner (action) - (lambda (&rest _) - (quit-window 'kill-buffer) - (with-current-buffer - (if (buffer-live-p buffer) buffer (current-buffer)) - (let ((embark--command command)) - (funcall action)))))) - (if (minibufferp) - (rerunner - (let ((input (minibuffer-contents-no-properties))) - (lambda () - (minibuffer-with-setup-hook - (lambda () - (delete-minibuffer-contents) - (insert input)) - (setq this-command embark--command) - (command-execute embark--command))))) - (rerunner kind))))) - -(defun embark-rerun-collect-or-export () - "Rerun the `embark-collect' or `embark-export' that created this buffer." - (interactive) - (if embark--rerun-function - (funcall embark--rerun-function) - (user-error "No function to rerun collect or export found"))) - -;;;###autoload -(defun embark-export () - "Create a type-specific buffer to manage current candidates. -The variable `embark-exporters-alist' controls how to make the -buffer for each type of completion. - -In Embark Export buffers `revert-buffer' is remapped to -`embark-rerun-collect-or-export', which has slightly unusual -behavior if the buffer was obtained by running `embark-export' -from within a minibuffer completion session. In that case -reverting just restarts the completion session, that is, the -command that opened the minibuffer is run again and the -minibuffer contents restored. You can then interact normally -with the command, perhaps editing the minibuffer contents, and, -if you wish, you can rerun `embark-export' to get an updated -buffer." - (interactive) - (let* ((transformed (embark--maybe-transform-candidates)) - (candidates (or (plist-get transformed :candidates) - (user-error "No candidates for export"))) - (type (plist-get transformed :type))) - (let ((exporter (or (alist-get type embark-exporters-alist) - (alist-get t embark-exporters-alist)))) - (if (eq exporter 'embark-collect) - (embark-collect) - (let* ((after embark-after-export-hook) - (cmd embark--command) - (name (embark--descriptive-buffer-name 'export)) - (rerun (embark--rerun-function #'embark-export)) - (buffer (save-excursion - (funcall exporter candidates) - (rename-buffer name t) - (current-buffer)))) - (embark--quit-and-run - (lambda () - (pop-to-buffer buffer) - (setq embark--rerun-function rerun) - (use-local-map - (make-composed-keymap - '(keymap - (remap keymap - (revert-buffer . embark-rerun-collect-or-export))) - (current-local-map))) - (let ((embark-after-export-hook after) - (embark--command cmd)) - (run-hooks 'embark-after-export-hook))))))))) - -(defmacro embark--export-rename (buffer title &rest body) - "Run BODY and rename BUFFER to Embark export buffer with TITLE." - (declare (indent 2)) - (let ((saved (make-symbol "saved"))) - `(let ((,saved (embark-rename-buffer - ,buffer " *Embark Saved*" t))) - ,@body - (set-buffer (embark-rename-buffer - ,buffer ,(format "*Embark Export %s*" title) t)) - (when ,saved (embark-rename-buffer ,saved ,buffer))))) - -(defun embark--export-customize (items type pred) - "Create a customization buffer listing ITEMS. -TYPE is the items type. -PRED is a predicate function used to filter the items." - (custom-buffer-create - (cl-loop for item in items - for sym = (intern-soft item) - when (and sym (funcall pred sym)) collect `(,sym ,type)))) - -(autoload 'apropos-parse-pattern "apropos") -(autoload 'apropos-symbols-internal "apropos") -(defun embark-export-apropos (symbols) - "Create apropos buffer listing SYMBOLS." - (embark--export-rename "*Apropos*" "Apropos" - (apropos-parse-pattern "") ;; Initialize apropos pattern - ;; HACK: Ensure that order of exported symbols is kept. - (cl-letf (((symbol-function #'sort) (lambda (list _pred) (nreverse list)))) - (apropos-symbols-internal - (delq nil (mapcar #'intern-soft symbols)) - (bound-and-true-p apropos-do-all))))) - -(defun embark-export-customize-face (faces) - "Create a customization buffer listing FACES." - (embark--export-customize faces 'custom-face #'facep)) - -(defun embark-export-customize-variable (variables) - "Create a customization buffer listing VARIABLES." - ;; The widget library serializes/deserializes the values. We advise - ;; the serialization in order to avoid errors for nonserializable - ;; variables. - (cl-letf* ((ht (make-hash-table :test #'equal)) - (orig-read (symbol-function #'read)) - (orig-write (symbol-function 'widget-sexp-value-to-internal)) - ((symbol-function #'read) - (lambda (&optional str) - (condition-case nil - (funcall orig-read str) - (error (gethash str ht))))) - ((symbol-function 'widget-sexp-value-to-internal) - (lambda (widget val) - (let ((str (funcall orig-write widget val))) - (puthash str val ht) - str)))) - (embark--export-customize variables 'custom-variable #'boundp))) - -(defun embark-export-ibuffer (buffers) - "Create an ibuffer buffer listing BUFFERS." - (ibuffer t "*Embark Export Ibuffer*" - `((predicate . (member (buffer-name) ',buffers))))) - -(autoload 'dired-check-switches "dired") -(declare-function dired-unadvertise "dired") -(defvar dired-directory) - -(defun embark-export-dired (files) - "Create a Dired buffer listing FILES." - (setq files (mapcar #'directory-file-name - (cl-remove-if-not #'file-exists-p files))) - (when (dired-check-switches dired-listing-switches "A" "almost-all") - (setq files (cl-remove-if - (lambda (path) - (let ((file (file-name-nondirectory path))) - (or (string= file ".") (string= file "..")))) - files))) - (cl-letf* ((dir (or (file-name-directory (try-completion "" files)) "")) - ;; Prevent reusing existing Dired buffer. - ((symbol-function 'dired-find-buffer-nocreate) #'ignore) - (buf (dired-noselect - (cons (expand-file-name dir) - (mapcar (lambda (file) (string-remove-prefix dir file)) - files))))) - (with-current-buffer buf - ;; Unadvertise to prevent the new buffer from being reused. - (dired-unadvertise (car dired-directory)) - (rename-buffer (format "*Embark Export Dired %s*" default-directory))) - (pop-to-buffer buf))) - -(autoload 'package-menu-mode "package") -(autoload 'package-menu--generate "package") - -(defun embark-export-list-packages (packages) - "Create a package menu mode buffer listing PACKAGES." - (let ((buf (generate-new-buffer "*Embark Export Packages*"))) - (with-current-buffer buf - (package-menu-mode) - (package-menu--generate nil (mapcar #'intern packages))) - (pop-to-buffer buf))) - -(defvar bookmark-alist) - -(defun embark-export-bookmarks (bookmarks) - "Create a `bookmark-bmenu-mode' buffer listing BOOKMARKS." - (embark--export-rename "*Bookmark List*" "Bookmarks" - (let ((bookmark-alist - (cl-remove-if-not - (lambda (bmark) - (member (car bmark) bookmarks)) - bookmark-alist))) - (bookmark-bmenu-list)))) - -;;; Multiple target selection - -(defface embark-selected '((t (:inherit match))) - "Face for selected candidates.") - -(defcustom embark-selection-indicator - #(" Embark:%s " 1 12 (face (embark-selected bold))) - "Mode line indicator used for selected candidates." - :type '(choice string (const nil))) - -(defvar-local embark--selection nil - "Buffer local list of selected targets. -Add or remove elements to this list using the `embark-select' -action.") - -(defun embark--selection-indicator () - "Mode line indicator showing number of selected items." - (when-let ((sel - (buffer-local-value - 'embark--selection - (or (when-let ((win (active-minibuffer-window))) - (window-buffer win)) - (current-buffer))))) - (format embark-selection-indicator (length sel)))) - -(cl-defun embark--select - (&key orig-target orig-type bounds &allow-other-keys) - "Add or remove ORIG-TARGET of given ORIG-TYPE to the selection. -If BOUNDS are given, also highlight the target when selecting it." - (cl-flet ((multi-type (x) (car (get-text-property 0 'multi-category x)))) - (if-let* ((existing (seq-find - (pcase-lambda (`(,cand . ,ov)) - (and - (equal cand orig-target) - (if (and bounds ov) - (and (= (car bounds) (overlay-start ov)) - (= (cdr bounds) (overlay-end ov))) - (let ((cand-type (multi-type cand))) - (or (eq cand-type orig-type) - (eq cand-type (multi-type orig-target))))))) - embark--selection))) - (progn - (when (cdr existing) (delete-overlay (cdr existing))) - (setq embark--selection (delq existing embark--selection))) - (let ((target (copy-sequence orig-target)) overlay) - (when bounds - (setq overlay (make-overlay (car bounds) (cdr bounds))) - (overlay-put overlay 'category 'embark-selected-overlay)) - (add-text-properties 0 (length orig-target) - `(multi-category ,(cons orig-type orig-target)) - target) - (push (cons target overlay) embark--selection)))) - (when embark-selection-indicator - (add-to-list 'mode-line-misc-info '(:eval (embark--selection-indicator))) - (force-mode-line-update t))) - -;;;###autoload -(defun embark-select () - "Add or remove the target from the current buffer's selection. -You can act on all selected targets at once with `embark-act-all'. -When called from outside `embark-act' this command will select -the first target at point." - (interactive) - (if-let ((target (car (embark--targets)))) - (apply #'embark--select target) - (user-error "No target to select"))) - -(defun embark-selected-candidates () - "Return currently selected candidates in the buffer." - (when embark--selection - (cl-flet ((unwrap (x) (get-text-property 0 'multi-category x))) - (let* ((first-type (car (unwrap (caar embark--selection)))) - (same (cl-every (lambda (item) - (eq (car (unwrap (car item))) first-type)) - embark--selection)) - (extract (if same - (pcase-lambda (`(,cand . ,overlay)) - (cons (cdr (unwrap cand)) overlay)) - #'identity))) - (cons - (if same first-type 'multi-category) - (nreverse - (mapcar - (lambda (item) - (pcase-let ((`(,cand . ,ov) (funcall extract item))) - (if ov `(,cand ,(overlay-start ov) . ,(overlay-end ov)) cand))) - embark--selection))))))) - -;;; Integration with external packages, mostly completion UIs - -;; marginalia - -;; Ensure that the Marginalia cache is reset, such that -;; `embark-toggle-variable-value' updates the display (See #540). -(with-eval-after-load 'marginalia - (push 'marginalia--cache-reset (alist-get :always embark-post-action-hooks))) - -;; vertico - -(declare-function vertico--candidate "ext:vertico") -(declare-function vertico--update "ext:vertico") -(defvar vertico--input) -(defvar vertico--candidates) -(defvar vertico--base) - -(defun embark--vertico-selected () - "Target the currently selected item in Vertico. -Return the category metadatum as the type of the target." - (when vertico--input - ;; Force candidate computation, if candidates are not yet available. - (vertico--update) - (cons (completion-metadata-get (embark--metadata) 'category) - (vertico--candidate)))) - -(defun embark--vertico-candidates () - "Collect the current Vertico candidates. -Return the category metadatum as the type of the candidates." - (when vertico--input - ;; Force candidate computation, if candidates are not yet available. - (vertico--update) - (cons (completion-metadata-get (embark--metadata) 'category) - vertico--candidates))) - -(defun embark--vertico-indicator () - "Embark indicator highlighting the current Vertico candidate." - (let ((fr face-remapping-alist)) - (lambda (&optional keymap _targets _prefix) - (when vertico--input - (setq-local face-remapping-alist - (if keymap - (cons '(vertico-current . embark-target) fr) - fr)))))) - -(with-eval-after-load 'vertico - (cl-defmethod vertico--format-candidate - :around (cand prefix suffix index start &context (embark--selection cons)) - (when (cl-find (concat vertico--base (nth index vertico--candidates)) - embark--selection - :test #'equal :key #'car) - (setq cand (copy-sequence cand)) - (add-face-text-property 0 (length cand) 'embark-selected t cand)) - (cl-call-next-method cand prefix suffix index start)) - (add-hook 'embark-indicators #'embark--vertico-indicator) - (add-hook 'embark-target-finders #'embark--vertico-selected) - (add-hook 'embark-candidate-collectors #'embark--vertico-candidates) - (remove-hook 'embark-candidate-collectors #'embark-selected-candidates) - (add-hook 'embark-candidate-collectors #'embark-selected-candidates)) - -;; ivy - -(declare-function ivy--expand-file-name "ext:ivy") -(declare-function ivy-state-current "ext:ivy") -(defvar ivy-text) -(defvar ivy-last) -(defvar ivy--old-cands) ; this stores the current candidates :) -(defvar ivy--length) - -(defun embark--ivy-selected () - "Target the currently selected item in Ivy. -Return the category metadatum as the type of the target." - ;; my favorite way of detecting Ivy - (when (memq 'ivy--queue-exhibit post-command-hook) - (cons - (completion-metadata-get (embark--metadata) 'category) - (ivy--expand-file-name - (if (and (> ivy--length 0) - (stringp (ivy-state-current ivy-last))) - (ivy-state-current ivy-last) - ivy-text))))) - -(defun embark--ivy-candidates () - "Return all current Ivy candidates." - ;; my favorite way of detecting Ivy - (when (memq 'ivy--queue-exhibit post-command-hook) - (cons - ;; swiper-isearch uses swiper-isearch-function as a completion - ;; table, but it doesn't understand metadata queries - (ignore-errors - (completion-metadata-get (embark--metadata) 'category)) - ivy--old-cands))) - -(with-eval-after-load 'ivy - (add-hook 'embark-target-finders #'embark--ivy-selected) - (add-hook 'embark-candidate-collectors #'embark--ivy-candidates) - (remove-hook 'embark-candidate-collectors #'embark-selected-candidates) - (add-hook 'embark-candidate-collectors #'embark-selected-candidates)) - -;;; Custom actions - -(defvar embark-separator-history nil - "Input history for the separators used by some embark commands. -The commands that prompt for a string separator are -`embark-insert' and `embark-copy-as-kill'.") - -(defun embark-keymap-help () - "Prompt for an action to perform or command to become and run it." - (interactive) - (user-error "Not meant to be called directly")) - -(defun embark-toggle-quit () - "Toggle whether the following action quits the minibuffer." - (interactive) - (when (minibufferp) - (setq embark--toggle-quit (not embark--toggle-quit)) - (if (consp embark-quit-after-action) - (message "Will %sobey embark-quit-after-action." - (if embark--toggle-quit "dis" "")) - (message - "Will %squit minibuffer after action" - (if (eq embark--toggle-quit embark-quit-after-action) "not " ""))))) - -(defun embark--separator (strings) - "Return a separator to join the STRINGS together. -With a prefix argument, prompt the user (unless STRINGS has 0 or -1 elements, in which case a separator is not needed)." - (if (and current-prefix-arg (cdr strings)) - (read-string "Separator: " nil 'embark-separator-history) - "\n")) - -(defun embark-copy-as-kill (strings) - "Join STRINGS and save on the `kill-ring'. -With a prefix argument, prompt for the separator to join the -STRINGS, which defaults to a newline." - (kill-new (string-join strings (embark--separator strings)))) - -(defun embark-insert (strings) - "Join STRINGS and insert the result at point. -With a prefix argument, prompt for the separator to join the -STRINGS, which defaults to a newline. - -Some whitespace is also inserted if necessary to avoid having the -inserted string blend into the existing buffer text. More -precisely: - -1. If the inserted string does not contain newlines, a space may -be added before or after it as needed to avoid inserting a word -constituent character next to an existing word constituent. - -2. For a multiline inserted string, newlines may be added before -or after as needed to ensure the inserted string is on lines of -its own." - (let* ((separator (embark--separator strings)) - (multiline - (or (and (cdr strings) (string-match-p "\n" separator)) - (and (null (cdr strings)) - (equal (buffer-substring (line-beginning-position) - (line-end-position)) - (car strings))) - (seq-some (lambda (s) (string-match-p "\n" s)) strings)))) - (cl-labels ((maybe-space () - (and (looking-at "\\w") (looking-back "\\w" 1) - (insert " "))) - (maybe-newline () - (or (looking-back "^[ \t]*" 40) (looking-at "\n") - (newline-and-indent))) - (maybe-whitespace () - (if multiline (maybe-newline) (maybe-space))) - (ins-string () - (let ((start (point))) - (insert (string-join strings separator)) - (save-excursion (goto-char start) (maybe-whitespace)) - (when (looking-back "\n" 1) (delete-char -1)) - (save-excursion (maybe-whitespace))))) - (if buffer-read-only - (with-selected-window (other-window-for-scrolling) - (ins-string)) - (ins-string))))) - -;; For Emacs 28 dired-jump will be moved to dired.el, but it seems -;; that since it already has an autoload in Emacs 28, this next -;; autoload is ignored. -(autoload 'dired-jump "dired-x" nil t) - -(defun embark-dired-jump (file &optional other-window) - "Open Dired buffer in directory containing FILE and move to its line. -When called with a prefix argument OTHER-WINDOW, open Dired in other window." - (interactive "fJump to Dired file: \nP") - (dired-jump other-window file)) - -(defun embark--read-from-history (prompt candidates &optional category) - "Read with completion from list of history CANDIDATES of CATEGORY. -Sorting and history are disabled. PROMPT is the prompt message." - (completing-read prompt - (embark--with-category category candidates) - nil t nil t)) - -(defun embark-kill-ring-remove (text) - "Remove TEXT from `kill-ring'." - (interactive (list (embark--read-from-history - "Remove from kill-ring: " kill-ring 'kill-ring))) - (embark-history-remove text) - (setq kill-ring (delete text kill-ring))) - -(defvar recentf-list) -(defun embark-recentf-remove (file) - "Remove FILE from the list of recent files." - (interactive (list (embark--read-from-history - "Remove recent file: " recentf-list 'file))) - (embark-history-remove (expand-file-name file)) - (embark-history-remove (abbreviate-file-name file)) - (when (and (boundp 'recentf-list) (fboundp 'recentf-expand-file-name)) - (setq recentf-list (delete (recentf-expand-file-name file) recentf-list)))) - -(defun embark-history-remove (str) - "Remove STR from `minibuffer-history-variable'. -Many completion UIs sort by history position. This command can be used -to remove entries from the history, such that they are not sorted closer -to the top." - (interactive (list (embark--read-from-history - "Remove history item: " - (if (eq minibuffer-history-variable t) - (user-error "No minibuffer history") - (symbol-value minibuffer-history-variable))))) - (unless (eq minibuffer-history-variable t) - (set minibuffer-history-variable - (delete str (symbol-value minibuffer-history-variable))))) - -(defvar xref-backend-functions) - -(defun embark-find-definition (symbol) - "Find definition of Emacs Lisp SYMBOL." - (interactive "sSymbol: ") - (let ((xref-backend-functions (lambda () 'elisp))) - (xref-find-definitions symbol))) - -(defun embark-info-lookup-symbol (symbol) - "Display the definition of SYMBOL, from the Elisp manual." - (interactive "SSymbol: ") - (info-lookup-symbol symbol 'emacs-lisp-mode)) - -(defun embark-rename-buffer (buffer newname &optional unique) - "Rename BUFFER to NEWNAME, optionally making it UNIQUE. -Interactively, you can set UNIQUE with a prefix argument. -Returns the new name actually used." - (interactive "bBuffer: \nBRename %s to: \nP") - (when-let ((buf (get-buffer buffer))) - (with-current-buffer buf - (rename-buffer newname unique)))) - -(defun embark--package-url (pkg) - "Return homepage for package PKG." - (when-let (desc (embark--package-desc pkg)) - (alist-get :url (package-desc-extras desc)))) - -(defun embark--prompt-for-package () - "Prompt user for a package name." - ;; this code is taken from the interactive spec of describe-package - (unless package--initialized - (package-initialize t)) - (intern - (completing-read "Package: " - (append (mapcar #'car package-alist) - (mapcar #'car package-archive-contents) - (mapcar #'car package--builtins))))) - -(defun embark-browse-package-url (pkg) - "Open homepage for package PKG with `browse-url'." - (interactive (list (embark--prompt-for-package))) - (if-let ((url (embark--package-url pkg))) - (browse-url url) - (user-error "No homepage found for `%s'" pkg))) - -(defun embark-save-package-url (pkg) - "Save URL of homepage for package PKG on the `kill-ring'." - (interactive (list (embark--prompt-for-package))) - (if-let ((url (embark--package-url pkg))) - (kill-new url) - (user-error "No homepage found for `%s'" pkg))) - -(defun embark-save-variable-value (var) - "Save value of VAR in the `kill-ring'." - (interactive "SVariable: ") - (kill-new (string-trim (pp-to-string (symbol-value var))))) - -(defun embark-insert-variable-value (var) - "Insert value of VAR." - (interactive "SVariable: ") - (embark-insert (list (string-trim (pp-to-string (symbol-value var)))))) - -(defun embark-toggle-variable (var &optional local) - "Toggle value of boolean variable VAR. -If prefix LOCAL is non-nil make variable local." - (interactive "SVariable: \nP") - (let ((val (symbol-value var))) - (unless (memq val '(nil t)) - (user-error "Not a boolean variable")) - (when local - (make-local-variable var)) - (funcall (or (get var 'custom-set) 'set) var (not val)))) - -(defun embark-insert-relative-path (file) - "Insert relative path to FILE. -The insert path is relative to `default-directory'." - (interactive "FFile: ") - (embark-insert (list (file-relative-name (substitute-in-file-name file))))) - -(defun embark-save-relative-path (file) - "Save the relative path to FILE in the kill ring. -The insert path is relative to `default-directory'." - (interactive "FFile: ") - (kill-new (file-relative-name (substitute-in-file-name file)))) - -(defun embark-shell-command-on-buffer (buffer command &optional replace) - "Run shell COMMAND on contents of BUFFER. -Called with \\[universal-argument], replace contents of buffer -with command output. For replacement behavior see -`shell-command-dont-erase-buffer' setting." - (interactive - (list - (read-buffer "Buffer: " nil t) - (read-shell-command "Shell command: ") - current-prefix-arg)) - (with-current-buffer buffer - (shell-command-on-region (point-min) (point-max) - command - (and replace (current-buffer))))) - -(defun embark-open-externally (file) - "Open FILE or url using system's default application." - (interactive "sOpen externally: ") - (unless (string-match-p "\\`[a-z]+://" file) - (setq file (expand-file-name file))) - (message "Opening `%s' externally..." file) - (if (and (eq system-type 'windows-nt) - (fboundp 'w32-shell-execute)) - (w32-shell-execute "open" file) - (call-process (pcase system-type - ('darwin "open") - ('cygwin "cygstart") - (_ "xdg-open")) - nil 0 nil file))) - -(declare-function bookmark-prop-get "bookmark") -(declare-function bookmark-completing-read "bookmark") - -(defun embark-bookmark-open-externally (bookmark) - "Open BOOKMARK in external application." - (interactive (list (bookmark-completing-read "Open externally: "))) - (embark-open-externally - (or (bookmark-prop-get bookmark 'location) - (bookmark-prop-get bookmark 'filename) - (user-error "Bookmark `%s' does not have a location" bookmark)))) - -(defun embark-bury-buffer (buf) - "Bury buffer BUF." - (interactive "bBuffer: ") - (if-let (win (get-buffer-window buf)) - (with-selected-window win - (bury-buffer)) - (bury-buffer))) - -(defun embark-kill-buffer-and-window (buf) - "Kill buffer BUF and delete its window." - (interactive "bBuffer: ") - (when-let (buf (get-buffer buf)) - (if-let (win (get-buffer-window buf)) - (with-selected-window win - (kill-buffer-and-window)) - (kill-buffer buf)))) - -(defun embark-save-unicode-character (char) - "Save Unicode character CHAR to kill ring." - (interactive - (list (read-char-by-name "Insert character (Unicode name or hex): "))) - (kill-new (format "%c" char))) - -(defun embark-isearch-forward () - "Prompt for string in the minibuffer and start isearch forwards. -Unlike isearch, this command reads the string from the -minibuffer, which means it can be used as an Embark action." - (interactive) - (isearch-mode t) - (isearch-edit-string)) - -(defun embark-isearch-backward () - "Prompt for string in the minibuffer and start isearch backwards. -Unlike isearch, this command reads the string from the -minibuffer, which means it can be used as an Embark action." - (interactive) - (isearch-mode nil) - (isearch-edit-string)) - -(defun embark-toggle-highlight () - "Toggle symbol highlighting using `highlight-symbol-at-point'." - (interactive) - (let ((regexp (find-tag-default-as-symbol-regexp)) - (highlighted (cl-find-if #'boundp - '(hi-lock-interactive-lighters - hi-lock-interactive-patterns)))) - (if (and highlighted (assoc regexp (symbol-value highlighted))) - (unhighlight-regexp regexp) - (highlight-symbol-at-point)))) - -(defun embark-next-symbol () - "Jump to next occurrence of symbol at point. -The search respects symbol boundaries." - (interactive) - (if-let ((symbol (thing-at-point 'symbol))) - (let ((regexp (format "\\_<%s\\_>" (regexp-quote symbol)))) - (when (looking-at regexp) - (forward-symbol 1)) - (unless (re-search-forward regexp nil t) - (user-error "Symbol `%s' not found" symbol))) - (user-error "No symbol at point"))) - -(defun embark-previous-symbol () - "Jump to previous occurrence of symbol at point. -The search respects symbol boundaries." - (interactive) - (if-let ((symbol (thing-at-point 'symbol))) - (let ((regexp (format "\\_<%s\\_>" (regexp-quote symbol)))) - (when (looking-back regexp (- (point) (length symbol))) - (forward-symbol -1)) - (unless (re-search-backward regexp nil t) - (user-error "Symbol `%s' not found" symbol))) - (user-error "No symbol at point"))) - -(defun embark-compose-mail (address) - "Compose email to ADDRESS." - ;; The only reason we cannot use compose-mail directly is its - ;; interactive specification, which just supplies nil for the - ;; address (and several other arguments). - (interactive "sTo: ") - (compose-mail address)) - -(autoload 'pp-display-expression "pp") - -(defun embark-pp-eval-defun (edebug) - "Run `eval-defun' and pretty print the result. -With a prefix argument EDEBUG, instrument the code for debugging." - (interactive "P") - (cl-letf (((symbol-function #'eval-expression-print-format) - (lambda (result) - (pp-display-expression result "*Pp Eval Output*")))) - (eval-defun edebug))) - -(defun embark-eval-replace (noquote) - "Evaluate region and replace with evaluated result. -If NOQUOTE is non-nil (interactively, if called with a prefix -argument), no quoting is used for strings." - (interactive "P") - (let ((beg (region-beginning)) - (end (region-end))) - (save-excursion - (goto-char end) - (insert (format (if noquote "%s" "%S") - (eval (read (buffer-substring beg end)) lexical-binding))) - (delete-region beg end)))) - -(when (< emacs-major-version 29) - (defun embark-elp-restore-package (prefix) - "Remove instrumentation from functions with names starting with PREFIX." - (interactive "SPrefix: ") - (when (fboundp 'elp-restore-list) - (elp-restore-list - (mapcar #'intern - (all-completions (symbol-name prefix) - obarray 'elp-profilable-p)))))) - -(defmacro embark--define-hash (algorithm) - "Define command which computes hash from a string. -ALGORITHM is the hash algorithm symbol understood by `secure-hash'." - `(defun ,(intern (format "embark-hash-%s" algorithm)) (str) - ,(format "Compute %s hash of STR and store it in the kill ring." algorithm) - (interactive "sString: ") - (let ((hash (secure-hash ',algorithm str))) - (kill-new hash) - (message "%s: %s" ',algorithm hash)))) - -(embark--define-hash md5) -(embark--define-hash sha1) -(embark--define-hash sha224) -(embark--define-hash sha256) -(embark--define-hash sha384) -(embark--define-hash sha512) - -(defun embark-encode-url (start end) - "Properly URI-encode the region between START and END in current buffer." - (interactive "r") - (let ((encoded (url-encode-url (buffer-substring-no-properties start end)))) - (delete-region start end) - (insert encoded))) - -(defun embark-decode-url (start end) - "Decode the URI-encoded region between START and END in current buffer." - (interactive "r") - (let ((decoded (url-unhex-string (buffer-substring-no-properties start end)))) - (delete-region start end) - (insert decoded))) - -(defvar epa-replace-original-text) -(defun embark-epa-decrypt-region (start end) - "Decrypt region between START and END." - (interactive "r") - (let ((epa-replace-original-text t)) - (epa-decrypt-region start end))) - -(defvar eww-download-directory) -(autoload 'eww-download-callback "eww") - -(defun embark-download-url (url) - "Download URL to `eww-download-directory'." - (interactive "sDownload URL: ") - (let ((dir eww-download-directory)) - (when (functionp dir) (setq dir (funcall dir))) - (access-file dir "Download failed") - (url-retrieve - url #'eww-download-callback - (if (>= emacs-major-version 28) (list url dir) (list url))))) - -;;; Setup and pre-action hooks - -(defun embark--restart (&rest _) - "Restart current command with current input. -Use this to refresh the list of candidates for commands that do -not handle that themselves." - (when (minibufferp) - (embark--become-command embark--command (minibuffer-contents)))) - -(defun embark--shell-prep (&rest _) - "Prepare target for use as argument for a shell command. -This quotes the spaces, inserts an extra space at the beginning -and leaves the point to the left of it." - (let ((contents (minibuffer-contents))) - (delete-minibuffer-contents) - (insert " " (shell-quote-wildcard-pattern contents)) - (goto-char (minibuffer-prompt-end)))) - -(defun embark--force-complete (&rest _) - "Select first minibuffer completion candidate matching target." - (minibuffer-force-complete)) - -(cl-defun embark--eval-prep (&key type &allow-other-keys) - "If target's TYPE is variable, skip edit; if function, wrap in ()." - (when (memq type '(command function)) - (embark--allow-edit) - (goto-char (minibuffer-prompt-end)) - (insert "(") - (goto-char (point-max)) - (insert ")") - (backward-char))) - -(cl-defun embark--beginning-of-target (&key bounds &allow-other-keys) - "Go to beginning of the target BOUNDS." - (when (number-or-marker-p (car bounds)) - (goto-char (car bounds)))) - -(cl-defun embark--end-of-target (&key bounds &allow-other-keys) - "Go to end of the target BOUNDS." - (when (number-or-marker-p (cdr bounds)) - (goto-char (cdr bounds)))) - -(cl-defun embark--mark-target (&rest rest &key run bounds &allow-other-keys) - "Mark the target if its BOUNDS are known. -After marking the target, call RUN with the REST of its arguments." - (cond - ((and bounds run) - (save-mark-and-excursion - (set-mark (cdr bounds)) - (goto-char (car bounds)) - (apply run :bounds bounds rest))) - (bounds ;; used as pre- or post-action hook - (set-mark (cdr bounds)) - (goto-char (car bounds))) - (run (apply run rest)))) - -(cl-defun embark--unmark-target (&rest _) - "Deactivate the region target." - (deactivate-mark t)) - -(cl-defun embark--narrow-to-target - (&rest rest &key run bounds &allow-other-keys) - "Narrow buffer to target if its BOUNDS are known. -Intended for use as an Embark around-action hook. This function -runs RUN with the buffer narrowed to given BOUNDS passing along -the REST of the arguments." - (if bounds - (save-excursion - (save-restriction - (narrow-to-region (car bounds) (cdr bounds)) - (goto-char (car bounds)) - (apply run :bounds bounds rest))) - (apply run rest))) - -(defun embark--allow-edit (&rest _) - "Allow editing the target." - (remove-hook 'post-command-hook #'exit-minibuffer t) - (remove-hook 'post-command-hook 'ivy-immediate-done t)) - -(defun embark--ignore-target (&rest _) - "Ignore the target." - (let ((contents - (get-text-property (minibuffer-prompt-end) 'embark--initial-input))) - (delete-minibuffer-contents) - (when contents (insert contents))) - (embark--allow-edit)) - -(autoload 'xref-push-marker-stack "xref") -(defun embark--xref-push-marker (&rest _) - "Push a marker onto the xref marker stack." - (xref-push-marker-stack)) - -(cl-defun embark--confirm (&key action target &allow-other-keys) - "Ask for confirmation before running the ACTION on the TARGET." - (unless (y-or-n-p (format "Run %s on %s? " action target)) - (user-error "Canceled"))) - -(defconst embark--associated-file-fn-alist - `((file . identity) - (buffer . ,(lambda (target) - (let ((buffer (get-buffer target))) - (or (buffer-file-name buffer) - (buffer-local-value 'default-directory buffer))))) - (bookmark . bookmark-location) - (library . locate-library)) - "Alist of functions that extract a file path from targets of a given type.") - -(defun embark--associated-directory (target type) - "Return directory associated to TARGET of given TYPE. -The supported values of TYPE are file, buffer, bookmark and -library, which have an obvious notion of associated directory." - (when-let ((file-fn (alist-get type embark--associated-file-fn-alist)) - (file (funcall file-fn target))) - (if (file-directory-p file) - (file-name-as-directory file) - (file-name-directory file)))) - -(cl-defun embark--cd (&rest rest &key run target type &allow-other-keys) - "Run action with `default-directory' set to the directory of TARGET. -The supported values of TYPE are file, buffer, bookmark and -library, which have an obvious notion of associated directory. -The REST of the arguments are also passed to RUN." - (let ((default-directory - (or (embark--associated-directory target type) default-directory))) - (apply run :target target :type type rest))) - -(cl-defun embark--save-excursion (&rest rest &key run &allow-other-keys) - "Run action without moving point. -This simply calls RUN with the REST of its arguments inside -`save-excursion'." - (save-excursion (apply run rest))) - -(defun embark--universal-argument (&rest _) - "Run action with a universal prefix argument." - (setq prefix-arg '(4))) - -;;; keymaps - -(defvar-keymap embark-meta-map - :doc "Keymap for non-action Embark functions." - "-" #'negative-argument - "0" #'digit-argument - "1" #'digit-argument - "2" #'digit-argument - "3" #'digit-argument - "4" #'digit-argument - "5" #'digit-argument - "6" #'digit-argument - "7" #'digit-argument - "8" #'digit-argument - "9" #'digit-argument) - -(defvar-keymap embark-general-map - :doc "Keymap for Embark general actions." - :parent embark-meta-map - "i" #'embark-insert - "w" #'embark-copy-as-kill - "q" #'embark-toggle-quit - "E" #'embark-export - "S" #'embark-collect - "L" #'embark-live - "B" #'embark-become - "A" #'embark-act-all - "C-s" #'embark-isearch-forward - "C-r" #'embark-isearch-backward - "C-SPC" #'mark - "DEL" #'delete-region - "SPC" #'embark-select) - -(defvar-keymap embark-encode-map - :doc "Keymap for Embark region encoding actions." - "r" #'rot13-region - "." #'morse-region - "-" #'unmorse-region - "s" #'studlify-region - "m" #'embark-hash-md5 - "1" #'embark-hash-sha1 - "2" #'embark-hash-sha256 - "3" #'embark-hash-sha384 - "4" #'embark-hash-sha224 - "5" #'embark-hash-sha512 - "f" #'format-encode-region - "F" #'format-decode-region - "b" #'base64-encode-region - "B" #'base64-decode-region - "u" #'embark-encode-url - "U" #'embark-decode-url - "c" #'epa-encrypt-region - "C" #'embark-epa-decrypt-region) - -(fset 'embark-encode-map embark-encode-map) - -(defvar-keymap embark-sort-map - :doc "Keymap for Embark actions that sort the region" - "l" #'sort-lines - "P" #'sort-pages - "f" #'sort-fields - "c" #'sort-columns - "p" #'sort-paragraphs - "r" #'sort-regexp-fields - "n" #'sort-numeric-fields) - -(fset 'embark-sort-map embark-sort-map) - -;; these will have autoloads in Emacs 28 -(autoload 'calc-grab-sum-down "calc" nil t) -(autoload 'calc-grab-sum-across "calc" nil t) - -;; this has had an autoload cookie since at least Emacs 26 -;; but that autoload doesn't seem to work for me -(autoload 'org-table-convert-region "org-table" nil t) - -(defvar-keymap embark-region-map - :doc "Keymap for Embark actions on the active region." - :parent embark-general-map - "u" #'upcase-region - "l" #'downcase-region - "c" #'capitalize-region - "|" #'shell-command-on-region - "e" #'eval-region - "<" #'embark-eval-replace - "a" #'align - "A" #'align-regexp - "" #'indent-rigidly - "" #'indent-rigidly - "TAB" #'indent-region - "f" #'fill-region - "p" #'fill-region-as-paragraph - "$" #'ispell-region - "=" #'count-words-region - "F" #'whitespace-cleanup-region - "t" #'transpose-regions - "o" #'org-table-convert-region - ";" #'comment-or-uncomment-region - "W" #'write-region - "+" #'append-to-file - "m" #'apply-macro-to-region-lines - "n" #'narrow-to-region - "*" #'calc-grab-region - ":" #'calc-grab-sum-down - "_" #'calc-grab-sum-across - "r" #'reverse-region - "d" #'delete-duplicate-lines - "b" #'browse-url-of-region - "h" #'shr-render-region - "'" #'expand-region-abbrevs - "v" #'vc-region-history - "R" #'repunctuate-sentences - "s" 'embark-sort-map - ">" 'embark-encode-map) - -(defvar-keymap embark-vc-file-map - :doc "Keymap for Embark VC file actions." - "d" #'vc-delete-file - "r" #'vc-rename-file - "i" #'vc-ignore) - -(fset 'embark-vc-file-map embark-vc-file-map) - -(defvar-keymap embark-file-map - :doc "Keymap for Embark file actions." - :parent embark-general-map - "RET" #'find-file - "f" #'find-file - "F" #'find-file-literally - "o" #'find-file-other-window - "d" #'delete-file - "D" #'delete-directory - "r" #'rename-file - "c" #'copy-file - "s" #'make-symbolic-link - "j" #'embark-dired-jump - "!" #'shell-command - "&" #'async-shell-command - "$" #'eshell - "<" #'insert-file - "m" #'chmod - "=" #'ediff-files - "+" #'make-directory - "\\" #'embark-recentf-remove - "I" #'embark-insert-relative-path - "W" #'embark-save-relative-path - "x" #'embark-open-externally - "e" #'eww-open-file - "l" #'load-file - "b" #'byte-compile-file - "R" #'byte-recompile-directory - "v" 'embark-vc-file-map) - -(defvar-keymap embark-kill-ring-map - :doc "Keymap for `kill-ring' commands." - :parent embark-general-map - "\\" #'embark-kill-ring-remove) - -(defvar-keymap embark-url-map - :doc "Keymap for Embark url actions." - :parent embark-general-map - "RET" #'browse-url - "b" #'browse-url - "d" #'embark-download-url - "x" #'embark-open-externally - "e" #'eww) - -(defvar-keymap embark-email-map - :doc "Keymap for Embark email actions." - :parent embark-general-map - "RET" #'embark-compose-mail - "c" #'embark-compose-mail) - -(defvar-keymap embark-library-map - :doc "Keymap for operations on Emacs Lisp libraries." - :parent embark-general-map - "RET" #'find-library - "l" #'load-library - "f" #'find-library - "h" #'finder-commentary - "a" #'apropos-library - "L" #'locate-library - "m" #'info-display-manual - "$" #'eshell) - -(defvar-keymap embark-buffer-map - :doc "Keymap for Embark buffer actions." - :parent embark-general-map - "RET" #'switch-to-buffer - "k" #'kill-buffer - "b" #'switch-to-buffer - "o" #'switch-to-buffer-other-window - "z" #'embark-bury-buffer - "K" #'embark-kill-buffer-and-window - "r" #'embark-rename-buffer - "=" #'ediff-buffers - "|" #'embark-shell-command-on-buffer - "<" #'insert-buffer - "$" #'eshell) - -(defvar-keymap embark-tab-map - :doc "Keymap for actions for tab-bar tabs." - :parent embark-general-map - "RET" #'tab-bar-select-tab-by-name - "s" #'tab-bar-select-tab-by-name - "r" #'tab-bar-rename-tab-by-name - "k" #'tab-bar-close-tab-by-name) - -(defvar-keymap embark-identifier-map - :doc "Keymap for Embark identifier actions." - :parent embark-general-map - "RET" #'xref-find-definitions - "h" #'display-local-help - "H" #'embark-toggle-highlight - "d" #'xref-find-definitions - "r" #'xref-find-references - "a" #'xref-find-apropos - "s" #'info-lookup-symbol - "n" #'embark-next-symbol - "p" #'embark-previous-symbol - "'" #'expand-abbrev - "$" #'ispell-word - "o" #'occur) - -(defvar-keymap embark-expression-map - :doc "Keymap for Embark expression actions." - :parent embark-general-map - "RET" #'pp-eval-expression - "e" #'pp-eval-expression - "<" #'embark-eval-replace - "m" #'pp-macroexpand-expression - "TAB" #'indent-region - "r" #'raise-sexp - "t" #'transpose-sexps - "k" #'kill-region - "u" #'backward-up-list - "n" #'forward-list - "p" #'backward-list) - -(defvar-keymap embark-defun-map - :doc "Keymap for Embark defun actions." - :parent embark-expression-map - "RET" #'embark-pp-eval-defun - "e" #'embark-pp-eval-defun - "c" #'compile-defun - "D" #'edebug-defun - "o" #'checkdoc-defun - "N" #'narrow-to-defun) - -;; Use quoted symbols to avoid byte-compiler warnings. -(defvar-keymap embark-heading-map - :doc "Keymap for Embark heading actions." - :parent embark-general-map - "RET" 'outline-show-subtree - "TAB" 'outline-cycle ;; New in Emacs 28! - "C-SPC" 'outline-mark-subtree - "n" 'outline-next-visible-heading - "p" 'outline-previous-visible-heading - "f" 'outline-forward-same-level - "b" 'outline-backward-same-level - "^" 'outline-move-subtree-up - "v" 'outline-move-subtree-down - "u" 'outline-up-heading - "+" 'outline-show-subtree - "-" 'outline-hide-subtree - ">" 'outline-demote - "<" 'outline-promote) - -(defvar-keymap embark-symbol-map - :doc "Keymap for Embark symbol actions." - :parent embark-identifier-map - "RET" #'embark-find-definition - "h" #'describe-symbol - "s" #'embark-info-lookup-symbol - "d" #'embark-find-definition - "e" #'pp-eval-expression - "a" #'apropos - "\\" #'embark-history-remove) - -(defvar-keymap embark-face-map - :doc "Keymap for Embark face actions." - :parent embark-symbol-map - "h" #'describe-face - "c" #'customize-face - "+" #'make-face-bold - "-" #'make-face-unbold - "/" #'make-face-italic - "|" #'make-face-unitalic - "!" #'invert-face - "f" #'set-face-foreground - "b" #'set-face-background) - -(defvar-keymap embark-variable-map - :doc "Keymap for Embark variable actions." - :parent embark-symbol-map - "=" #'set-variable - "c" #'customize-set-variable - "u" #'customize-variable - "v" #'embark-save-variable-value - "<" #'embark-insert-variable-value - "t" #'embark-toggle-variable) - -(defvar-keymap embark-function-map - :doc "Keymap for Embark function actions." - :parent embark-symbol-map - "m" #'elp-instrument-function ;; m=measure - "M" 'elp-restore-function ;; quoted, not autoloaded - "k" #'debug-on-entry ;; breaKpoint (running out of letters, really) - "K" #'cancel-debug-on-entry - "t" #'trace-function - "T" 'untrace-function) ;; quoted, not autoloaded - -(defvar-keymap embark-command-map - :doc "Keymap for Embark command actions." - :parent embark-function-map - "x" #'execute-extended-command - "I" #'Info-goto-emacs-command-node - "b" #'where-is - "g" #'global-set-key - "l" #'local-set-key) - -(defvar-keymap embark-package-map - :doc "Keymap for Embark package actions." - :parent embark-general-map - "RET" #'describe-package - "h" #'describe-package - "i" #'package-install - "I" #'embark-insert - "d" #'package-delete - "r" #'package-reinstall - "u" #'embark-browse-package-url - "W" #'embark-save-package-url - "a" #'package-autoremove - "g" #'package-refresh-contents - "m" #'elp-instrument-package ;; m=measure - "M" (if (fboundp 'embark-elp-restore-package) - 'embark-elp-restore-package - 'elp-restore-package)) - -(defvar-keymap embark-bookmark-map - :doc "Keymap for Embark bookmark actions." - :parent embark-general-map - "RET" #'bookmark-jump - "s" #'bookmark-set - "d" #'bookmark-delete - "r" #'bookmark-rename - "R" #'bookmark-relocate - "l" #'bookmark-locate - "<" #'bookmark-insert - "j" #'bookmark-jump - "o" #'bookmark-jump-other-window - "f" #'bookmark-jump-other-frame - "a" 'bookmark-show-annotation - "e" 'bookmark-edit-annotation - "x" #'embark-bookmark-open-externally - "$" #'eshell) - -(defvar-keymap embark-unicode-name-map - :doc "Keymap for Embark Unicode name actions." - :parent embark-general-map - "RET" #'insert-char - "I" #'insert-char - "W" #'embark-save-unicode-character) - -(defvar-keymap embark-prose-map - :doc "Keymap for Embark actions for dealing with prose." - :parent embark-general-map - "$" #'ispell-region - "f" #'fill-region - "u" #'upcase-region - "l" #'downcase-region - "c" #'capitalize-region - "F" #'whitespace-cleanup-region - "=" #'count-words-region) - -(defvar-keymap embark-sentence-map - :doc "Keymap for Embark actions for dealing with sentences." - :parent embark-prose-map - "t" #'transpose-sentences - "n" #'forward-sentence - "p" #'backward-sentence) - -(defvar-keymap embark-paragraph-map - :doc "Keymap for Embark actions for dealing with paragraphs." - :parent embark-prose-map - "t" #'transpose-paragraphs - "n" #'forward-paragraph - "p" #'backward-paragraph - "R" #'repunctuate-sentences) - -(defvar-keymap embark-flymake-map - :doc "Keymap for Embark actions on Flymake diagnostics." - :parent embark-general-map - "RET" 'flymake-show-buffer-diagnostics - "n" 'flymake-goto-next-error - "p" 'flymake-goto-prev-error) - -(defvar-keymap embark-become-help-map - :doc "Keymap for Embark help actions." - :parent embark-meta-map - "V" #'apropos-variable - "U" #'apropos-user-option - "C" #'apropos-command - "v" #'describe-variable - "f" #'describe-function - "s" #'describe-symbol - "F" #'describe-face - "p" #'describe-package - "i" #'describe-input-method) - -(autoload 'recentf-open-files "recentf" nil t) - -(defvar-keymap embark-become-file+buffer-map - :doc "Embark become keymap for files and buffers." - :parent embark-meta-map - "f" #'find-file - "4 f" #'find-file-other-window - "." #'find-file-at-point - "p" #'project-find-file - "r" #'recentf-open-files - "b" #'switch-to-buffer - "4 b" #'switch-to-buffer-other-window - "l" #'locate - "L" #'find-library - "v" #'vc-dir) - -(defvar-keymap embark-become-shell-command-map - :doc "Embark become keymap for shell commands." - :parent embark-meta-map - "!" #'shell-command - "&" #'async-shell-command - "c" #'comint-run - "t" #'term) - -(defvar-keymap embark-become-match-map - :doc "Embark become keymap for search." - :parent embark-meta-map - "o" #'occur - "k" #'keep-lines - "f" #'flush-lines - "c" #'count-matches) - -(provide 'embark) - -;; Check that embark-consult is installed. If Embark is used in -;; combination with Consult, you should install the integration package, -;; such that features like embark-export from consult-grep work as -;; expected. - -(with-eval-after-load 'consult - (unless (require 'embark-consult nil 'noerror) - (warn "The package embark-consult should be installed if you use both Embark and Consult"))) - -(with-eval-after-load 'org - (require 'embark-org)) - -;;; embark.el ends here blob - 5bdf6fd00ee6ccba2545efb7da723caf19b0713e (mode 644) blob + /dev/null --- elpa/embark-1.0/embark.info +++ /dev/null @@ -1,1449 +0,0 @@ -This is docKihIip.info, produced by makeinfo version 6.8 from -embark.texi. - -INFO-DIR-SECTION Emacs misc features -START-INFO-DIR-ENTRY -* Embark: (embark). Emacs Mini-Buffer Actions Rooted in Keymaps. -END-INFO-DIR-ENTRY - - -File: docKihIip.info, Node: Top, Next: Overview, Up: (dir) - -Embark: Emacs Mini-Buffer Actions Rooted in Keymaps -*************************************************** - -* Menu: - -* Overview:: -* Quick start:: -* Advanced configuration:: -* How does Embark call the actions?:: -* Embark, Marginalia and Consult: Embark Marginalia and Consult. -* Related Packages:: -* Resources:: -* Contributions:: -* Acknowledgments:: - -— The Detailed Node Listing — - -Overview - -* Acting on targets:: -* The default action on a target:: -* Working with sets of possible targets:: -* Switching to a different command without losing what you've typed:: - -Working with sets of possible targets - -* Selecting some targets to make an ad hoc candidate set:: -* embark-live a live-updating variant of embark-collect:: - -Advanced configuration - -* Showing information about available targets and actions:: -* Selecting commands via completions instead of key bindings:: -* Quitting the minibuffer after an action:: -* Running some setup after injecting the target:: -* Running hooks before, after or around an action: Running hooks before after or around an action. -* Creating your own keymaps:: -* Defining actions for new categories of targets:: - -Defining actions for new categories of targets - -* New minibuffer target example - tab-bar tabs:: -* New target example in regular buffers - short Wikipedia links:: - -How does Embark call the actions? - -* Non-interactive functions as actions:: - -Embark, Marginalia and Consult - -* Marginalia:: -* Consult:: - - - -File: docKihIip.info, Node: Overview, Next: Quick start, Prev: Top, Up: Top - -1 Overview -********** - -Embark makes it easy to choose a command to run based on what is near -point, both during a minibuffer completion session (in a way familiar to -Helm or Counsel users) and in normal buffers. Bind the command -‘embark-act’ to a key and it acts like prefix-key for a keymap of -_actions_ (commands) relevant to the _target_ around point. With point -on an URL in a buffer you can open the URL in a browser or eww or -download the file it points to. If while switching buffers you spot an -old one, you can kill it right there and continue to select another. -Embark comes preconfigured with over a hundred actions for common types -of targets such as files, buffers, identifiers, s-expressions, -sentences; and it is easy to add more actions and more target types. -Embark can also collect all the candidates in a minibuffer to an -occur-like buffer or export them to a buffer in a major-mode specific to -the type of candidates, such as dired for a set of files, ibuffer for a -set of buffers, or customize for a set of variables. - -* Menu: - -* Acting on targets:: -* The default action on a target:: -* Working with sets of possible targets:: -* Switching to a different command without losing what you've typed:: - - -File: docKihIip.info, Node: Acting on targets, Next: The default action on a target, Up: Overview - -1.1 Acting on targets -===================== - -You can think of ‘embark-act’ as a keyboard-based version of a -right-click contextual menu. The ‘embark-act’ command (which you should -bind to a convenient key), acts as a prefix for a keymap offering you -relevant _actions_ to use on a _target_ determined by the context: - - • In the minibuffer, the target is the current top completion - candidate. - • In the ‘*Completions*’ buffer the target is the completion at - point. - • In a regular buffer, the target is the region if active, or else - the file, symbol, URL, s-expression or defun at point. - - Multiple targets can be present at the same location and you can -cycle between them by repeating the ‘embark-act’ key binding. The type -of actions offered depend on the type of the target. Here is a sample -of a few of the actions offered in the default configuration: - - • For files you get offered actions like deleting, copying, renaming, - visiting in another window, running a shell command on the file, - etc. - • For buffers the actions include switching to or killing the buffer. - • For package names the actions include installing, removing or - visiting the homepage. - • For Emacs Lisp symbols the actions include finding the definition, - looking up documentation, evaluating (which for a variable - immediately shows the value, but for a function lets you pass it - some arguments first). There are some actions specific to - variables, such as setting the value directly or though the - customize system, and some actions specific to commands, such as - binding it to a key. - - By default when you use ‘embark-act’ if you don’t immediately select -an action, after a short delay Embark will pop up a buffer showing a -list of actions and their corresponding key bindings. If you are using -‘embark-act’ outside the minibuffer, Embark will also highlight the -current target. These behaviors are configurable via the variable -‘embark-indicators’. Instead of selecting an action via its key -binding, you can select it by name with completion by typing ‘C-h’ after -‘embark-act’. - - Everything is easily configurable: determining the current target, -classifying it, and deciding which actions are offered for each type in -the classification. The above introduction just mentions part of the -default configuration. - - Configuring which actions are offered for a type is particularly easy -and requires no programming: the variable ‘embark-keymap-alist’ -associates target types with variables containing keymaps, and those -keymaps containing bindings for the actions. (To examine the available -categories and their associated keymaps, you can use ‘C-h v -embark-keymap-alist’ or customize that variable.) For example, in the -default configuration the type ‘file’ is associated with the symbol -‘embark-file-map’. That symbol names a keymap with single-letter key -bindings for common Emacs file commands, for instance ‘c’ is bound to -‘copy-file’. This means that if you are in the minibuffer after running -a command that prompts for a file, such as ‘find-file’ or ‘rename-file’, -you can copy a file by running ‘embark-act’ and then pressing ‘c’. - - These action keymaps are very convenient but not strictly necessary -when using ‘embark-act’: you can use any command that reads from the -minibuffer as an action and the target of the action will be inserted at -the first minibuffer prompt. After running ‘embark-act’ all of your key -bindings and even ‘execute-extended-command’ can be used to run a -command. For example, if you want to replace all occurrences of the -symbol at point, just use ‘M-%’ as the action, there is no need to bind -‘query-replace’ in one of Embark’s keymaps. Also, those action keymaps -are normal Emacs keymaps and you should feel free to bind in them -whatever commands you find useful as actions and want to be available -through convenient bindings. - - The actions in ‘embark-general-map’ are available no matter what type -of completion you are in the middle of. By default this includes -bindings to save the current candidate in the kill ring and to insert -the current candidate in the previously selected buffer (the buffer that -was current when you executed a command that opened up the minibuffer). - - Emacs’s minibuffer completion system includes metadata indicating the -_category_ of what is being completed. For example, ‘find-file’’s -metadata indicates a category of ‘file’ and ‘switch-to-buffer’’s -metadata indicates a category of ‘buffer’. Embark has the related -notion of the _type_ of a target for actions, and by default when -category metadata is present it is taken to be the type of minibuffer -completion candidates when used as targets. Emacs commands often do not -set useful category metadata so the Marginalia -(https://github.com/minad/marginalia) package, which supplies this -missing metadata, is highly recommended for use with Embark. - - Embark’s default configuration has actions for the following target -types: files, buffers, symbols, packages, URLs, bookmarks, and as a -somewhat special case, actions for when the region is active. You can -read about the default actions and their key bindings -(https://github.com/oantolin/embark/wiki/Default-Actions) on the GitHub -project wiki. - - -File: docKihIip.info, Node: The default action on a target, Next: Working with sets of possible targets, Prev: Acting on targets, Up: Overview - -1.2 The default action on a target -================================== - -Embark has a notion of default action for a target: - - • If the target is a minibuffer completion candidate, then the - default action is whatever command opened the minibuffer in the - first place. For example if you run ‘kill-buffer’, then the - default action will be to kill buffers. - • If the target comes from a regular buffer (i.e., not a minibuffer), - then the default action is whatever is bound to ‘RET’ in the keymap - of actions for that type of target. For example, in Embark’s - default configuration for a URL found at point the default action - is ‘browse-url’, because ‘RET’ is bound to ‘browse-url’ in the - ‘embark-url-map’ keymap. - - To run the default action you can press ‘RET’ after running -‘embark-act’. Note that if there are several different targets at a -given location, each has its own default action, so first cycle to the -target you want and then press ‘RET’ to run the corresponding default -action. - - There is also ‘embark-dwim’ which runs the default action for the -first target found. It’s pretty handy in non-minibuffer buffers: with -Embark’s default configuration it will: - - • Open the file at point. - • Open the URL at point in a web browser (using the ‘browse-url’ - command). - • Compose a new email to the email address at point. - • In an Emacs Lisp buffer, if point is on an opening parenthesis or - right after a closing one, it will evaluate the corresponding - expression. - • Go to the definition of an Emacs Lisp function, variable or macro - at point. - • Find the file corresponding to an Emacs Lisp library at point. - - -File: docKihIip.info, Node: Working with sets of possible targets, Next: Switching to a different command without losing what you've typed, Prev: The default action on a target, Up: Overview - -1.3 Working with sets of possible targets -========================================= - -Besides acting individually on targets, Embark lets you work -collectively on a set of target _candidates_. For example, while you -are in the minibuffer the candidates are simply the possible completions -of your input. Embark provides three main commands to work on candidate -sets: - - • The ‘embark-act-all’ command runs the same action on each of the - current candidates. It is just like using ‘embark-act’ on each - candidate in turn. (Because you can easily act on many more - candidates than you meant to, by default Embark asks you to confirm - uses of ‘embark-act-all’; you can turn this off by setting the user - option ‘embark-confirm-act-all’ to ‘nil’.) - - • The ‘embark-collect’ command produces a buffer listing all the - current candidates, for you to peruse and run actions on at your - leisure. The candidates are displayed as a list showing additional - annotations. If any of the candidates contain newlines, then - horizontal lines are used to separate candidates. - - The Embark Collect buffer is somewhat “dired-like”: you can select - and deselect candidates through ‘embark-select’ (available as an - action in ‘embark-act’, bound to ‘SPC’; but you could also give it - a global key binding). In an Embark Collect buffer ‘embark-act’ is - bound to ‘a’ and ‘embark-act-all’ is bound to ‘A’; ‘embark-act-all’ - will act on all currently marked candidates if there any, and will - act on all candidates if none are marked. In particular, this - means that ‘a SPC’ will toggle whether the candidate at point is - selected, and ‘A SPC’ will select all candidates if none are - selected, or deselect all selected candidates if there are some. - - • The ‘embark-export’ command tries to open a buffer in an - appropriate major mode for the set of candidates. If the - candidates are files export produces a Dired buffer; if they are - buffers, you get an Ibuffer buffer; and if they are packages you - get a buffer in package menu mode. - - If you use the grepping commands from the Consult - (https://github.com/minad/consult/) package, ‘consult-grep’, - ‘consult-git-grep’ or ‘consult-ripgrep’, then you should install - the ‘embark-consult’ package, which adds support for exporting a - list of grep results to an honest grep-mode buffer, on which you - can even use wgrep (https://github.com/mhayashi1120/Emacs-wgrep) if - you wish. - - When in doubt choosing between exporting and collecting, a good rule -of thumb is to always prefer ‘embark-export’ since when an exporter to a -special major mode is available for a given type of target, it will be -more featureful than an Embark collect buffer, and if no such exporter -is configured the ‘embark-export’ command falls back to the generic -‘embark-collect’. - - These commands are always available as “actions” (although they do -not act on just the current target but on all candidates) for -‘embark-act’ and are bound to ‘A’, ‘S’ (for “snapshot”), and ‘E’, -respectively, in ‘embark-general-map’. This means that you do not have -to bind your own key bindings for these (although you can, of course!), -just a key binding for ‘embark-act’. - - In Embark Collect or Embark Export buffers that were obtained by -running ‘embark-collect’ or ‘embark-export’ from within a minibuffer -completion session, ‘g’ is bound to a command that restarts the -completion session, that is, the command that opened the minibuffer is -run again and the minibuffer contents restored. You can then interact -normally with the command, perhaps editing the minibuffer contents, and, -if you wish, you can rerun ‘embark-collect’ or ‘embark-export’ to get an -updated buffer. - -* Menu: - -* Selecting some targets to make an ad hoc candidate set:: -* embark-live a live-updating variant of embark-collect:: - - -File: docKihIip.info, Node: Selecting some targets to make an ad hoc candidate set, Next: embark-live a live-updating variant of embark-collect, Up: Working with sets of possible targets - -1.3.1 Selecting some targets to make an ad hoc candidate set ------------------------------------------------------------- - -The commands for working with sets of candidates just described, namely -‘embark-act-all’, ‘embark-export’ and ‘embark-collect’ by default work -with all candidates defined in the current context. For example, in the -minibuffer they operate on all currently completion candidates, or in a -dired buffer they work on all marked files (or all files if none are -marked). Embark also has a notion of _selection_, where you can -accumulate an ad hoc list of targets for these commands to work on. - - The selection is controlled by using the ‘embark-select’ action, -bound to ‘SPC’ in ‘embark-general-map’ so that it is always available -(you can also give ‘embark-select’ a global key binding if you wish; -when called directly, not as an action for ‘embark-act’, it will select -the first target at point). Calling this action on a target toggles its -membership in the current buffer’s Embark selection; that is, it adds it -to selection if not selected and removes it from the selection if it was -selected. Whenever the selection for a buffer is non-empty, the -commands ‘embark-act-all’, ‘embark-export’ and ‘embark-collect’ will act -on the selection. - - To deselect all selected targets, you can use the ‘embark-select’ -action through ‘embark-act-all’, since this will run ‘embark-select’ on -each member of the current selection. Similarly if no targets are -selected and you are in a minibuffer completion session, running -‘embark-select’ from ‘embark-act-all’ will select all the current -completion candidates. - - By default, whenever some targets are selected in the current buffer, -a count of selected targets appears in the mode line. This can be -turned off or customized through the ‘embark-selection-indicator’ user -option. - - The selection functionality is supported in every buffer: - - • In the minibuffer this gives a convenient way to act on several - completion candidates that don’t follow any simple pattern: just go - through the completions selecting the ones you want, then use - ‘embark-act-all’. For example, you could attach several files at - once to an email. - • For Embark Collect buffers this functionality enables a dired-like - workflow, in which you mark various candidates and apply an action - to all at once. (It supersedes a previous ad hoc dired-like - interface that was implemented only in Embark Collect buffers, with - a slightly different interface.) - • In a eww buffer you could use this to select various links you wish - to follow up on, and then collect them into a buffer. Similarly, - while reading Emacs’s info manual you could select some symbols you - want to read more about and export them to an ‘apropos-mode’ - buffer. - • You can use selections in regular text or programming buffers to do - complex editing operations. For example, if you have three - paragraphs scattered over a file and you want to bring them - together, you can select each one, insert them all somewhere and - finally delete all of them (from their original locations). - - -File: docKihIip.info, Node: embark-live a live-updating variant of embark-collect, Prev: Selecting some targets to make an ad hoc candidate set, Up: Working with sets of possible targets - -1.3.2 ‘embark-live’ a live-updating variant of ‘embark-collect’ ---------------------------------------------------------------- - -Finally, there is also an ‘embark-live’ variant of the ‘embark-collect’ -command which automatically updates the collection after each change in -the source buffer. Users of a completion UI that automatically updates -and displays the candidate list (such as Vertico, Icomplete, Fido-mode, -or MCT) will probably not want to use ‘embark-live’ from the minibuffer -as they will then have two live updating displays of the completion -candidates! - - A more likely use of ‘embark-live’ is to be called from a regular -buffer to display a sort of live updating “table of contents” for the -buffer. This depends on having appropriate candidate collectors -configured in ‘embark-candidate-collectors’. There are not many in -Embark’s default configuration, but you can try this experiment: open a -dired buffer in a directory that has very many files, mark a few, and -run ‘embark-live’. You’ll get an Embark Collect buffer containing only -the marked files, which updates as you mark or unmark files in dired. -To make ‘embark-live’ genuinely useful other candidate collectors are -required. The ‘embark-consult’ package (documented near the end of this -manual) contains a few: one for imenu items and one for outline headings -as used by ‘outline-minor-mode’. Those collectors really do give -‘embark-live’ a table-of-contents feel. - - -File: docKihIip.info, Node: Switching to a different command without losing what you've typed, Prev: Working with sets of possible targets, Up: Overview - -1.4 Switching to a different command without losing what you’ve typed -===================================================================== - -Embark also has the ‘embark-become’ command which is useful for when you -run a command, start typing at the minibuffer and realize you meant a -different command. The most common case for me is that I run -‘switch-to-buffer’, start typing a buffer name and realize I haven’t -opened the file I had in mind yet! I’ll use this situation as a running -example to illustrate ‘embark-become’. When this happens I can, of -course, press ‘C-g’ and then run ‘find-file’ and open the file, but this -requires retyping the portion of the file name you already typed. This -process can be streamlined with ‘embark-become’: while still in the -‘switch-to-buffer’ you can run ‘embark-become’ and effectively make the -‘switch-to-buffer’ command become ‘find-file’ for this run. - - You can bind ‘embark-become’ to a key in ‘minibuffer-local-map’, but -it is also available as an action under the letter ‘B’ (uppercase), so -you don’t need a binding if you already have one for ‘embark-act’. So, -assuming I have ‘embark-act’ bound to, say, ‘C-.’, once I realize I -haven’t open the file I can type ‘C-. B C-x C-f’ to have -‘switch-to-buffer’ become ‘find-file’ without losing what I have already -typed in the minibuffer. - - But for even more convenience, ‘embark-become’ offers shorter key -bindings for commands you are likely to want the current command to -become. When you use ‘embark-become’ it looks for the current command -in all keymaps named in the list ‘embark-become-keymaps’ and then -activates all keymaps that contain it. For example, the default value -of ‘embark-become-keymaps’ contains a keymap -‘embark-become-file+buffer-map’ with bindings for several commands -related to files and buffers, in particular, it binds ‘switch-to-buffer’ -to ‘b’ and ‘find-file’ to ‘f’. So when I accidentally try to switch to -a buffer for a file I haven’t opened yet, ‘embark-become’ finds that the -command I ran, ‘switch-to-buffer’, is in the keymap -‘embark-become-file+buffer-map’, so it activates that keymap (and any -others that also contain a binding for ‘switch-to-buffer’). The end -result is that I can type ‘C-. B f’ to switch to ‘find-file’. - - -File: docKihIip.info, Node: Quick start, Next: Advanced configuration, Prev: Overview, Up: Top - -2 Quick start -************* - -The easiest way to install Embark is from GNU ELPA, just run ‘M-x -package-install RET embark RET’. (It is also available on MELPA.) It -is highly recommended to also install Marginalia -(https://github.com/minad/marginalia) (also available on GNU ELPA), so -that Embark can offer you preconfigured actions in more contexts. For -‘use-package’ users, the following is a very reasonable starting -configuration: - - (use-package marginalia - :ensure t - :config - (marginalia-mode)) - - (use-package embark - :ensure t - - :bind - (("C-." . embark-act) ;; pick some comfortable binding - ("C-;" . embark-dwim) ;; good alternative: M-. - ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' - - :init - - ;; Optionally replace the key help with a completing-read interface - (setq prefix-help-command #'embark-prefix-help-command) - - ;; Show the Embark target at point via Eldoc. You may adjust the Eldoc - ;; strategy, if you want to see the documentation from multiple providers. - (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target) - ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) - - :config - - ;; Hide the mode line of the Embark live/completions buffers - (add-to-list 'display-buffer-alist - '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" - nil - (window-parameters (mode-line-format . none))))) - - ;; Consult users will also want the embark-consult package. - (use-package embark-consult - :ensure t ; only need to install it, embark loads it after consult if found - :hook - (embark-collect-mode . consult-preview-at-point-mode)) - - About the suggested key bindings for ‘embark-act’ and ‘embark-dwim’: - • Those key bindings are unlikely to work in the terminal, but - terminal users are probably well aware of this and will know to - select different bindings. - • The suggested ‘C-.’ binding is used by default in (at least some - installations of) GNOME to input emojis, and Emacs doesn’t even get - a chance to respond to the binding. You can select a different key - binding for ‘embark-act’ or use ‘ibus-setup’ to change the shortcut - for emoji insertion (Emacs 29 will likely use ‘C-x 8 e e’, in case - you want to set the same one system-wide). - • The suggested alternative of ‘M-.’ for ‘embark-dwim’ is bound by - default to ‘xref-find-definitions’. That is a very useful command - but overwriting it with ‘embark-dwim’ is sensible since in Embark’s - default configuration, ‘embark-dwim’ will also find the definition - of the identifier at point. (Note that ‘xref-find-definitions’ - with a prefix argument prompts you for an identifier, ‘embark-dwim’ - does not cover this case). - - Other Embark commands such as ‘embark-act-all’, ‘embark-become’, -‘embark-collect’, and ‘embark-export’ can be run through ‘embark-act’ as -actions bound to ‘A’, ‘B’, ‘S’ (for “snapshot”), and ‘E’ respectively, -and thus don’t really need a dedicated key binding, but feel free to -bind them directly if you so wish. If you do choose to bind them -directly, you’ll probably want to bind them in ‘minibuffer-local-map’, -since they are most useful in the minibuffer (in fact, ‘embark-become’ -only works in the minibuffer). - - The command ‘embark-dwim’ executes the default action at point. -Another good keybinding for ‘embark-dwim’ is ‘M-.’ since ‘embark-dwim’ -acts like ‘xref-find-definitions’ on the symbol at point. ‘C-.’ can be -seen as a right-click context menu at point and ‘M-.’ acts like -left-click. The keybindings are mnemonic, both act at the point (‘.’). - - Embark needs to know what your minibuffer completion system considers -to be the list of candidates and which one is the current candidate. -Embark works out of the box if you use Emacs’s default tab completion, -the built-in ‘icomplete-mode’ or ‘fido-mode’, or the third-party -packages Vertico (https://github.com/minad/vertico) or Ivy -(https://github.com/abo-abo/swiper). - - If you are a Helm (https://emacs-helm.github.io/helm/) or Ivy -(https://github.com/abo-abo/swiper) user you are unlikely to want Embark -since those packages include comprehensive functionality for acting on -minibuffer completion candidates. (Embark does come with Ivy -integration despite this.) - - -File: docKihIip.info, Node: Advanced configuration, Next: How does Embark call the actions?, Prev: Quick start, Up: Top - -3 Advanced configuration -************************ - -* Menu: - -* Showing information about available targets and actions:: -* Selecting commands via completions instead of key bindings:: -* Quitting the minibuffer after an action:: -* Running some setup after injecting the target:: -* Running hooks before, after or around an action: Running hooks before after or around an action. -* Creating your own keymaps:: -* Defining actions for new categories of targets:: - - -File: docKihIip.info, Node: Showing information about available targets and actions, Next: Selecting commands via completions instead of key bindings, Up: Advanced configuration - -3.1 Showing information about available targets and actions -=========================================================== - -By default, if you run ‘embark-act’ and do not immediately select an -action, after a short delay Embark will pop up a buffer called ‘*Embark -Actions*’ containing a list of available actions with their key -bindings. You can scroll that buffer with the mouse of with the usual -commands ‘scroll-other-window’ and ‘scroll-other-window-down’ (bound by -default to ‘C-M-v’ and ‘C-M-S-v’). - - That functionality is provided by the ‘embark-mixed-indicator’, but -Embark has other indicators that can provide information about the -target and its type, what other targets you can cycle to, and which -actions have key bindings in the action map for the current type of -target. Any number of indicators can be active at once and the user -option ‘embark-indicators’ should be set to a list of the desired -indicators. - - Embark comes with the following indicators: - - • ‘embark-minimal-indicator’: shows a messages in the echo area or - minibuffer prompt showing the current target and the types of all - targets starting with the current one; this one is on by default. - - • ‘embark-highlight-indicator’: highlights the target at point; also - on by default. - - • ‘embark-verbose-indicator’: displays a table of actions and their - key bindings in a buffer; this is not on by default, in favor of - the mixed indicator described next. - - • ‘embark-mixed-indicator’: starts out by behaving as the minimal - indicator but after a short delay acts as the verbose indicator; - this is on by default. - - • ‘embark-isearch-highlight-indicator’: this only does something when - the current target is the symbol at point, in which case it lazily - highlights all occurrences of that symbol in the current buffer, - like isearch; also on by default. - - Users of the popular which-key -(https://github.com/justbur/emacs-which-key) package may prefer to use -the ‘embark-which-key-indicator’ from the Embark wiki -(https://github.com/oantolin/embark/wiki/Additional-Configuration#use-which-key-like-a-key-menu-prompt). -Just copy its definition from the wiki into your configuration and -customize the ‘embark-indicators’ user option to exclude the mixed and -verbose indicators and to include ‘embark-which-key-indicator’. - - -File: docKihIip.info, Node: Selecting commands via completions instead of key bindings, Next: Quitting the minibuffer after an action, Prev: Showing information about available targets and actions, Up: Advanced configuration - -3.2 Selecting commands via completions instead of key bindings -============================================================== - -As an alternative to reading the list of actions in the verbose or mixed -indicators (see the previous section for a description of these), you -can press the ‘embark-help-key’, which is ‘C-h’ by default (but you may -prefer ‘?’ to free up ‘C-h’ for use as a prefix) after running -‘embark-act’. Pressing the help key will prompt you for the name of an -action with completion (but feel free to enter a command that is not -among the offered candidates!), and will also remind you of the key -bindings. You can press ‘embark-keymap-prompter-key’, which is ‘@’ by -default, at the prompt and then one of the key bindings to enter the -name of the corresponding action. - - You may think that with the ‘*Embark Actions*’ buffer popping up to -remind you of the key bindings you’d never want to use completion to -select an action by name, but personally I find that typing a small -portion of the action name to narrow down the list of candidates feels -significantly faster than visually scanning the entire list of actions. - - If you find you prefer entering actions that way, you can configure -embark to always prompt you for actions by setting the variable -‘embark-prompter’ to ‘embark-completing-read-prompter’. - - -File: docKihIip.info, Node: Quitting the minibuffer after an action, Next: Running some setup after injecting the target, Prev: Selecting commands via completions instead of key bindings, Up: Advanced configuration - -3.3 Quitting the minibuffer after an action -=========================================== - -By default, if you call ‘embark-act’ from the minibuffer it quits the -minibuffer after performing the action. You can change this by setting -the user option ‘embark-quit-after-action’ to ‘nil’. Having -‘embark-act’ _not_ quit the minibuffer can be useful to turn commands -into little “thing managers”. For example, you can use ‘find-file’ as a -little file manager or ‘describe-package’ as a little package manager: -you can run those commands, perform a series of actions, and then quit -the command. - - If you want to control the quitting behavior in a fine-grained manner -depending on the action, you can set ‘embark-quit-after-action’ to an -alist, associating commands to either ‘t’ for quitting or ‘nil’ for not -quitting. When using an alist, you can use the special key ‘t’ to -specify the default behavior. For example, to specify that by default -actions should not quit the minibuffer but that using ‘kill-buffer’ as -an action should quit, you can use the following configuration: - - (setq embark-quit-after-action '((kill-buffer . t) (t . nil))) - - The variable ‘embark-quit-after-action’ only specifies a default, -that is, it only controls whether or not ‘embark-act’ quits the -minibuffer when you call it without a prefix argument, and you can -select the opposite behavior to what the variable says by calling -‘embark-act’ with ‘C-u’. Also note that both the variable -‘embark-quit-after-action’ and ‘C-u’ have no effect when you call -‘embark-act’ outside the minibuffer. - - If you find yourself using the quitting and non-quitting variants of -‘embark-act’ about equally often, independently of the action, you may -prefer to simply have separate commands for them instead of a single -command that you call with ‘C-u’ half the time. You could, for example, -keep the default exiting behavior of ‘embark-act’ and define a -non-quitting version as follows: - - (defun embark-act-noquit () - "Run action but don't quit the minibuffer afterwards." - (interactive) - (let ((embark-quit-after-action nil)) - (embark-act))) - - -File: docKihIip.info, Node: Running some setup after injecting the target, Next: Running hooks before after or around an action, Prev: Quitting the minibuffer after an action, Up: Advanced configuration - -3.4 Running some setup after injecting the target -================================================= - -You can customize what happens after the target is inserted at the -minibuffer prompt of an action. There are -‘embark-target-injection-hooks’, that are run by default after injecting -the target into the minibuffer. The variable -‘embark-target-injection-hooks’ is an alist associating commands to -their setup hooks. There are two special keys: if no setup hook is -specified for a given action, the hook associated to ‘t’ is run; and the -hook associated to ‘:always’ is run regardless of the action. (This -variable used to have the less explicit name of -‘embark-setup-action-hooks’, so please update your configuration.) - - For example, consider using ‘shell-command’ as an action during file -completion. It would be useful to insert a space before the target file -name and to leave the point at the beginning, so you can immediately -type the shell command to run on that file. That’s why in Embark’s -default configuration there is an entry in -‘embark-target-injection-hooks’ associating ‘shell-command’ to a hook -that includes ‘embark--shell-prep’, a simple helper function that quotes -all the spaces in the file name, inserts an extra space at the beginning -of the line and leaves point to the left of it. - - Now, the preparation that ‘embark--shell-prep’ does would be useless -if Embark did what it normally does after it inserts the target of the -action at the minibuffer prompt, which is to “press ‘RET’” for you, -accepting the target as is; if Embark did that for ‘shell-command’ you -wouldn’t get a chance to type in the command to execute! That is why in -Embark’s default configuration the entry for ‘shell-command’ in -‘embark-target-injection-hooks’ also contains the function -‘embark--allow-edit’. - - Embark used to have a dedicated variable ‘embark-allow-edit-actions’ -to which you could add commands for which Embark should forgo pressing -‘RET’ for you after inserting the target. Since its effect can also be -achieved via the general ‘embark-target-injection-hooks’ mechanism, that -variable has been removed to simplify Embark. Be sure to update your -configuration; if you had something like: - - (add-to-list 'embark-allow-edit-actions 'my-command) - - you should replace it with: - - (push 'embark--allow-edit - (alist-get 'my-command embark-target-injection-hooks)) - - Also note that while you could abuse ‘embark--allow-edit’ so that you -have to confirm “dangerous” actions such as ‘delete-file’, it is better -to implement confirmation by adding the ‘embark--confirm’ function to -the appropriate entry of a different hook alist, namely, -‘embark-pre-action-hooks’. - - Besides ‘embark--allow-edit’, Embark comes with another function that -is of general utility in action setup hooks: ‘embark--ignore-target’. -Use it for commands that do prompt you in the minibuffer but for which -inserting the target would be inappropriate. This is not a common -situation but does occasionally arise. For example it is used by -default for ‘shell-command-on-region’: that command is used as an action -for region targets, and it prompts you for a shell command; you -typically do _not_ want the target, that is the contents of the region, -to be entered at that prompt! - - -File: docKihIip.info, Node: Running hooks before after or around an action, Next: Creating your own keymaps, Prev: Running some setup after injecting the target, Up: Advanced configuration - -3.5 Running hooks before, after or around an action -=================================================== - -Embark has three variables, ‘embark-pre-action-hooks’, -‘embark-post-action-hooks’ and ‘embark-around-action-hooks’, which are -alists associating commands to hooks that should run before or after or -as around advice for the command when used as an action. As with -‘embark-target-injection-hooks’, there are two special keys for the -alists: ‘t’ designates the default hook to run when no specific hook is -specified for a command; and the hook associated to ‘:always’ runs -regardless. - - The default values of those variables are fairly extensive, adding -creature comforts to make running actions a smooth experience. Embark -comes with several functions intended to be added to these hooks, and -used in the default values of ‘embark-pre-action-hooks’, -‘embark-post-action-hooks’ and ‘embark-around-action-hooks’. - - For pre-action hooks: - -‘embark--confirm’ - Prompt the user for confirmation before executing the action. This - is used be default for commands deemed “dangerous”, or, more - accurately, hard to undo, such as ‘delete-file’ and ‘kill-buffer’. - -‘embark--unmark-target’ - Unmark the active region. Use this for commands you want to act on - the region contents but without the region being active. The - default configuration uses this function as a pre-action hook for - ‘occur’ and ‘query-replace’, for example, so that you can use them - as actions with region targets to search the whole buffer for the - text contained in the region. Without this pre-action hook using - ‘occur’ as an action for a region target would be pointless: it - would search for the the region contents _in the region_, - (typically, due to the details of regexps) finding only one match! - -‘embark--beginning-of-target’ - Move to the beginning of the target (for targets that report - bounds). This is used by default for backward motion commands such - as ‘backward-sexp’, so that they don’t accidentally leave you on - the current target. - -‘embark--end-of-target’ - Move to the end of the target. This is used similarly to the - previous function, but also for commands that act on the last - s-expression like ‘eval-last-sexp’. This allow you to act on an - s-expression from anywhere inside it and still use ‘eval-last-sexp’ - as an action. - -‘embark--xref-push-markers’ - Push the current location on the xref marker stack. Use this for - commands that take you somewhere and for which you’d like to be - able to come back to where you were using ‘xref-pop-marker-stack’. - This is used by default for ‘find-library’. - - For post-action hooks: - -‘embark--restart’ - Restart the command currently prompting in the minibuffer, so that - the list of completion candidates is updated. This is useful as a - post action hook for commands that delete or rename a completion - candidate; for example the default value of - ‘embark-post-action-hooks’ uses it for ‘delete-file’, - ‘kill-buffer’, ‘rename-file’, ‘rename-buffer’, etc. - - For around-action hooks: - -‘embark--mark-target’ - Save existing mark and point location, mark the target and run the - action. Most targets at point outside the minibuffer report which - region of the buffer they correspond to (this is the information - used by ‘embark-highlight-indicator’ to know what portion of the - buffer to highlight); this function marks that region. It is - useful as an around action hook for commands that expect a region - to be marked, for example, it is used by default for - ‘indent-region’ so that it works on s-expression targets, or for - ‘fill-region’ so that it works on paragraph targets. - -‘embark--cd’ - Run the action with ‘default-directory’ set to the directory - associated to the current target. The target should be of type - ‘file’, ‘buffer’, ‘bookmark’ or ‘library’, and the associated - directory is what you’d expect in each case. - -‘embark--narrow-to-target’ - Run the action with buffer narrowed to current target. Use this as - an around hook to localize the effect of actions that don’t already - work on just the region. In the default configuration it is used - for ‘repunctuate-sentences’. - -‘embark--save-excursion’ - Run the action restoring point at the end. The current default - configuration doesn’t use this but it is available for users. - - -File: docKihIip.info, Node: Creating your own keymaps, Next: Defining actions for new categories of targets, Prev: Running hooks before after or around an action, Up: Advanced configuration - -3.6 Creating your own keymaps -============================= - -All internal keymaps are defined with the standard helper macro -‘defvar-keymap’. For example a simple version of the file action keymap -could be defined as follows: - - (defvar-keymap embark-file-map - :doc "Example keymap with a few file actions" - :parent embark-general-map - "d" #'delete-file - "r" #'rename-file - "c" #'copy-file) - - These action keymaps are perfectly normal Emacs keymaps. You may -want to inherit from the ‘embark-general-map’ if you want to access the -default Embark actions. Note that ‘embark-collect’ and ‘embark-export’ -are also made available via ‘embark-general-map’. - - -File: docKihIip.info, Node: Defining actions for new categories of targets, Prev: Creating your own keymaps, Up: Advanced configuration - -3.7 Defining actions for new categories of targets -================================================== - -It is easy to configure Embark to provide actions for new types of -targets, either in the minibuffer or outside it. I present below two -very detailed examples of how to do this. At several points I’ll -explain more than one way to proceed, typically with the easiest option -first. I include the alternative options since there will be similar -situations where the easiest option is not available. - -* Menu: - -* New minibuffer target example - tab-bar tabs:: -* New target example in regular buffers - short Wikipedia links:: - - -File: docKihIip.info, Node: New minibuffer target example - tab-bar tabs, Next: New target example in regular buffers - short Wikipedia links, Up: Defining actions for new categories of targets - -3.7.1 New minibuffer target example - tab-bar tabs --------------------------------------------------- - -As an example, take the new tab bars -(https://www.gnu.org/software/emacs/manual/html_node/emacs/Tab-Bars.html) -from Emacs 27. I’ll explain how to configure Embark to offer -tab-specific actions when you use the tab-bar-mode commands that mention -tabs by name. The configuration explained here is now built-in to -Embark (and Marginalia), but it’s still a good self-contained example. -In order to setup up tab actions you would need to: (1) make sure Embark -knows those commands deal with tabs, (2) define a keymap for tab actions -and configure Embark so it knows that’s the keymap you want. - - 1. Telling Embark about commands that prompt for tabs by name - - For step (1), it would be great if the ‘tab-bar-mode’ commands - reported the completion category ‘tab’ when asking you for a tab - with completion. (All built-in Emacs commands that prompt for file - names, for example, do have metadata indicating that they want a - ‘file’.) They do not, unfortunately, and I will describe a couple - of ways to deal with this. - - Maybe the easiest thing is to configure Marginalia - (https://github.com/minad/marginalia) to enhance those commands. - All of the ‘tab-bar-*-tab-by-name’ commands have the words “tab by - name” in the minibuffer prompt, so you can use: - - (add-to-list 'marginalia-prompt-categories '("tab by name" . tab)) - - That’s it! But in case you are ever in a situation where you don’t - already have commands that prompt for the targets you want, I’ll - describe how writing your own command with appropriate ‘category’ - metadata looks: - - (defun my-select-tab-by-name (tab) - (interactive - (list - (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) - (tab-bar-tabs)) - (user-error "No tabs found")))) - (completing-read - "Tabs: " - (lambda (string predicate action) - (if (eq action 'metadata) - '(metadata (category . tab)) - (complete-with-action - action tab-list string predicate))))))) - (tab-bar-select-tab-by-name tab)) - - As you can see, the built-in support for setting the category - meta-datum is not very easy to use or pretty to look at. To help - with this I recommend the ‘consult--read’ function from the - excellent Consult (https://github.com/minad/consult/) package. - With that function we can rewrite the command as follows: - - (defun my-select-tab-by-name (tab) - (interactive - (list - (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) - (tab-bar-tabs)) - (user-error "No tabs found")))) - (consult--read tab-list - :prompt "Tabs: " - :category 'tab)))) - (tab-bar-select-tab-by-name tab)) - - Much nicer! No matter how you define the ‘my-select-tab-by-name’ - command, the first approach with Marginalia and prompt detection - has the following advantages: you get the ‘tab’ category for all - the ‘tab-bar-*-bar-by-name’ commands at once, also, you enhance - built-in commands, instead of defining new ones. - - 2. Defining and configuring a keymap for tab actions - - Let’s say we want to offer select, rename and close actions for - tabs (in addition to Embark general actions, such as saving the tab - name to the kill-ring, which you get for free). Then this will do: - - (defvar-keymap embark-tab-actions - :doc "Keymap for actions for tab-bar tabs (when mentioned by name)." - :parent embark-general-map - "s" #'tab-bar-select-tab-by-name - "r" #'tab-bar-rename-tab-by-name - "k" #'tab-bar-close-tab-by-name) - - (add-to-list 'embark-keymap-alist '(tab . embark-tab-actions)) - - What if after using this for a while you feel closing the tab - without confirmation is dangerous? You have a couple of options: - - 1. You can keep using the ‘tab-bar-close-tab-by-name’ command, - but have Embark ask you for confirmation: - (push #'embark--confirm - (alist-get 'tab-bar-close-tab-by-name - embark-pre-action-hooks)) - - 2. You can write your own command that prompts for confirmation - and use that instead of ‘tab-bar-close-tab-by-name’ in the - above keymap: - (defun my-confirm-close-tab-by-name (tab) - (interactive "sTab to close: ") - (when (y-or-n-p (format "Close tab '%s'? " tab)) - (tab-bar-close-tab-by-name tab))) - - Notice that this is a command you can also use directly from - ‘M-x’ independently of Embark. Using it from ‘M-x’ leaves - something to be desired, though, since you don’t get - completion for the tab names. You can fix this if you wish as - described in the previous section. - - -File: docKihIip.info, Node: New target example in regular buffers - short Wikipedia links, Prev: New minibuffer target example - tab-bar tabs, Up: Defining actions for new categories of targets - -3.7.2 New target example in regular buffers - short Wikipedia links -------------------------------------------------------------------- - -Say you want to teach Embark to treat text of the form -‘wikipedia:Garry_Kasparov’ in any regular buffer as a link to Wikipedia, -with actions to open the Wikipedia page in eww or an external browser or -to save the URL of the page in the kill-ring. We can take advantage of -the actions that Embark has preconfigured for URLs, so all we need to do -is teach Embark that ‘wikipedia:Garry_Kasparov’ stands for the URL -‘https://en.wikipedia.org/wiki/Garry_Kasparov’. - - You can be as fancy as you want with the recognized syntax. Here, to -keep the example simple, I’ll assume the link matches the regexp -‘wikipedia:[[:alnum:]_]+’. We will write a function that looks for a -match surrounding point, and returns a dotted list of the form ‘'(url -URL-OF-THE-PAGE START . END)’ where ‘START’ and ‘END’ are the buffer -positions bounding the target, and are used by Embark to highlight it if -you have ‘embark-highlight-indicator’ included in the list -‘embark-indicators’. (There are a couple of other options for the -return value of a target finder: the bounding positions are optional and -a single target finder is allowed to return multiple targets; see the -documentation for ‘embark-target-finders’ for details.) - - (defun my-short-wikipedia-link () - "Target a link at point of the form wikipedia:Page_Name." - (save-excursion - (let* ((start (progn (skip-chars-backward "[:alnum:]_:") (point))) - (end (progn (skip-chars-forward "[:alnum:]_:") (point))) - (str (buffer-substring-no-properties start end))) - (save-match-data - (when (string-match "wikipedia:\\([[:alnum:]_]+\\)" str) - `(url - ,(format "https://en.wikipedia.org/wiki/%s" - (match-string 1 str)) - ,start . ,end)))))) - - (add-to-list 'embark-target-finders 'my-short-wikipedia-link) - - -File: docKihIip.info, Node: How does Embark call the actions?, Next: Embark Marginalia and Consult, Prev: Advanced configuration, Up: Top - -4 How does Embark call the actions? -*********************************** - -Embark actions are normal Emacs commands, that is, functions with an -interactive specification. In order to execute an action, Embark calls -the command with ‘call-interactively’, so the command reads user input -exactly as if run directly by the user. For example the command may -open a minibuffer and read a string (‘read-from-minibuffer’) or open a -completion interface (‘completing-read’). If this happens, Embark takes -the target string and inserts it automatically into the minibuffer, -simulating user input this way. After inserting the string, Embark -exits the minibuffer, submitting the input. (The immediate minibuffer -exit can be disabled for specific actions in order to allow editing the -input; this is done by adding the ‘embark--allow-edit’ function to the -appropriate entry of ‘embark-target-injection-hooks’). Embark inserts -the target string at the first minibuffer opened by the action command, -and if the command happens to prompt the user for input more than once, -the user still interacts with the second and further prompts in the -normal fashion. Note that if a command does not prompt the user for -input in the minibuffer, Embark still allows you to use it as an action, -but of course, never inserts the target anywhere. (There are plenty of -examples in the default configuration of commands that do not prompt the -user bound to keys in the action maps, most of the region actions, for -instance.) - - This is how Embark manages to reuse normal commands as actions. The -mechanism allows you to use as Embark actions commands that were not -written with Embark in mind (and indeed almost all actions that are -bound by default in Embark’s action keymaps are standard Emacs -commands). It also allows you to write new custom actions in such a way -that they are useful even without Embark. - - Staring from version 28.1, Emacs has a variable -‘y-or-n-p-use-read-key’, which when set to ‘t’ causes ‘y-or-n-p’ to use -‘read-key’ instead of ‘read-from-minibuffer’. Setting -‘y-or-n-p-use-read-key’ to ‘t’ is recommended for Embark users because -it keeps Embark from attempting to insert the target at a ‘y-or-n-p’ -prompt, which would almost never be sensible. Also consider this as a -warning to structure your own action commands so that if they use -‘y-or-n-p’, they do so only after the prompting for the target. - - Here is a simple example illustrating the various ways of reading -input from the user mentioned above. Bind the following commands to the -‘embark-symbol-map’ to be used as actions, then put the point on some -symbol and run them with ‘embark-act’: - - (defun example-action-command1 () - (interactive) - (message "The input was `%s'." (read-from-minibuffer "Input: "))) - - (defun example-action-command2 (arg input1 input2) - (interactive "P\nsInput 1: \nsInput 2: ") - (message "The first input %swas `%s', and the second was `%s'." - (if arg "truly " "") - input1 - input2)) - - (defun example-action-command3 () - (interactive) - (message "Your selection was `%s'." - (completing-read "Select: " '("E" "M" "B" "A" "R" "K")))) - - (defun example-action-command4 () - (interactive) - (message "I don't prompt you for input and thus ignore the target!")) - - (keymap-set embark-symbol-map "X 1" #'example-action-command1) - (keymap-set embark-symbol-map "X 2" #'example-action-command2) - (keymap-set embark-symbol-map "X 3" #'example-action-command3) - (keymap-set embark-symbol-map "X 4" #'example-action-command4) - - Also note that if you are using the key bindings to call actions, you -can pass prefix arguments to actions in the normal way. For example, -you can use ‘C-u X2’ with the above demonstration actions to make the -message printed by ‘example-action-command2’ more emphatic. This -ability to pass prefix arguments to actions is useful for some actions -in the default configuration, such as ‘embark-shell-command-on-buffer’. - -* Menu: - -* Non-interactive functions as actions:: - - -File: docKihIip.info, Node: Non-interactive functions as actions, Up: How does Embark call the actions? - -4.1 Non-interactive functions as actions -======================================== - -Alternatively, Embark does support one other type of action: a -non-interactive function of a single argument. The target is passed as -argument to the function. For example: - - (defun example-action-function (target) - (message "The target was `%s'." target)) - - (keymap-set embark-symbol-map "X 4" #'example-action-function) - - Note that normally binding non-interactive functions in a keymap is -useless, since when attempting to run them using the key binding you get -an error message similar to “Wrong type argument: commandp, -example-action-function”. In general it is more flexible to write any -new Embark actions as commands, that is, as interactive functions, -because that way you can also run them directly, without Embark. But -there are a couple of reasons to use non-interactive functions as -actions: - - 1. You may already have the function lying around, and it is - convenient to simply reuse it. - - 2. For command actions the targets can only be simple string, with no - text properties. For certain advanced uses you may want the action - to receive a string _with_ some text properties, or even a - non-string target. - - -File: docKihIip.info, Node: Embark Marginalia and Consult, Next: Related Packages, Prev: How does Embark call the actions?, Up: Top - -5 Embark, Marginalia and Consult -******************************** - -Embark cooperates well with the Marginalia -(https://github.com/minad/marginalia) and Consult -(https://github.com/minad/consult) packages. Neither of those packages -is a dependency of Embark, but both are highly recommended companions to -Embark, for opposite reasons: Marginalia greatly enhances Embark’s -usefulness, while Embark can help enhance Consult. - - In the remainder of this section I’ll explain what exactly Marginalia -does for Embark, and what Embark can do for Consult. - -* Menu: - -* Marginalia:: -* Consult:: - - -File: docKihIip.info, Node: Marginalia, Next: Consult, Up: Embark Marginalia and Consult - -5.1 Marginalia -============== - -Embark comes with actions for symbols (commands, functions, variables -with actions such as finding the definition, looking up the -documentation, evaluating, etc.) in the ‘embark-symbol-map’ keymap, and -for packages (actions like install, delete, browse url, etc.) in the -‘embark-package-keymap’. - - Unfortunately Embark does not automatically offers you these keymaps -when relevant, because many built-in Emacs commands don’t report -accurate category metadata. For example, a command like -‘describe-package’, which reads a package name from the minibuffer, does -not have metadata indicating this fact. - - In an earlier Embark version, there were functions to supply this -missing metadata, but they have been moved to Marginalia, which augments -many Emacs command to report accurate category metadata. Simply -activating ‘marginalia-mode’ allows Embark to offer you the package and -symbol actions when appropriate again. Candidate annotations in the -Embark collect buffer are also provided by the Marginalia package: - - • If you install Marginalia and activate ‘marginalia-mode’, Embark - Collect buffers will use the Marginalia annotations automatically. - - • If you don’t install Marginalia, you will see only the annotations - that come with Emacs (such as key bindings in ‘M-x’, or the unicode - characters in ‘C-x 8 RET’). - - -File: docKihIip.info, Node: Consult, Prev: Marginalia, Up: Embark Marginalia and Consult - -5.2 Consult -=========== - -The excellent Consult package provides many commands that use minibuffer -completion, via the ‘completing-read’ function; plenty of its commands -can be considered enhanced versions of built-in Emacs commands, and some -are completely new functionality. One common enhancement provided in -all commands for which it makes sense is preview functionality, for -example ‘consult-buffer’ will show you a quick preview of a buffer -before you actually switch to it. - - If you use both Consult and Embark you should install the -‘embark-consult’ package which provides integration between the two. It -provides exporters for several Consult commands and also tweaks the -behavior of many Consult commands when used as actions with ‘embark-act’ -in subtle ways that you may not even notice, but make for a smoother -experience. You need only install it to get these benefits: Embark will -automatically load it after Consult if found. - - The ‘embark-consult’ package provides the following exporters: - - • You can use ‘embark-export’ from ‘consult-line’, ‘consult-outline’, - or ‘consult-mark’ to obtain an ‘occur-mode’ buffer. As with the - built-in ‘occur’ command you use that buffer to jump to a match and - after that, you can then use ‘next-error’ and ‘previous-error’ to - navigate to other matches. You can also press ‘e’ to activate - ‘occur-edit-mode’ and edit the matches in place! - - • You can export from any of the Consult asynchronous search - commands, ‘consult-grep’, ‘consult-git-grep’, or ‘consult-ripgrep’ - to get a ‘grep-mode’ buffer. Here too you can use ‘next-error’ and - ‘previous-error’ to navigate among matches, and, if you install the - wgrep - (http://github.com/mhayashi1120/Emacs-wgrep/raw/master/wgrep.el) - package, you can use it to edit the matches in place. - - In both cases, pressing ‘g’ will rerun the Consult command you had -exported from and re-enter the input you had typed (which is similar to -reverting but a little more flexible). You can then proceed to -re-export if that’s what you want, but you can also edit the input -changing the search terms or simply cancel if you see you are done with -that search. - - The ‘embark-consult’ also contains some candidates collectors that -allow you to run ‘embark-live’ to get a live-updating table of contents -for your buffer: - - • ‘embark-consult-outline-candidates’ produces the outline headings - of the current buffer, using ‘consult-outline’. - • ‘embark-consult-imenu-candidates’ produces the imenu items of the - current buffer, using ‘consult-imenu’. - • ‘embark-consult-imenu-or-outline-candidates’ is a simple - combination of the two previous functions: it produces imenu items - in buffers deriving from ‘prog-mode’ and otherwise outline - headings. - - The way to configure ‘embark-live’ (or ‘embark-collect’ and -‘embark-export’ for that matter) to use one of these function is to add -it at the end of the ‘embark-candidate-collectors’ list. The -‘embark-consult’ package by default adds the last one, which seems to be -the most sensible default. - - Besides those exporters and candidate collectors, the -‘embark-consult’ package provides many subtle tweaks and small -integrations between Embark and Consult. Some examples are: - - • When used as actions, the asynchronous search commands will search - only the files associated to the targets: if the targets _are_ - files, it searches those files; for buffers it will search either - the associated file if there is one, else all files in the buffer’s - ‘default-directory’; for bookmarks it will search the file they - point to, same for Emacs Lisp libraries. This is particularly - powerful when using ‘embark-act-all’ to act on multiple files at - once, for example you can use ‘consult-find’ to search among file - _names_ and then ‘embark-act-all’ and ‘consult-grep’ to search - within the matching files. - - • For all other target types, those that do not have a sensible - notion of associated file, a Consult search command - (asynchronous or not) will search for the text of the target - but leave the minibuffer open so you can interact with the - Consult command. - - • ‘consult-imenu’ will search for the target and take you directly to - the location if it matches a unique imenu entry, otherwise it will - leave the minibuffer open so you can navigate among the matches. - - -File: docKihIip.info, Node: Related Packages, Next: Resources, Prev: Embark Marginalia and Consult, Up: Top - -6 Related Packages -****************** - -There are several packages that offer functionality similar to Embark’s. - -Acting on minibuffer completion candidates - The popular Ivy and Helm packages have support for acting on the - completion candidates of commands written using their APIs, and - there is an extensive ecosystem of packages meant for Helm and for - Ivy (the Ivy ones usually have “counsel” in the name) providing - commands and appropriate actions. -Acting on things at point - The built-in ‘context-menu-mode’ provides a mouse-driven - context-sensitive configurable menu. The ‘do-at-point’ package by - Philip Kaludercic (available on GNU ELPA), on the other hand is - keyboard-driven. -Collecting completion candidates into a buffer - The Ivy package has the command ‘ivy-occur’ which is similar to - ‘embark-collect’. As with Ivy actions, ‘ivy-occur’ only works for - commands written using the Ivy API. - - -File: docKihIip.info, Node: Resources, Next: Contributions, Prev: Related Packages, Up: Top - -7 Resources -*********** - -If you want to learn more about how others have used Embark here are -some links to read: - - • Fifteen ways to use Embark - (https://karthinks.com/software/fifteen-ways-to-use-embark/), a - blog post by Karthik Chikmagalur. - • Protesilaos Stavrou’s dotemacs (https://protesilaos.com/dotemacs/), - look for the section called “Extended minibuffer actions and more - (embark.el and prot-embark.el)” - - And some videos to watch: - - • Embark and my extras - (https://protesilaos.com/codelog/2021-01-09-emacs-embark-extras/) - by Protesilaos Stavrou. - • Embark – Key features and tweaks (https://youtu.be/qpoQiiinCtY) by - Raoul Comninos on the Emacs-Elements YouTube channel. - • Livestreamed: Adding an Embark context action to send a stream - message (https://youtu.be/WsxXr1ncukY) by Sacha Chua. - • System Crafters Live! - The Many Uses of Embark - (https://youtu.be/qk2Is_sC8Lk) by David Wilson. - • Marginalia, Consult and Embark by Mike Zamansky. - - -File: docKihIip.info, Node: Contributions, Next: Acknowledgments, Prev: Resources, Up: Top - -8 Contributions -*************** - -Contributions to Embark are very welcome. There is a wish list -(https://github.com/oantolin/embark/issues/95) for actions, target -finders, candidate collectors and exporters. For other ideas you have -for Embark, feel free to open an issue on the issue tracker -(https://github.com/oantolin/embark/issues). Any neat configuration -tricks you find might be a good fit for the wiki -(https://github.com/oantolin/embark/wiki). - - Code contributions are very welcome too, but since Embark is now on -GNU ELPA, copyright assignment to the FSF is required before you can -contribute code. - - -File: docKihIip.info, Node: Acknowledgments, Prev: Contributions, Up: Top - -9 Acknowledgments -***************** - -While I, Omar Antolín Camarena, have written most of the Embark code and -remain very stubborn about some of the design decisions, Embark has -received substantial help from a number of other people which this -document has neglected to mention for far too long. In particular, -Daniel Mendler has been absolutely invaluable, implementing several -important features, and providing a lot of useful advice. - - Code contributions: - - • Daniel Mendler (https://github.com/minad) - • Clemens Radermacher (https://github.com/clemera/) - • José Antonio Ortega Ruiz (https://codeberg.org/jao/) - • Itai Y. Efrat (https://github.com/iyefrat) - • a13 (https://github.com/a13) - • jakanakaevangeli (https://github.com/jakanakaevangeli) - • mihakam (https://github.com/mihakam) - • Brian Leung (https://github.com/leungbk) - • Karthik Chikmagalur (https://github.com/karthink) - • Roshan Shariff (https://github.com/roshanshariff) - • condy0919 (https://github.com/condy0919) - • Damien Cassou (https://github.com/DamienCassou) - • JimDBh (https://github.com/JimDBh) - - Advice and useful discussions: - - • Daniel Mendler (https://github.com/minad) - • Protesilaos Stavrou (https://gitlab.com/protesilaos/) - • Clemens Radermacher (https://github.com/clemera/) - • Howard Melman (https://github.com/hmelman/) - • Augusto Stoffel (https://github.com/astoff) - • Bruce d’Arcus (https://github.com/bdarcus) - • JD Smith (https://github.com/jdtsmith) - • Karthik Chikmagalur (https://github.com/karthink) - • jakanakaevangeli (https://github.com/jakanakaevangeli) - • Itai Y. Efrat (https://github.com/iyefrat) - • Mohsin Kaleem (https://github.com/mohkale) - - - -Tag Table: -Node: Top223 -Node: Overview1731 -Node: Acting on targets3040 -Node: The default action on a target8585 -Node: Working with sets of possible targets10495 -Node: Selecting some targets to make an ad hoc candidate set14774 -Node: embark-live a live-updating variant of embark-collect18230 -Node: Switching to a different command without losing what you've typed19928 -Node: Quick start22505 -Node: Advanced configuration27244 -Node: Showing information about available targets and actions27829 -Node: Selecting commands via completions instead of key bindings30441 -Node: Quitting the minibuffer after an action32048 -Node: Running some setup after injecting the target34504 -Node: Running hooks before after or around an action38122 -Node: Creating your own keymaps43001 -Node: Defining actions for new categories of targets43908 -Node: New minibuffer target example - tab-bar tabs44680 -Ref: Telling Embark about commands that prompt for tabs by name45586 -Ref: Defining and configuring a keymap for tab actions48449 -Node: New target example in regular buffers - short Wikipedia links50240 -Node: How does Embark call the actions?52503 -Node: Non-interactive functions as actions56846 -Node: Embark Marginalia and Consult58203 -Node: Marginalia58934 -Node: Consult60441 -Node: Related Packages65203 -Node: Resources66300 -Node: Contributions67434 -Node: Acknowledgments68147 - -End Tag Table - - -Local Variables: -coding: utf-8 -End: blob - cc41bf4ec54a89ac515dc36ec344f94bc0d6b965 (mode 644) blob + /dev/null --- elpa/embark-1.0/embark.texi +++ /dev/null @@ -1,1479 +0,0 @@ -\input texinfo @c -*- texinfo -*- -@c %**start of header -@setfilename embark.info -@settitle Embark: Emacs Mini-Buffer Actions Rooted in Keymaps -@documentencoding UTF-8 -@documentlanguage en -@c %**end of header - -@dircategory Emacs misc features -@direntry -* Embark: (embark). Emacs Mini-Buffer Actions Rooted in Keymaps. -@end direntry - -@finalout -@titlepage -@title Embark: Emacs Mini-Buffer Actions Rooted in Keymaps -@author Omar Antolín Camarena -@end titlepage - -@contents - -@ifnottex -@node Top -@top Embark: Emacs Mini-Buffer Actions Rooted in Keymaps -@end ifnottex - -@menu -* Overview:: -* Quick start:: -* Advanced configuration:: -* How does Embark call the actions?:: -* Embark, Marginalia and Consult: Embark Marginalia and Consult. -* Related Packages:: -* Resources:: -* Contributions:: -* Acknowledgments:: - -@detailmenu ---- The Detailed Node Listing --- - -Overview - -* Acting on targets:: -* The default action on a target:: -* Working with sets of possible targets:: -* Switching to a different command without losing what you've typed:: - -Working with sets of possible targets - -* Selecting some targets to make an ad hoc candidate set:: -* @samp{embark-live} a live-updating variant of @samp{embark-collect}:: - -Advanced configuration - -* Showing information about available targets and actions:: -* Selecting commands via completions instead of key bindings:: -* Quitting the minibuffer after an action:: -* Running some setup after injecting the target:: -* Running hooks before, after or around an action: Running hooks before after or around an action. -* Creating your own keymaps:: -* Defining actions for new categories of targets:: - -Defining actions for new categories of targets - -* New minibuffer target example - tab-bar tabs:: -* New target example in regular buffers - short Wikipedia links:: - -How does Embark call the actions? - -* Non-interactive functions as actions:: - -Embark, Marginalia and Consult - -* Marginalia:: -* Consult:: - -@end detailmenu -@end menu - -@node Overview -@chapter Overview - -Embark makes it easy to choose a command to run based on what is near -point, both during a minibuffer completion session (in a way familiar -to Helm or Counsel users) and in normal buffers. Bind the command -@samp{embark-act} to a key and it acts like prefix-key for a keymap of -@emph{actions} (commands) relevant to the @emph{target} around point. With point on -an URL in a buffer you can open the URL in a browser or eww or -download the file it points to. If while switching buffers you spot an -old one, you can kill it right there and continue to select another. -Embark comes preconfigured with over a hundred actions for common -types of targets such as files, buffers, identifiers, s-expressions, -sentences; and it is easy to add more actions and more target types. -Embark can also collect all the candidates in a minibuffer to an -occur-like buffer or export them to a buffer in a major-mode specific -to the type of candidates, such as dired for a set of files, ibuffer -for a set of buffers, or customize for a set of variables. - -@menu -* Acting on targets:: -* The default action on a target:: -* Working with sets of possible targets:: -* Switching to a different command without losing what you've typed:: -@end menu - -@node Acting on targets -@section Acting on targets - -You can think of @samp{embark-act} as a keyboard-based version of a -right-click contextual menu. The @samp{embark-act} command (which you should -bind to a convenient key), acts as a prefix for a keymap offering you -relevant @emph{actions} to use on a @emph{target} determined by the context: - -@itemize -@item -In the minibuffer, the target is the current top completion -candidate. -@item -In the @samp{*Completions*} buffer the target is the completion at point. -@item -In a regular buffer, the target is the region if active, or else the -file, symbol, URL, s-expression or defun at point. -@end itemize - -Multiple targets can be present at the same location and you can cycle -between them by repeating the @samp{embark-act} key binding. The type of -actions offered depend on the type of the target. Here is a sample of -a few of the actions offered in the default configuration: - -@itemize -@item -For files you get offered actions like deleting, copying, -renaming, visiting in another window, running a shell command on the -file, etc. -@item -For buffers the actions include switching to or killing the buffer. -@item -For package names the actions include installing, removing or -visiting the homepage. -@item -For Emacs Lisp symbols the actions include finding the definition, -looking up documentation, evaluating (which for a variable -immediately shows the value, but for a function lets you pass it -some arguments first). There are some actions specific to variables, -such as setting the value directly or though the customize system, -and some actions specific to commands, such as binding it to a key. -@end itemize - -By default when you use @samp{embark-act} if you don't immediately select an -action, after a short delay Embark will pop up a buffer showing a list -of actions and their corresponding key bindings. If you are using -@samp{embark-act} outside the minibuffer, Embark will also highlight the -current target. These behaviors are configurable via the variable -@samp{embark-indicators}. Instead of selecting an action via its key binding, -you can select it by name with completion by typing @samp{C-h} after -@samp{embark-act}. - -Everything is easily configurable: determining the current target, -classifying it, and deciding which actions are offered for each type -in the classification. The above introduction just mentions part of -the default configuration. - -Configuring which actions are offered for a type is particularly easy -and requires no programming: the variable @samp{embark-keymap-alist} -associates target types with variables containing keymaps, and those -keymaps containing bindings for the actions. (To examine the available -categories and their associated keymaps, you can use @samp{C-h v -embark-keymap-alist} or customize that variable.) For example, in the -default configuration the type @samp{file} is associated with the symbol -@samp{embark-file-map}. That symbol names a keymap with single-letter key -bindings for common Emacs file commands, for instance @samp{c} is bound to -@samp{copy-file}. This means that if you are in the minibuffer after running -a command that prompts for a file, such as @samp{find-file} or @samp{rename-file}, -you can copy a file by running @samp{embark-act} and then pressing @samp{c}. - -These action keymaps are very convenient but not strictly necessary -when using @samp{embark-act}: you can use any command that reads from the -minibuffer as an action and the target of the action will be inserted -at the first minibuffer prompt. After running @samp{embark-act} all of your -key bindings and even @samp{execute-extended-command} can be used to run a -command. For example, if you want to replace all occurrences of the -symbol at point, just use @samp{M-%} as the action, there is no need to bind -@samp{query-replace} in one of Embark's keymaps. Also, those action keymaps -are normal Emacs keymaps and you should feel free to bind in them -whatever commands you find useful as actions and want to be available -through convenient bindings. - -The actions in @samp{embark-general-map} are available no matter what type -of completion you are in the middle of. By default this includes -bindings to save the current candidate in the kill ring and to insert -the current candidate in the previously selected buffer (the buffer -that was current when you executed a command that opened up the -minibuffer). - -Emacs's minibuffer completion system includes metadata indicating the -@emph{category} of what is being completed. For example, @samp{find-file}'s -metadata indicates a category of @samp{file} and @samp{switch-to-buffer}'s metadata -indicates a category of @samp{buffer}. Embark has the related notion of the -@emph{type} of a target for actions, and by default when category metadata -is present it is taken to be the type of minibuffer completion -candidates when used as targets. Emacs commands often do not set -useful category metadata so the @uref{https://github.com/minad/marginalia, Marginalia} package, which supplies -this missing metadata, is highly recommended for use with Embark. - -Embark's default configuration has actions for the following target -types: files, buffers, symbols, packages, URLs, bookmarks, and as a -somewhat special case, actions for when the region is active. You can -read about the @uref{https://github.com/oantolin/embark/wiki/Default-Actions, default actions and their key bindings} on the GitHub -project wiki. - -@node The default action on a target -@section The default action on a target - -Embark has a notion of default action for a target: - -@itemize -@item -If the target is a minibuffer completion candidate, then the default -action is whatever command opened the minibuffer in the first place. -For example if you run @samp{kill-buffer}, then the default action will be -to kill buffers. -@item -If the target comes from a regular buffer (i.e., not a minibuffer), -then the default action is whatever is bound to @samp{RET} in the keymap of -actions for that type of target. For example, in Embark's default -configuration for a URL found at point the default action is -@samp{browse-url}, because @samp{RET} is bound to @samp{browse-url} in the @samp{embark-url-map} -keymap. -@end itemize - -To run the default action you can press @samp{RET} after running @samp{embark-act}. -Note that if there are several different targets at a given location, -each has its own default action, so first cycle to the target you want -and then press @samp{RET} to run the corresponding default action. - -There is also @samp{embark-dwim} which runs the default action for the first -target found. It's pretty handy in non-minibuffer buffers: with -Embark's default configuration it will: - -@itemize -@item -Open the file at point. -@item -Open the URL at point in a web browser (using the @samp{browse-url} -command). -@item -Compose a new email to the email address at point. -@item -In an Emacs Lisp buffer, if point is on an opening parenthesis or -right after a closing one, it will evaluate the corresponding -expression. -@item -Go to the definition of an Emacs Lisp function, variable or macro at -point. -@item -Find the file corresponding to an Emacs Lisp library at point. -@end itemize - -@node Working with sets of possible targets -@section Working with sets of possible targets - -Besides acting individually on targets, Embark lets you work -collectively on a set of target @emph{candidates}. For example, while you are -in the minibuffer the candidates are simply the possible completions -of your input. Embark provides three main commands to work on candidate -sets: - -@itemize -@item -The @samp{embark-act-all} command runs the same action on each of the -current candidates. It is just like using @samp{embark-act} on each -candidate in turn. (Because you can easily act on many more -candidates than you meant to, by default Embark asks you to confirm -uses of @samp{embark-act-all}; you can turn this off by setting the user -option @samp{embark-confirm-act-all} to @samp{nil}.) - -@item -The @samp{embark-collect} command produces a buffer listing all the current -candidates, for you to peruse and run actions on at your leisure. -The candidates are displayed as a list showing additional -annotations. If any of the candidates contain newlines, then -horizontal lines are used to separate candidates. - -The Embark Collect buffer is somewhat ``dired-like'': you can select -and deselect candidates through @samp{embark-select} (available as an -action in @samp{embark-act}, bound to @samp{SPC}; but you could also give it a -global key binding). In an Embark Collect buffer @samp{embark-act} is bound -to @samp{a} and @samp{embark-act-all} is bound to @samp{A}; @samp{embark-act-all} will act on -all currently marked candidates if there any, and will act on all -candidates if none are marked. In particular, this means that @samp{a SPC} -will toggle whether the candidate at point is selected, and @samp{A SPC} -will select all candidates if none are selected, or deselect all -selected candidates if there are some. - -@item -The @samp{embark-export} command tries to open a buffer in an appropriate -major mode for the set of candidates. If the candidates are files -export produces a Dired buffer; if they are buffers, you get an -Ibuffer buffer; and if they are packages you get a buffer in -package menu mode. - -If you use the grepping commands from the @uref{https://github.com/minad/consult/, Consult} package, -@samp{consult-grep}, @samp{consult-git-grep} or @samp{consult-ripgrep}, then you should -install the @samp{embark-consult} package, which adds support for exporting a -list of grep results to an honest grep-mode buffer, on which you can -even use @uref{https://github.com/mhayashi1120/Emacs-wgrep, wgrep} if you wish. -@end itemize - -When in doubt choosing between exporting and collecting, a good rule -of thumb is to always prefer @samp{embark-export} since when an exporter to a -special major mode is available for a given type of target, it will be -more featureful than an Embark collect buffer, and if no such exporter -is configured the @samp{embark-export} command falls back to the generic -@samp{embark-collect}. - -These commands are always available as ``actions'' (although they do not -act on just the current target but on all candidates) for @samp{embark-act} -and are bound to @samp{A}, @samp{S} (for ``snapshot''), and @samp{E}, respectively, in -@samp{embark-general-map}. This means that you do not have to bind your own -key bindings for these (although you can, of course!), just a key -binding for @samp{embark-act}. - -In Embark Collect or Embark Export buffers that were obtained by -running @samp{embark-collect} or @samp{embark-export} from within a minibuffer -completion session, @samp{g} is bound to a command that restarts the -completion session, that is, the command that opened the minibuffer is -run again and the minibuffer contents restored. You can then interact -normally with the command, perhaps editing the minibuffer contents, -and, if you wish, you can rerun @samp{embark-collect} or @samp{embark-export} to get -an updated buffer. - -@menu -* Selecting some targets to make an ad hoc candidate set:: -* @samp{embark-live} a live-updating variant of @samp{embark-collect}:: -@end menu - -@node Selecting some targets to make an ad hoc candidate set -@subsection Selecting some targets to make an ad hoc candidate set - -The commands for working with sets of candidates just described, -namely @samp{embark-act-all}, @samp{embark-export} and @samp{embark-collect} by default -work with all candidates defined in the current context. For example, -in the minibuffer they operate on all currently completion candidates, -or in a dired buffer they work on all marked files (or all files if -none are marked). Embark also has a notion of @emph{selection}, where you can -accumulate an ad hoc list of targets for these commands to work on. - -The selection is controlled by using the @samp{embark-select} action, bound -to @samp{SPC} in @samp{embark-general-map} so that it is always available (you can -also give @samp{embark-select} a global key binding if you wish; when called -directly, not as an action for @samp{embark-act}, it will select the first -target at point). Calling this action on a target toggles its -membership in the current buffer's Embark selection; that is, it adds -it to selection if not selected and removes it from the selection if -it was selected. Whenever the selection for a buffer is non-empty, the -commands @samp{embark-act-all}, @samp{embark-export} and @samp{embark-collect} will act on -the selection. - -To deselect all selected targets, you can use the @samp{embark-select} action -through @samp{embark-act-all}, since this will run @samp{embark-select} on each -member of the current selection. Similarly if no targets are selected -and you are in a minibuffer completion session, running @samp{embark-select} -from @samp{embark-act-all} will select all the current completion candidates. - -By default, whenever some targets are selected in the current buffer, -a count of selected targets appears in the mode line. This can be -turned off or customized through the @samp{embark-selection-indicator} user -option. - -The selection functionality is supported in every buffer: - -@itemize -@item -In the minibuffer this gives a convenient way to act on several -completion candidates that don't follow any simple pattern: just go -through the completions selecting the ones you want, then use -@samp{embark-act-all}. For example, you could attach several files at once -to an email. -@item -For Embark Collect buffers this functionality enables a dired-like -workflow, in which you mark various candidates and apply an action -to all at once. (It supersedes a previous ad hoc dired-like -interface that was implemented only in Embark Collect buffers, with -a slightly different interface.) -@item -In a eww buffer you could use this to select various links you wish -to follow up on, and then collect them into a buffer. Similarly, -while reading Emacs's info manual you could select some symbols you -want to read more about and export them to an @samp{apropos-mode} buffer. -@item -You can use selections in regular text or programming buffers to do -complex editing operations. For example, if you have three -paragraphs scattered over a file and you want to bring them -together, you can select each one, insert them all somewhere and -finally delete all of them (from their original locations). -@end itemize - -@node @samp{embark-live} a live-updating variant of @samp{embark-collect} -@subsection @samp{embark-live} a live-updating variant of @samp{embark-collect} - -Finally, there is also an @samp{embark-live} variant of the @samp{embark-collect} -command which automatically updates the collection after each change -in the source buffer. Users of a completion UI that automatically -updates and displays the candidate list (such as Vertico, Icomplete, -Fido-mode, or MCT) will probably not want to use -@samp{embark-live} from the minibuffer as they will then have two live -updating displays of the completion candidates! - -A more likely use of @samp{embark-live} is to be called from a regular buffer -to display a sort of live updating ``table of contents'' for the buffer. -This depends on having appropriate candidate collectors configured in -@samp{embark-candidate-collectors}. There are not many in Embark's default -configuration, but you can try this experiment: open a dired buffer in -a directory that has very many files, mark a few, and run @samp{embark-live}. -You'll get an Embark Collect buffer containing only the marked files, -which updates as you mark or unmark files in dired. To make -@samp{embark-live} genuinely useful other candidate collectors are required. -The @samp{embark-consult} package (documented near the end of this manual) -contains a few: one for imenu items and one for outline headings as -used by @samp{outline-minor-mode}. Those collectors really do give -@samp{embark-live} a table-of-contents feel. - -@node Switching to a different command without losing what you've typed -@section Switching to a different command without losing what you've typed - -Embark also has the @samp{embark-become} command which is useful for when -you run a command, start typing at the minibuffer and realize you -meant a different command. The most common case for me is that I run -@samp{switch-to-buffer}, start typing a buffer name and realize I haven't -opened the file I had in mind yet! I'll use this situation as a -running example to illustrate @samp{embark-become}. When this happens I can, -of course, press @samp{C-g} and then run @samp{find-file} and open the file, but -this requires retyping the portion of the file name you already -typed. This process can be streamlined with @samp{embark-become}: while still -in the @samp{switch-to-buffer} you can run @samp{embark-become} and effectively -make the @samp{switch-to-buffer} command become @samp{find-file} for this run. - -You can bind @samp{embark-become} to a key in @samp{minibuffer-local-map}, but it is -also available as an action under the letter @samp{B} (uppercase), so you -don't need a binding if you already have one for @samp{embark-act}. So, -assuming I have @samp{embark-act} bound to, say, @samp{C-.}, once I realize I -haven't open the file I can type @samp{C-. B C-x C-f} to have -@samp{switch-to-buffer} become @samp{find-file} without losing what I have already -typed in the minibuffer. - -But for even more convenience, @samp{embark-become} offers shorter key -bindings for commands you are likely to want the current command to -become. When you use @samp{embark-become} it looks for the current command in -all keymaps named in the list @samp{embark-become-keymaps} and then activates -all keymaps that contain it. For example, the default value of -@samp{embark-become-keymaps} contains a keymap @samp{embark-become-file+buffer-map} -with bindings for several commands related to files and buffers, in -particular, it binds @samp{switch-to-buffer} to @samp{b} and @samp{find-file} to @samp{f}. So when -I accidentally try to switch to a buffer for a file I haven't opened -yet, @samp{embark-become} finds that the command I ran, @samp{switch-to-buffer}, is -in the keymap @samp{embark-become-file+buffer-map}, so it activates that -keymap (and any others that also contain a binding for -@samp{switch-to-buffer}). The end result is that I can type @samp{C-. B f} to -switch to @samp{find-file}. - -@node Quick start -@chapter Quick start - -The easiest way to install Embark is from GNU ELPA, just run @samp{M-x -package-install RET embark RET}. (It is also available on MELPA@.) It is -highly recommended to also install @uref{https://github.com/minad/marginalia, Marginalia} (also available on GNU -ELPA), so that Embark can offer you preconfigured actions in more -contexts. For @samp{use-package} users, the following is a very reasonable -starting configuration: - -@lisp -(use-package marginalia - :ensure t - :config - (marginalia-mode)) - -(use-package embark - :ensure t - - :bind - (("C-." . embark-act) ;; pick some comfortable binding - ("C-;" . embark-dwim) ;; good alternative: M-. - ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' - - :init - - ;; Optionally replace the key help with a completing-read interface - (setq prefix-help-command #'embark-prefix-help-command) - - ;; Show the Embark target at point via Eldoc. You may adjust the Eldoc - ;; strategy, if you want to see the documentation from multiple providers. - (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target) - ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) - - :config - - ;; Hide the mode line of the Embark live/completions buffers - (add-to-list 'display-buffer-alist - '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" - nil - (window-parameters (mode-line-format . none))))) - -;; Consult users will also want the embark-consult package. -(use-package embark-consult - :ensure t ; only need to install it, embark loads it after consult if found - :hook - (embark-collect-mode . consult-preview-at-point-mode)) -@end lisp - -About the suggested key bindings for @samp{embark-act} and @samp{embark-dwim}: -@itemize -@item -Those key bindings are unlikely to work in the terminal, but -terminal users are probably well aware of this and will know to -select different bindings. -@item -The suggested @samp{C-.} binding is used by default in (at least some -installations of) GNOME to input emojis, and Emacs doesn't even get -a chance to respond to the binding. You can select a different key -binding for @samp{embark-act} or use @samp{ibus-setup} to change the shortcut for -emoji insertion (Emacs 29 will likely use @samp{C-x 8 e e}, in case you -want to set the same one system-wide). -@item -The suggested alternative of @samp{M-.} for @samp{embark-dwim} is bound by default -to @samp{xref-find-definitions}. That is a very useful command but -overwriting it with @samp{embark-dwim} is sensible since in Embark's -default configuration, @samp{embark-dwim} will also find the definition of -the identifier at point. (Note that @samp{xref-find-definitions} with a -prefix argument prompts you for an identifier, @samp{embark-dwim} does not -cover this case). -@end itemize - -Other Embark commands such as @samp{embark-act-all}, @samp{embark-become}, -@samp{embark-collect}, and @samp{embark-export} can be run through @samp{embark-act} as -actions bound to @samp{A}, @samp{B}, @samp{S} (for ``snapshot''), and @samp{E} respectively, and -thus don't really need a dedicated key binding, but feel free to bind -them directly if you so wish. If you do choose to bind them directly, -you'll probably want to bind them in @samp{minibuffer-local-map}, since they -are most useful in the minibuffer (in fact, @samp{embark-become} only works -in the minibuffer). - -The command @samp{embark-dwim} executes the default action at point. Another good -keybinding for @samp{embark-dwim} is @samp{M-.} since @samp{embark-dwim} acts like -@samp{xref-find-definitions} on the symbol at point. @samp{C-.} can be seen as a -right-click context menu at point and @samp{M-.} acts like left-click. The -keybindings are mnemonic, both act at the point (@samp{.}). - -Embark needs to know what your minibuffer completion system considers -to be the list of candidates and which one is the current candidate. -Embark works out of the box if you use Emacs's default tab completion, -the built-in @samp{icomplete-mode} or @samp{fido-mode}, or the third-party packages -@uref{https://github.com/minad/vertico, Vertico} or @uref{https://github.com/abo-abo/swiper, Ivy}. - -If you are a @uref{https://emacs-helm.github.io/helm/, Helm} or @uref{https://github.com/abo-abo/swiper, Ivy} user you are unlikely to want Embark since -those packages include comprehensive functionality for acting on -minibuffer completion candidates. (Embark does come with Ivy -integration despite this.) - -@node Advanced configuration -@chapter Advanced configuration - -@menu -* Showing information about available targets and actions:: -* Selecting commands via completions instead of key bindings:: -* Quitting the minibuffer after an action:: -* Running some setup after injecting the target:: -* Running hooks before, after or around an action: Running hooks before after or around an action. -* Creating your own keymaps:: -* Defining actions for new categories of targets:: -@end menu - -@node Showing information about available targets and actions -@section Showing information about available targets and actions - -By default, if you run @samp{embark-act} and do not immediately select an -action, after a short delay Embark will pop up a buffer called @samp{*Embark -Actions*} containing a list of available actions with their key -bindings. You can scroll that buffer with the mouse of with the usual -commands @samp{scroll-other-window} and @samp{scroll-other-window-down} (bound by -default to @samp{C-M-v} and @samp{C-M-S-v}). - -That functionality is provided by the @samp{embark-mixed-indicator}, but -Embark has other indicators that can provide information about the -target and its type, what other targets you can cycle to, and which -actions have key bindings in the action map for the current type of -target. Any number of indicators can be active at once and the user -option @samp{embark-indicators} should be set to a list of the desired -indicators. - -Embark comes with the following indicators: - -@itemize -@item -@samp{embark-minimal-indicator}: shows a messages in the echo area or -minibuffer prompt showing the current target and the types of all -targets starting with the current one; this one is on by default. - -@item -@samp{embark-highlight-indicator}: highlights the target at point; -also on by default. - -@item -@samp{embark-verbose-indicator}: displays a table of actions and their key -bindings in a buffer; this is not on by default, in favor of the -mixed indicator described next. - -@item -@samp{embark-mixed-indicator}: starts out by behaving as the minimal -indicator but after a short delay acts as the verbose indicator; -this is on by default. - -@item -@samp{embark-isearch-highlight-indicator}: this only does something when -the current target is the symbol at point, in which case it -lazily highlights all occurrences of that symbol in the current -buffer, like isearch; also on by default. -@end itemize - -Users of the popular @uref{https://github.com/justbur/emacs-which-key, which-key} package may prefer to use the -@samp{embark-which-key-indicator} from the @uref{https://github.com/oantolin/embark/wiki/Additional-Configuration#use-which-key-like-a-key-menu-prompt, Embark wiki}. Just copy its -definition from the wiki into your configuration and customize the -@samp{embark-indicators} user option to exclude the mixed and verbose -indicators and to include @samp{embark-which-key-indicator}. - -@node Selecting commands via completions instead of key bindings -@section Selecting commands via completions instead of key bindings - -As an alternative to reading the list of actions in the verbose or -mixed indicators (see the previous section for a description of -these), you can press the @samp{embark-help-key}, which is @samp{C-h} by default -(but you may prefer @samp{?} to free up @samp{C-h} for use as a prefix) after -running @samp{embark-act}. Pressing the help key will prompt you for the name -of an action with completion (but feel free to enter a command that is -not among the offered candidates!), and will also remind you of the -key bindings. You can press @samp{embark-keymap-prompter-key}, which is @samp{@@} by -default, at the prompt and then one of the key bindings to enter the -name of the corresponding action. - -You may think that with the @samp{*Embark Actions*} buffer popping up to -remind you of the key bindings you'd never want to use completion to -select an action by name, but personally I find that typing a small -portion of the action name to narrow down the list of candidates feels -significantly faster than visually scanning the entire list of actions. - -If you find you prefer entering actions that way, you can configure -embark to always prompt you for actions by setting the variable -@samp{embark-prompter} to @samp{embark-completing-read-prompter}. - -@node Quitting the minibuffer after an action -@section Quitting the minibuffer after an action - -By default, if you call @samp{embark-act} from the minibuffer it quits the -minibuffer after performing the action. You can change this by setting -the user option @samp{embark-quit-after-action} to @samp{nil}. Having @samp{embark-act} @emph{not} -quit the minibuffer can be useful to turn commands into little ``thing -managers''. For example, you can use @samp{find-file} as a little file manager -or @samp{describe-package} as a little package manager: you can run those -commands, perform a series of actions, and then quit the command. - -If you want to control the quitting behavior in a fine-grained manner -depending on the action, you can set @samp{embark-quit-after-action} to an -alist, associating commands to either @samp{t} for quitting or @samp{nil} for not -quitting. When using an alist, you can use the special key @samp{t} to -specify the default behavior. For example, to specify that by default -actions should not quit the minibuffer but that using @samp{kill-buffer} as -an action should quit, you can use the following configuration: - -@lisp -(setq embark-quit-after-action '((kill-buffer . t) (t . nil))) -@end lisp - -The variable @samp{embark-quit-after-action} only specifies a default, that -is, it only controls whether or not @samp{embark-act} quits the minibuffer -when you call it without a prefix argument, and you can select the -opposite behavior to what the variable says by calling @samp{embark-act} with -@samp{C-u}. Also note that both the variable @samp{embark-quit-after-action} and @samp{C-u} -have no effect when you call @samp{embark-act} outside the minibuffer. - -If you find yourself using the quitting and non-quitting variants of -@samp{embark-act} about equally often, independently of the action, you may -prefer to simply have separate commands for them instead of a single -command that you call with @samp{C-u} half the time. You could, for example, -keep the default exiting behavior of @samp{embark-act} and define a -non-quitting version as follows: - -@lisp -(defun embark-act-noquit () - "Run action but don't quit the minibuffer afterwards." - (interactive) - (let ((embark-quit-after-action nil)) - (embark-act))) -@end lisp - -@node Running some setup after injecting the target -@section Running some setup after injecting the target - -You can customize what happens after the target is inserted at the -minibuffer prompt of an action. There are -@samp{embark-target-injection-hooks}, that are run by default after injecting -the target into the minibuffer. The variable -@samp{embark-target-injection-hooks} is an alist associating commands to -their setup hooks. There are two special keys: if no setup hook is -specified for a given action, the hook associated to @samp{t} is run; and the -hook associated to @samp{:always} is run regardless of the action. (This -variable used to have the less explicit name of -@samp{embark-setup-action-hooks}, so please update your configuration.) - -For example, consider using @samp{shell-command} as an action during file -completion. It would be useful to insert a space before the target -file name and to leave the point at the beginning, so you can -immediately type the shell command to run on that file. That's why in -Embark's default configuration there is an entry in -@samp{embark-target-injection-hooks} associating @samp{shell-command} to a hook that -includes @samp{embark--shell-prep}, a simple helper function that quotes all -the spaces in the file name, inserts an extra space at the beginning -of the line and leaves point to the left of it. - -Now, the preparation that @samp{embark--shell-prep} does would be useless if -Embark did what it normally does after it inserts the target of the -action at the minibuffer prompt, which is to ``press @samp{RET}'' for you, -accepting the target as is; if Embark did that for @samp{shell-command} you -wouldn't get a chance to type in the command to execute! That is why -in Embark's default configuration the entry for @samp{shell-command} in -@samp{embark-target-injection-hooks} also contains the function -@samp{embark--allow-edit}. - -Embark used to have a dedicated variable @samp{embark-allow-edit-actions} to -which you could add commands for which Embark should forgo pressing -@samp{RET} for you after inserting the target. Since its effect can also be -achieved via the general @samp{embark-target-injection-hooks} mechanism, that -variable has been removed to simplify Embark. Be sure to update your -configuration; if you had something like: - -@lisp -(add-to-list 'embark-allow-edit-actions 'my-command) -@end lisp - -you should replace it with: - -@lisp -(push 'embark--allow-edit - (alist-get 'my-command embark-target-injection-hooks)) -@end lisp - - -Also note that while you could abuse @samp{embark--allow-edit} so that you -have to confirm ``dangerous'' actions such as @samp{delete-file}, it is better -to implement confirmation by adding the @samp{embark--confirm} function to -the appropriate entry of a different hook alist, namely, -@samp{embark-pre-action-hooks}. - -Besides @samp{embark--allow-edit}, Embark comes with another function that is -of general utility in action setup hooks: @samp{embark--ignore-target}. Use -it for commands that do prompt you in the minibuffer but for which -inserting the target would be inappropriate. This is not a common -situation but does occasionally arise. For example it is used by -default for @samp{shell-command-on-region}: that command is used as an action -for region targets, and it prompts you for a shell command; you -typically do @emph{not} want the target, that is the contents of the region, -to be entered at that prompt! - -@node Running hooks before after or around an action -@section Running hooks before, after or around an action - -Embark has three variables, @samp{embark-pre-action-hooks}, -@samp{embark-post-action-hooks} and @samp{embark-around-action-hooks}, which are -alists associating commands to hooks that should run before or after -or as around advice for the command when used as an action. As with -@samp{embark-target-injection-hooks}, there are two special keys for the -alists: @samp{t} designates the default hook to run when no specific hook is -specified for a command; and the hook associated to @samp{:always} runs -regardless. - -The default values of those variables are fairly extensive, adding -creature comforts to make running actions a smooth experience. Embark -comes with several functions intended to be added to these hooks, and -used in the default values of @samp{embark-pre-action-hooks}, -@samp{embark-post-action-hooks} and @samp{embark-around-action-hooks}. - -For pre-action hooks: - -@table @asis -@item @samp{embark--confirm} -Prompt the user for confirmation before executing -the action. This is used be default for commands deemed ``dangerous'', -or, more accurately, hard to undo, such as @samp{delete-file} and -@samp{kill-buffer}. - -@item @samp{embark--unmark-target} -Unmark the active region. Use this for -commands you want to act on the region contents but without the -region being active. The default configuration uses this function as -a pre-action hook for @samp{occur} and @samp{query-replace}, for example, so that -you can use them as actions with region targets to search the whole -buffer for the text contained in the region. Without this pre-action -hook using @samp{occur} as an action for a region target would be -pointless: it would search for the the region contents @emph{in the -region}, (typically, due to the details of regexps) finding only one -match! - -@item @samp{embark--beginning-of-target} -Move to the beginning of the target -(for targets that report bounds). This is used by default for -backward motion commands such as @samp{backward-sexp}, so that they don't -accidentally leave you on the current target. - -@item @samp{embark--end-of-target} -Move to the end of the target. This is used -similarly to the previous function, but also for commands that act -on the last s-expression like @samp{eval-last-sexp}. This allow you to act -on an s-expression from anywhere inside it and still use -@samp{eval-last-sexp} as an action. - -@item @samp{embark--xref-push-markers} -Push the current location on the xref -marker stack. Use this for commands that take you somewhere and for -which you'd like to be able to come back to where you were using -@samp{xref-pop-marker-stack}. This is used by default for @samp{find-library}. -@end table - -For post-action hooks: - -@table @asis -@item @samp{embark--restart} -Restart the command currently prompting in the -minibuffer, so that the list of completion candidates is updated. -This is useful as a post action hook for commands that delete or -rename a completion candidate; for example the default value of -@samp{embark-post-action-hooks} uses it for @samp{delete-file}, @samp{kill-buffer}, -@samp{rename-file}, @samp{rename-buffer}, etc. -@end table - -For around-action hooks: - -@table @asis -@item @samp{embark--mark-target} -Save existing mark and point location, mark -the target and run the action. Most targets at point outside the -minibuffer report which region of the buffer they correspond to -(this is the information used by @samp{embark-highlight-indicator} to -know what portion of the buffer to highlight); this function marks -that region. It is useful as an around action hook for commands that -expect a region to be marked, for example, it is used by default for -@samp{indent-region} so that it works on s-expression targets, or for -@samp{fill-region} so that it works on paragraph targets. - -@item @samp{embark--cd} -Run the action with @samp{default-directory} set to the -directory associated to the current target. The target should be of -type @samp{file}, @samp{buffer}, @samp{bookmark} or @samp{library}, and the associated directory -is what you'd expect in each case. - -@item @samp{embark--narrow-to-target} -Run the action with buffer narrowed to -current target. Use this as an around hook to localize the effect of -actions that don't already work on just the region. In the default -configuration it is used for @samp{repunctuate-sentences}. - -@item @samp{embark--save-excursion} -Run the action restoring point at the end. -The current default configuration doesn't use this but it is -available for users. -@end table - -@node Creating your own keymaps -@section Creating your own keymaps - -All internal keymaps are defined with the standard helper macro -@samp{defvar-keymap}. For example a simple version of the file action keymap -could be defined as follows: - -@lisp -(defvar-keymap embark-file-map - :doc "Example keymap with a few file actions" - :parent embark-general-map - "d" #'delete-file - "r" #'rename-file - "c" #'copy-file) -@end lisp - -These action keymaps are perfectly normal Emacs -keymaps. You may want to inherit from the @samp{embark-general-map} if you -want to access the default Embark actions. Note that @samp{embark-collect} -and @samp{embark-export} are also made available via @samp{embark-general-map}. - -@node Defining actions for new categories of targets -@section Defining actions for new categories of targets - -It is easy to configure Embark to provide actions for new types of -targets, either in the minibuffer or outside it. I present below two -very detailed examples of how to do this. At several points I'll -explain more than one way to proceed, typically with the easiest -option first. I include the alternative options since there will be -similar situations where the easiest option is not available. - -@menu -* New minibuffer target example - tab-bar tabs:: -* New target example in regular buffers - short Wikipedia links:: -@end menu - -@node New minibuffer target example - tab-bar tabs -@subsection New minibuffer target example - tab-bar tabs - -As an example, take the new @uref{https://www.gnu.org/software/emacs/manual/html_node/emacs/Tab-Bars.html, tab bars} from Emacs 27. I'll explain how -to configure Embark to offer tab-specific actions when you use the -tab-bar-mode commands that mention tabs by name. The configuration -explained here is now built-in to Embark (and Marginalia), but it's -still a good self-contained example. In order to setup up tab actions -you would need to: (1) make sure Embark knows those commands deal with -tabs, (2) define a keymap for tab actions and configure Embark so it -knows that's the keymap you want. - -@enumerate -@item -@anchor{Telling Embark about commands that prompt for tabs by name}Telling Embark about commands that prompt for tabs by name - - -For step (1), it would be great if the @samp{tab-bar-mode} commands reported -the completion category @samp{tab} when asking you for a tab with -completion. (All built-in Emacs commands that prompt for file names, -for example, do have metadata indicating that they want a @samp{file}.) They -do not, unfortunately, and I will describe a couple of ways to deal -with this. - -Maybe the easiest thing is to configure @uref{https://github.com/minad/marginalia, Marginalia} to enhance those -commands. All of the @samp{tab-bar-*-tab-by-name} commands have the words -``tab by name'' in the minibuffer prompt, so you can use: - -@lisp -(add-to-list 'marginalia-prompt-categories '("tab by name" . tab)) -@end lisp - -That's it! But in case you are ever in a situation where you don't -already have commands that prompt for the targets you want, I'll -describe how writing your own command with appropriate @samp{category} -metadata looks: - -@lisp -(defun my-select-tab-by-name (tab) - (interactive - (list - (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) - (tab-bar-tabs)) - (user-error "No tabs found")))) - (completing-read - "Tabs: " - (lambda (string predicate action) - (if (eq action 'metadata) - '(metadata (category . tab)) - (complete-with-action - action tab-list string predicate))))))) - (tab-bar-select-tab-by-name tab)) -@end lisp - -As you can see, the built-in support for setting the category -meta-datum is not very easy to use or pretty to look at. To help with -this I recommend the @samp{consult--read} function from the excellent -@uref{https://github.com/minad/consult/, Consult} package. With that function we can rewrite the command as -follows: - -@lisp -(defun my-select-tab-by-name (tab) - (interactive - (list - (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) - (tab-bar-tabs)) - (user-error "No tabs found")))) - (consult--read tab-list - :prompt "Tabs: " - :category 'tab)))) - (tab-bar-select-tab-by-name tab)) -@end lisp - -Much nicer! No matter how you define the @samp{my-select-tab-by-name} -command, the first approach with Marginalia and prompt detection has -the following advantages: you get the @samp{tab} category for all the -@samp{tab-bar-*-bar-by-name} commands at once, also, you enhance built-in -commands, instead of defining new ones. - -@item -@anchor{Defining and configuring a keymap for tab actions}Defining and configuring a keymap for tab actions - - -Let's say we want to offer select, rename and close actions for tabs -(in addition to Embark general actions, such as saving the tab name to -the kill-ring, which you get for free). Then this will do: - -@lisp -(defvar-keymap embark-tab-actions - :doc "Keymap for actions for tab-bar tabs (when mentioned by name)." - :parent embark-general-map - "s" #'tab-bar-select-tab-by-name - "r" #'tab-bar-rename-tab-by-name - "k" #'tab-bar-close-tab-by-name) - -(add-to-list 'embark-keymap-alist '(tab . embark-tab-actions)) -@end lisp - -What if after using this for a while you feel closing the tab -without confirmation is dangerous? You have a couple of options: - -@enumerate -@item -You can keep using the @samp{tab-bar-close-tab-by-name} command, but have -Embark ask you for confirmation: -@lisp -(push #'embark--confirm - (alist-get 'tab-bar-close-tab-by-name - embark-pre-action-hooks)) -@end lisp - -@item -You can write your own command that prompts for confirmation and -use that instead of @samp{tab-bar-close-tab-by-name} in the above keymap: -@lisp -(defun my-confirm-close-tab-by-name (tab) - (interactive "sTab to close: ") - (when (y-or-n-p (format "Close tab '%s'? " tab)) - (tab-bar-close-tab-by-name tab))) -@end lisp - -Notice that this is a command you can also use directly from @samp{M-x} -independently of Embark. Using it from @samp{M-x} leaves something to be -desired, though, since you don't get completion for the tab names. -You can fix this if you wish as described in the previous section. -@end enumerate -@end enumerate - -@node New target example in regular buffers - short Wikipedia links -@subsection New target example in regular buffers - short Wikipedia links - -Say you want to teach Embark to treat text of the form -@samp{wikipedia:Garry_Kasparov} in any regular buffer as a link to Wikipedia, -with actions to open the Wikipedia page in eww or an external browser -or to save the URL of the page in the kill-ring. We can take advantage -of the actions that Embark has preconfigured for URLs, so all we need -to do is teach Embark that @samp{wikipedia:Garry_Kasparov} stands for the URL -@samp{https://en.wikipedia.org/wiki/Garry_Kasparov}. - -You can be as fancy as you want with the recognized syntax. Here, to -keep the example simple, I'll assume the link matches the regexp -@samp{wikipedia:[[:alnum:]_]+}. We will write a function that looks for a -match surrounding point, and returns a dotted list of the form @samp{'(url -URL-OF-THE-PAGE START . END)} where @samp{START} and @samp{END} are the buffer -positions bounding the target, and are used by Embark to highlight it -if you have @samp{embark-highlight-indicator} included in the list -@samp{embark-indicators}. (There are a couple of other options for the return -value of a target finder: the bounding positions are optional and a -single target finder is allowed to return multiple targets; see the -documentation for @samp{embark-target-finders} for details.) - -@lisp -(defun my-short-wikipedia-link () - "Target a link at point of the form wikipedia:Page_Name." - (save-excursion - (let* ((start (progn (skip-chars-backward "[:alnum:]_:") (point))) - (end (progn (skip-chars-forward "[:alnum:]_:") (point))) - (str (buffer-substring-no-properties start end))) - (save-match-data - (when (string-match "wikipedia:\\([[:alnum:]_]+\\)" str) - `(url - ,(format "https://en.wikipedia.org/wiki/%s" - (match-string 1 str)) - ,start . ,end)))))) - -(add-to-list 'embark-target-finders 'my-short-wikipedia-link) -@end lisp - -@node How does Embark call the actions? -@chapter How does Embark call the actions? - -Embark actions are normal Emacs commands, that is, functions with an -interactive specification. In order to execute an action, Embark -calls the command with @samp{call-interactively}, so the command reads user -input exactly as if run directly by the user. For example the -command may open a minibuffer and read a string -(@samp{read-from-minibuffer}) or open a completion interface -(@samp{completing-read}). If this happens, Embark takes the target string -and inserts it automatically into the minibuffer, simulating user -input this way. After inserting the string, Embark exits the -minibuffer, submitting the input. (The immediate minibuffer exit can -be disabled for specific actions in order to allow editing the -input; this is done by adding the @samp{embark--allow-edit} function to the -appropriate entry of @samp{embark-target-injection-hooks}). Embark inserts -the target string at the first minibuffer opened by the action -command, and if the command happens to prompt the user for input -more than once, the user still interacts with the second and further -prompts in the normal fashion. Note that if a command does not -prompt the user for input in the minibuffer, Embark still allows you -to use it as an action, but of course, never inserts the target -anywhere. (There are plenty of examples in the default configuration -of commands that do not prompt the user bound to keys in the action -maps, most of the region actions, for instance.) - -This is how Embark manages to reuse normal commands as actions. The -mechanism allows you to use as Embark actions commands that were not -written with Embark in mind (and indeed almost all actions that are -bound by default in Embark's action keymaps are standard Emacs -commands). It also allows you to write new custom actions in such a -way that they are useful even without Embark. - -Staring from version 28.1, Emacs has a variable -@samp{y-or-n-p-use-read-key}, which when set to @samp{t} causes @samp{y-or-n-p} to use -@samp{read-key} instead of @samp{read-from-minibuffer}. Setting -@samp{y-or-n-p-use-read-key} to @samp{t} is recommended for Embark users because -it keeps Embark from attempting to insert the target at a @samp{y-or-n-p} -prompt, which would almost never be sensible. Also consider this as -a warning to structure your own action commands so that if they use -@samp{y-or-n-p}, they do so only after the prompting for the target. - -Here is a simple example illustrating the various ways of reading -input from the user mentioned above. Bind the following commands to -the @samp{embark-symbol-map} to be used as actions, then put the point on -some symbol and run them with @samp{embark-act}: - -@lisp -(defun example-action-command1 () - (interactive) - (message "The input was `%s'." (read-from-minibuffer "Input: "))) - -(defun example-action-command2 (arg input1 input2) - (interactive "P\nsInput 1: \nsInput 2: ") - (message "The first input %swas `%s', and the second was `%s'." - (if arg "truly " "") - input1 - input2)) - -(defun example-action-command3 () - (interactive) - (message "Your selection was `%s'." - (completing-read "Select: " '("E" "M" "B" "A" "R" "K")))) - -(defun example-action-command4 () - (interactive) - (message "I don't prompt you for input and thus ignore the target!")) - -(keymap-set embark-symbol-map "X 1" #'example-action-command1) -(keymap-set embark-symbol-map "X 2" #'example-action-command2) -(keymap-set embark-symbol-map "X 3" #'example-action-command3) -(keymap-set embark-symbol-map "X 4" #'example-action-command4) -@end lisp - -Also note that if you are using the key bindings to call actions, -you can pass prefix arguments to actions in the normal way. For -example, you can use @samp{C-u X2} with the above demonstration actions to -make the message printed by @samp{example-action-command2} more emphatic. -This ability to pass prefix arguments to actions is useful for some -actions in the default configuration, such as -@samp{embark-shell-command-on-buffer}. - -@menu -* Non-interactive functions as actions:: -@end menu - -@node Non-interactive functions as actions -@section Non-interactive functions as actions - -Alternatively, Embark does support one other type of action: a -non-interactive function of a single argument. The target is passed -as argument to the function. For example: - -@lisp -(defun example-action-function (target) - (message "The target was `%s'." target)) - -(keymap-set embark-symbol-map "X 4" #'example-action-function) -@end lisp - -Note that normally binding non-interactive functions in a keymap is -useless, since when attempting to run them using the key binding you -get an error message similar to ``Wrong type argument: commandp, -example-action-function''. In general it is more flexible to write -any new Embark actions as commands, that is, as interactive -functions, because that way you can also run them directly, without -Embark. But there are a couple of reasons to use non-interactive -functions as actions: - -@enumerate -@item -You may already have the function lying around, and it is -convenient to simply reuse it. - -@item -For command actions the targets can only be simple string, with -no text properties. For certain advanced uses you may want the -action to receive a string @emph{with} some text properties, or even a -non-string target. -@end enumerate - -@node Embark Marginalia and Consult -@chapter Embark, Marginalia and Consult - -Embark cooperates well with the @uref{https://github.com/minad/marginalia, Marginalia} and @uref{https://github.com/minad/consult, Consult} packages. -Neither of those packages is a dependency of Embark, but both are -highly recommended companions to Embark, for opposite reasons: -Marginalia greatly enhances Embark's usefulness, while Embark can help -enhance Consult. - -In the remainder of this section I'll explain what exactly Marginalia -does for Embark, and what Embark can do for Consult. - -@menu -* Marginalia:: -* Consult:: -@end menu - -@node Marginalia -@section Marginalia - -Embark comes with actions for symbols (commands, functions, variables -with actions such as finding the definition, looking up the -documentation, evaluating, etc.) in the @samp{embark-symbol-map} keymap, and -for packages (actions like install, delete, browse url, etc.) in the -@samp{embark-package-keymap}. - -Unfortunately Embark does not automatically offers you these keymaps -when relevant, because many built-in Emacs commands don't report -accurate category metadata. For example, a command like -@samp{describe-package}, which reads a package name from the minibuffer, -does not have metadata indicating this fact. - -In an earlier Embark version, there were functions to supply this -missing metadata, but they have been moved to Marginalia, which -augments many Emacs command to report accurate category metadata. -Simply activating @samp{marginalia-mode} allows Embark to offer you the -package and symbol actions when appropriate again. Candidate -annotations in the Embark collect buffer are also provided by the -Marginalia package: - -@itemize -@item -If you install Marginalia and activate @samp{marginalia-mode}, Embark -Collect buffers will use the Marginalia annotations automatically. - -@item -If you don't install Marginalia, you will see only the annotations -that come with Emacs (such as key bindings in @samp{M-x}, or the unicode -characters in @samp{C-x 8 RET}). -@end itemize - -@node Consult -@section Consult - -The excellent Consult package provides many commands that use -minibuffer completion, via the @samp{completing-read} function; plenty of its -commands can be considered enhanced versions of built-in Emacs -commands, and some are completely new functionality. One common -enhancement provided in all commands for which it makes sense is -preview functionality, for example @samp{consult-buffer} will show you a -quick preview of a buffer before you actually switch to it. - -If you use both Consult and Embark you should install the -@samp{embark-consult} package which provides integration between the two. It -provides exporters for several Consult commands and also tweaks the -behavior of many Consult commands when used as actions with @samp{embark-act} -in subtle ways that you may not even notice, but make for a smoother -experience. You need only install it to get these benefits: Embark -will automatically load it after Consult if found. - -The @samp{embark-consult} package provides the following exporters: - -@itemize -@item -You can use @samp{embark-export} from @samp{consult-line}, @samp{consult-outline}, or -@samp{consult-mark} to obtain an @samp{occur-mode} buffer. As with the built-in -@samp{occur} command you use that buffer to jump to a match and after that, -you can then use @samp{next-error} and @samp{previous-error} to navigate to other -matches. You can also press @samp{e} to activate @samp{occur-edit-mode} and edit -the matches in place! - -@item -You can export from any of the Consult asynchronous search commands, -@samp{consult-grep}, @samp{consult-git-grep}, or @samp{consult-ripgrep} to get a -@samp{grep-mode} buffer. Here too you can use @samp{next-error} and @samp{previous-error} -to navigate among matches, and, if you install the @uref{http://github.com/mhayashi1120/Emacs-wgrep/raw/master/wgrep.el , wgrep} package, -you can use it to edit the matches in place. -@end itemize - -In both cases, pressing @samp{g} will rerun the Consult command you had -exported from and re-enter the input you had typed (which is similar -to reverting but a little more flexible). You can then proceed to -re-export if that's what you want, but you can also edit the input -changing the search terms or simply cancel if you see you are done -with that search. - -The @samp{embark-consult} also contains some candidates collectors that allow -you to run @samp{embark-live} to get a live-updating table of contents for -your buffer: - -@itemize -@item -@samp{embark-consult-outline-candidates} produces the outline headings of -the current buffer, using @samp{consult-outline}. -@item -@samp{embark-consult-imenu-candidates} produces the imenu items of -the current buffer, using @samp{consult-imenu}. -@item -@samp{embark-consult-imenu-or-outline-candidates} is a simple combination -of the two previous functions: it produces imenu items in buffers -deriving from @samp{prog-mode} and otherwise outline headings. -@end itemize - -The way to configure @samp{embark-live} (or @samp{embark-collect} and @samp{embark-export} -for that matter) to use one of these function is to add it at the end -of the @samp{embark-candidate-collectors} list. The @samp{embark-consult} package by -default adds the last one, which seems to be the most sensible -default. - -Besides those exporters and candidate collectors, the @samp{embark-consult} -package provides many subtle tweaks and small integrations between -Embark and Consult. Some examples are: - -@itemize -@item -When used as actions, the asynchronous search commands will search -only the files associated to the targets: if the targets @emph{are} files, -it searches those files; for buffers it will search either the -associated file if there is one, else all files in the buffer's -@samp{default-directory}; for bookmarks it will search the file they point -to, same for Emacs Lisp libraries. This is particularly powerful -when using @samp{embark-act-all} to act on multiple files at once, for -example you can use @samp{consult-find} to search among file @emph{names} and then -@samp{embark-act-all} and @samp{consult-grep} to search within the matching files. - -@itemize -@item -For all other target types, those that do not have a sensible -notion of associated file, a Consult search command (asynchronous -or not) will search for the text of the target but leave the -minibuffer open so you can interact with the Consult command. -@end itemize - -@item -@samp{consult-imenu} will search for the target and take you directly to -the location if it matches a unique imenu entry, otherwise it will -leave the minibuffer open so you can navigate among the matches. -@end itemize - -@node Related Packages -@chapter Related Packages - -There are several packages that offer functionality similar -to Embark's. - -@table @asis -@item Acting on minibuffer completion candidates -The popular Ivy and -Helm packages have support for acting on the completion candidates -of commands written using their APIs, and there is an extensive -ecosystem of packages meant for Helm and for Ivy (the Ivy ones -usually have ``counsel'' in the name) providing commands and -appropriate actions. -@item Acting on things at point -The built-in @samp{context-menu-mode} provides -a mouse-driven context-sensitive configurable menu. The @samp{do-at-point} -package by Philip Kaludercic (available on GNU ELPA), on the other -hand is keyboard-driven. -@item Collecting completion candidates into a buffer -The Ivy package -has the command @samp{ivy-occur} which is similar to @samp{embark-collect}. As -with Ivy actions, @samp{ivy-occur} only works for commands written using -the Ivy API@. -@end table - -@node Resources -@chapter Resources - -If you want to learn more about how others have used Embark here are -some links to read: - -@itemize -@item -@uref{https://karthinks.com/software/fifteen-ways-to-use-embark/, Fifteen ways to use Embark}, a blog post by Karthik Chikmagalur. -@item -@uref{https://protesilaos.com/dotemacs/, Protesilaos Stavrou's dotemacs}, look for the section called -``Extended minibuffer actions and more (embark.el and -prot-embark.el)'' -@end itemize - -And some videos to watch: - -@itemize -@item -@uref{https://protesilaos.com/codelog/2021-01-09-emacs-embark-extras/, Embark and my extras} by Protesilaos Stavrou. -@item -@uref{https://youtu.be/qpoQiiinCtY, Embark -- Key features and tweaks} by Raoul Comninos on the -Emacs-Elements YouTube channel. -@item -@uref{https://youtu.be/WsxXr1ncukY, Livestreamed: Adding an Embark context action to send a stream -message} by Sacha Chua. -@item -@uref{https://youtu.be/qk2Is_sC8Lk, System Crafters Live! - The Many Uses of Embark} by David Wilson. -@item -@uref{https://youtu.be/5ffb2at2d7w, Using Emacs Episode 80 - Vertico, Marginalia, Consult and Embark} by -Mike Zamansky. -@end itemize - -@node Contributions -@chapter Contributions - -Contributions to Embark are very welcome. There is a @uref{https://github.com/oantolin/embark/issues/95, wish list} for -actions, target finders, candidate collectors and exporters. For other -ideas you have for Embark, feel free to open an issue on the @uref{https://github.com/oantolin/embark/issues, issue -tracker}. Any neat configuration tricks you find might be a good fit -for the @uref{https://github.com/oantolin/embark/wiki, wiki}. - -Code contributions are very welcome too, but since Embark is now on -GNU ELPA, copyright assignment to the FSF is required before you can -contribute code. - -@node Acknowledgments -@chapter Acknowledgments - -While I, Omar Antolín Camarena, have written most of the Embark code -and remain very stubborn about some of the design decisions, Embark -has received substantial help from a number of other people which this -document has neglected to mention for far too long. In particular, -Daniel Mendler has been absolutely invaluable, implementing several -important features, and providing a lot of useful advice. - -Code contributions: - -@itemize -@item -@uref{https://github.com/minad, Daniel Mendler} -@item -@uref{https://github.com/clemera/, Clemens Radermacher} -@item -@uref{https://codeberg.org/jao/, José Antonio Ortega Ruiz} -@item -@uref{https://github.com/iyefrat, Itai Y@. Efrat} -@item -@uref{https://github.com/a13, a13} -@item -@uref{https://github.com/jakanakaevangeli, jakanakaevangeli} -@item -@uref{https://github.com/mihakam, mihakam} -@item -@uref{https://github.com/leungbk, Brian Leung} -@item -@uref{https://github.com/karthink, Karthik Chikmagalur} -@item -@uref{https://github.com/roshanshariff, Roshan Shariff} -@item -@uref{https://github.com/condy0919, condy0919} -@item -@uref{https://github.com/DamienCassou, Damien Cassou} -@item -@uref{https://github.com/JimDBh, JimDBh} -@end itemize - -Advice and useful discussions: - -@itemize -@item -@uref{https://github.com/minad, Daniel Mendler} -@item -@uref{https://gitlab.com/protesilaos/, Protesilaos Stavrou} -@item -@uref{https://github.com/clemera/, Clemens Radermacher} -@item -@uref{https://github.com/hmelman/, Howard Melman} -@item -@uref{https://github.com/astoff, Augusto Stoffel} -@item -@uref{https://github.com/bdarcus, Bruce d'Arcus} -@item -@uref{https://github.com/jdtsmith, JD Smith} -@item -@uref{https://github.com/karthink, Karthik Chikmagalur} -@item -@uref{https://github.com/jakanakaevangeli, jakanakaevangeli} -@item -@uref{https://github.com/iyefrat, Itai Y@. Efrat} -@item -@uref{https://github.com/mohkale, Mohsin Kaleem} -@end itemize - -@bye \ No newline at end of file blob - /dev/null blob + 6d22de33f570450fc5a630a3b4d331ce4aeccc53 (mode 644) --- /dev/null +++ elpa/embark-1.1/.dir-locals.el @@ -0,0 +1,6 @@ +;;; Directory Local Variables +;;; For more information see (info "(emacs) Directory Variables") + +((emacs-lisp-mode + (show-trailing-whitespace . t) + (indent-tabs-mode . nil))) blob - /dev/null blob + 7a694c9699a986b9adf1f6cb8a18a6e923e47ed9 (mode 644) --- /dev/null +++ elpa/embark-1.1/.elpaignore @@ -0,0 +1 @@ +LICENSE \ No newline at end of file blob - /dev/null blob + 058be1ce20770245b36f435560955810398007cc (mode 644) --- /dev/null +++ elpa/embark-1.1/CHANGELOG.org @@ -0,0 +1,105 @@ +#+title: Embark changelog + +* Version 1.1 (2024-04-18) +- The =embark-consult= package contains a new exporter for + =consult-location= targets (produced by several =consult= commands such + as =consult-line=), which exports to a grep mode buffer. Users wishing + to use the new grep mode exporter can use the following + configuration: + #+begin_src emacs-lisp + (setf (alist-get 'consult-location embark-exporters-alist) + #'embark-consult-export-location-grep) + #+end_src + The main reason for adding the new exporter is that users of the + =wgrep= package will be able to make use of a feature that =wgrep= has + and the built-in =occur-edit-mode= lacks: when editing search results + you can add new lines to a result location. There are also some + disadvantages of grep mode compared to occur mode (which is why the + previously existing occur mode exporter continues to be the + default): (1) =wgrep= is a third party package while =occur-edit-mode= + is built-in; (2) occur mode buffers can list lines in any kind of + buffer, but grep mode and =wgrep= are meant for lines of files + exclusively. +* Version 1.0 (2023-12-08) +- You can now use around action hooks with multitarget actions (that + you couldn't previously was an oversight). +- Users of the =embark-consult= package can now use consult async search + commands such as =consult-grep= as multitarget actions (through + =embark-act-all=) to search a list of files. For example, you can use + =consult-find= to search among file /names/ and once you have the + relevant files in the minibuffer, you can use =embark-act-all= to + search for some text in those files. When acting on buffers consult + async search commands will search the associated file if there is + one, or else the =default-directory= of the buffer. +- =embark-bindings= and similar commands now show definition of keyboard + macros. +- =embark-org= now recognizes Org links in non-org buffers. +- Now pressing RET in an =embark-collect= on a selection made by + using =embark-select= in a normal buffer will take you to the location + each target was collected from. +- Some functions renamed for greater consistency (these functions are + unlikely to be referred to in user's configuration): + - =embark-target-completion-at-point= → =embark-target-completion-list-candidate= + - =embark-target-top-minibuffer-completion= → =embark-target-top-minibuffer-candidate= + - =embark-completions-buffer-candidates= → =embark-completion-list-candidates= +* Version 0.23 (2023-09-19) +- Added a mode line indicator showing the number of selected targets in + the current buffer (contributed by @minad, thanks!) +- Now =embark-select= can also be called as a top-level command, from + outside =embark-act=. When called that way, it will select the first + target at point. +- =embark-org= now has support for acting on references to org headings + in other buffers, by jumping to the heading first and then running + the action. One source of references to org headings in other + buffers are agenda views: each agenda item is such a reference. But + this feature also supports some great third party commands which + produce references to org headings, such as =org-ql-find= from the + =org-ql= package or =consult-org-heading= from =consult=. +- Renamed =embark-isearch= to =embark-isearch-forward= and added + =embark-isearch-backward=. +- =embark-become= now removes any invisible text from the minibuffer + input on the grounds that users probably expect the target command + to receive exactly the input they can see. +- The meaning of the prefix argument in =embark-bindings= has flipped: + now by default global key bindings are excluded and you can use =C-u= + to include them. +- If any candidate in an embark-collect buffer contains a newline, + then candidates will be separated by horizontal lines. This is handy + for the kill-ring, which you can browse by calling =embark-collect= + from =yank-pop=. +* Version 0.22.1 (2023-04-20) +** New feature: selections +Now users can select several targets to make an ad hoc collection. The +commands =embark-act-all=, =embark-export= and =embark-collect= will act on +the selection if it is non-empty. To select or deselect a target use +the =embark-select= action (bound to =SPC= in =embark-general-map=). If you +have some targets selected, then using =embark-select= through +=embark-act-all= will deselect them. + +Before this change the Embark Collect buffers had their own +implementation of selections which has been removed. This is how to +translate the old bindings to the new feature (which is available in +all buffers, not just Embark Collect buffers!): + +| Task | Old binding | New binding | +|--------------------+-------------+---------------| +| Mark a candidate | m | a SPC | +| Unmark a candidate | u | a SPC | +| Unmark all | U | A SPC | +| Mark all [1] | t | A SPC | +| Toggle all marks | t | not available | + +[1] Marking all candidates (with either the old =t= or the new =A SPC=) +requires that there are no marked candidates to begin with. + +In order to make room for the binding of =embark-select= to +=SPC=, some other key bindings were moved: + +- =mark= in =embark-general-map= was moved to =C-SPC=. +- =outline-mark-subtree= in =embark-heading-map= was moved to =C-SPC=. +- =whitespace-cleanup-region= in =embark-region-map= was moved to =F=. + +* Version 0.21.1 (2020-01-30) +- Finally started this changelog on 2023-04-20. Known issues with the + changelog: it started very late, the first entry is not very + informative. blob - /dev/null blob + 0ac8be62137458349c1935f910593e8d6529a826 (mode 644) --- /dev/null +++ elpa/embark-1.1/README-elpa @@ -0,0 +1,1471 @@ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + EMBARK: EMACS MINI-BUFFER ACTIONS ROOTED IN + KEYMAPS + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + + + +1 Overview +══════════ + + Embark makes it easy to choose a command to run based on what is near + point, both during a minibuffer completion session (in a way familiar + to Helm or Counsel users) and in normal buffers. Bind the command + `embark-act' to a key and it acts like prefix-key for a keymap of + /actions/ (commands) relevant to the /target/ around point. With point + on an URL in a buffer you can open the URL in a browser or eww or + download the file it points to. If while switching buffers you spot an + old one, you can kill it right there and continue to select another. + Embark comes preconfigured with over a hundred actions for common + types of targets such as files, buffers, identifiers, s-expressions, + sentences; and it is easy to add more actions and more target types. + Embark can also collect all the candidates in a minibuffer to an + occur-like buffer or export them to a buffer in a major-mode specific + to the type of candidates, such as dired for a set of files, ibuffer + for a set of buffers, or customize for a set of variables. + + +1.1 Acting on targets +───────────────────── + + You can think of `embark-act' as a keyboard-based version of a + right-click contextual menu. The `embark-act' command (which you + should bind to a convenient key), acts as a prefix for a keymap + offering you relevant /actions/ to use on a /target/ determined by the + context: + + • In the minibuffer, the target is the current top completion + candidate. + • In the `*Completions*' buffer the target is the completion at point. + • In a regular buffer, the target is the region if active, or else the + file, symbol, URL, s-expression or defun at point. + + Multiple targets can be present at the same location and you can cycle + between them by repeating the `embark-act' key binding. The type of + actions offered depend on the type of the target. Here is a sample of + a few of the actions offered in the default configuration: + + • For files you get offered actions like deleting, copying, renaming, + visiting in another window, running a shell command on the file, + etc. + • For buffers the actions include switching to or killing the buffer. + • For package names the actions include installing, removing or + visiting the homepage. + • For Emacs Lisp symbols the actions include finding the definition, + looking up documentation, evaluating (which for a variable + immediately shows the value, but for a function lets you pass it + some arguments first). There are some actions specific to variables, + such as setting the value directly or though the customize system, + and some actions specific to commands, such as binding it to a key. + + By default when you use `embark-act' if you don't immediately select + an action, after a short delay Embark will pop up a buffer showing a + list of actions and their corresponding key bindings. If you are using + `embark-act' outside the minibuffer, Embark will also highlight the + current target. These behaviors are configurable via the variable + `embark-indicators'. Instead of selecting an action via its key + binding, you can select it by name with completion by typing `C-h' + after `embark-act'. + + Everything is easily configurable: determining the current target, + classifying it, and deciding which actions are offered for each type + in the classification. The above introduction just mentions part of + the default configuration. + + Configuring which actions are offered for a type is particularly easy + and requires no programming: the variable `embark-keymap-alist' + associates target types with variables containing keymaps, and those + keymaps containing bindings for the actions. (To examine the available + categories and their associated keymaps, you can use `C-h v + embark-keymap-alist' or customize that variable.) For example, in the + default configuration the type `file' is associated with the symbol + `embark-file-map'. That symbol names a keymap with single-letter key + bindings for common Emacs file commands, for instance `c' is bound to + `copy-file'. This means that if you are in the minibuffer after + running a command that prompts for a file, such as `find-file' or + `rename-file', you can copy a file by running `embark-act' and then + pressing `c'. + + These action keymaps are very convenient but not strictly necessary + when using `embark-act': you can use any command that reads from the + minibuffer as an action and the target of the action will be inserted + at the first minibuffer prompt. After running `embark-act' all of your + key bindings and even `execute-extended-command' can be used to run a + command. For example, if you want to replace all occurrences of the + symbol at point, just use `M-%' as the action, there is no need to + bind `query-replace' in one of Embark's keymaps. Also, those action + keymaps are normal Emacs keymaps and you should feel free to bind in + them whatever commands you find useful as actions and want to be + available through convenient bindings. + + The actions in `embark-general-map' are available no matter what type + of completion you are in the middle of. By default this includes + bindings to save the current candidate in the kill ring and to insert + the current candidate in the previously selected buffer (the buffer + that was current when you executed a command that opened up the + minibuffer). + + Emacs's minibuffer completion system includes metadata indicating the + /category/ of what is being completed. For example, `find-file''s + metadata indicates a category of `file' and `switch-to-buffer''s + metadata indicates a category of `buffer'. Embark has the related + notion of the /type/ of a target for actions, and by default when + category metadata is present it is taken to be the type of minibuffer + completion candidates when used as targets. Emacs commands often do + not set useful category metadata so the [Marginalia] package, which + supplies this missing metadata, is highly recommended for use with + Embark. + + Embark's default configuration has actions for the following target + types: files, buffers, symbols, packages, URLs, bookmarks, and as a + somewhat special case, actions for when the region is active. You can + read about the [default actions and their key bindings] on the GitHub + project wiki. + + +[Marginalia] + +[default actions and their key bindings] + + + +1.2 The default action on a target +────────────────────────────────── + + Embark has a notion of default action for a target: + + • If the target is a minibuffer completion candidate, then the default + action is whatever command opened the minibuffer in the first place. + For example if you run `kill-buffer', then the default action will + be to kill buffers. + • If the target comes from a regular buffer (i.e., not a minibuffer), + then the default action is whatever is bound to `RET' in the keymap + of actions for that type of target. For example, in Embark's default + configuration for a URL found at point the default action is + `browse-url', because `RET' is bound to `browse-url' in the + `embark-url-map' keymap. + + To run the default action you can press `RET' after running + `embark-act'. Note that if there are several different targets at a + given location, each has its own default action, so first cycle to the + target you want and then press `RET' to run the corresponding default + action. + + There is also `embark-dwim' which runs the default action for the + first target found. It's pretty handy in non-minibuffer buffers: with + Embark's default configuration it will: + + • Open the file at point. + • Open the URL at point in a web browser (using the `browse-url' + command). + • Compose a new email to the email address at point. + • In an Emacs Lisp buffer, if point is on an opening parenthesis or + right after a closing one, it will evaluate the corresponding + expression. + • Go to the definition of an Emacs Lisp function, variable or macro at + point. + • Find the file corresponding to an Emacs Lisp library at point. + + +1.3 Working with sets of possible targets +───────────────────────────────────────── + + Besides acting individually on targets, Embark lets you work + collectively on a set of target /candidates/. For example, while you + are in the minibuffer the candidates are simply the possible + completions of your input. Embark provides three main commands to work + on candidate sets: + + • The `embark-act-all' command runs the same action on each of the + current candidates. It is just like using `embark-act' on each + candidate in turn. (Because you can easily act on many more + candidates than you meant to, by default Embark asks you to confirm + uses of `embark-act-all'; you can turn this off by setting the user + option `embark-confirm-act-all' to `nil'.) + + • The `embark-collect' command produces a buffer listing all the + current candidates, for you to peruse and run actions on at your + leisure. The candidates are displayed as a list showing additional + annotations. If any of the candidates contain newlines, then + horizontal lines are used to separate candidates. + + The Embark Collect buffer is somewhat "dired-like": you can select + and deselect candidates through `embark-select' (available as an + action in `embark-act', bound to `SPC'; but you could also give it a + global key binding). In an Embark Collect buffer `embark-act' is + bound to `a' and `embark-act-all' is bound to `A'; `embark-act-all' + will act on all currently marked candidates if there any, and will + act on all candidates if none are marked. In particular, this means + that `a SPC' will toggle whether the candidate at point is selected, + and `A SPC' will select all candidates if none are selected, or + deselect all selected candidates if there are some. + + • The `embark-export' command tries to open a buffer in an appropriate + major mode for the set of candidates. If the candidates are files + export produces a Dired buffer; if they are buffers, you get an + Ibuffer buffer; and if they are packages you get a buffer in package + menu mode. + + If you use the grepping commands from the [Consult] package, + `consult-grep', `consult-git-grep' or `consult-ripgrep', then you + should install the `embark-consult' package, which adds support for + exporting a list of grep results to an honest grep-mode buffer, on + which you can even use [wgrep] if you wish. + + When in doubt choosing between exporting and collecting, a good rule + of thumb is to always prefer `embark-export' since when an exporter to + a special major mode is available for a given type of target, it will + be more featureful than an Embark collect buffer, and if no such + exporter is configured the `embark-export' command falls back to the + generic `embark-collect'. + + These commands are always available as "actions" (although they do not + act on just the current target but on all candidates) for `embark-act' + and are bound to `A', `S' (for "snapshot"), and `E', respectively, in + `embark-general-map'. This means that you do not have to bind your own + key bindings for these (although you can, of course!), just a key + binding for `embark-act'. + + In Embark Collect or Embark Export buffers that were obtained by + running `embark-collect' or `embark-export' from within a minibuffer + completion session, `g' is bound to a command that restarts the + completion session, that is, the command that opened the minibuffer is + run again and the minibuffer contents restored. You can then interact + normally with the command, perhaps editing the minibuffer contents, + and, if you wish, you can rerun `embark-collect' or `embark-export' to + get an updated buffer. + + +[Consult] + +[wgrep] + +1.3.1 Selecting some targets to make an ad hoc candidate set +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The commands for working with sets of candidates just described, + namely `embark-act-all', `embark-export' and `embark-collect' by + default work with all candidates defined in the current context. For + example, in the minibuffer they operate on all currently completion + candidates, or in a dired buffer they work on all marked files (or all + files if none are marked). Embark also has a notion of /selection/, + where you can accumulate an ad hoc list of targets for these commands + to work on. + + The selection is controlled by using the `embark-select' action, bound + to `SPC' in `embark-general-map' so that it is always available (you + can also give `embark-select' a global key binding if you wish; when + called directly, not as an action for `embark-act', it will select the + first target at point). Calling this action on a target toggles its + membership in the current buffer's Embark selection; that is, it adds + it to selection if not selected and removes it from the selection if + it was selected. Whenever the selection for a buffer is non-empty, the + commands `embark-act-all', `embark-export' and `embark-collect' will + act on the selection. + + To deselect all selected targets, you can use the `embark-select' + action through `embark-act-all', since this will run `embark-select' + on each member of the current selection. Similarly if no targets are + selected and you are in a minibuffer completion session, running + `embark-select' from `embark-act-all' will select all the current + completion candidates. + + By default, whenever some targets are selected in the current buffer, + a count of selected targets appears in the mode line. This can be + turned off or customized through the `embark-selection-indicator' user + option. + + The selection functionality is supported in every buffer: + + • In the minibuffer this gives a convenient way to act on several + completion candidates that don't follow any simple pattern: just go + through the completions selecting the ones you want, then use + `embark-act-all'. For example, you could attach several files at + once to an email. + • For Embark Collect buffers this functionality enables a dired-like + workflow, in which you mark various candidates and apply an action + to all at once. (It supersedes a previous ad hoc dired-like + interface that was implemented only in Embark Collect buffers, with + a slightly different interface.) + • In a eww buffer you could use this to select various links you wish + to follow up on, and then collect them into a buffer. Similarly, + while reading Emacs's info manual you could select some symbols you + want to read more about and export them to an `apropos-mode' buffer. + • You can use selections in regular text or programming buffers to do + complex editing operations. For example, if you have three + paragraphs scattered over a file and you want to bring them + together, you can select each one, insert them all somewhere and + finally delete all of them (from their original locations). + + +1.3.2 `embark-live' a live-updating variant of `embark-collect' +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + Finally, there is also an `embark-live' variant of the + `embark-collect' command which automatically updates the collection + after each change in the source buffer. Users of a completion UI that + automatically updates and displays the candidate list (such as + Vertico, Icomplete, Fido-mode, or MCT) will probably not want to use + `embark-live' from the minibuffer as they will then have two live + updating displays of the completion candidates! + + A more likely use of `embark-live' is to be called from a regular + buffer to display a sort of live updating "table of contents" for the + buffer. This depends on having appropriate candidate collectors + configured in `embark-candidate-collectors'. There are not many in + Embark's default configuration, but you can try this experiment: open + a dired buffer in a directory that has very many files, mark a few, + and run `embark-live'. You'll get an Embark Collect buffer containing + only the marked files, which updates as you mark or unmark files in + dired. To make `embark-live' genuinely useful other candidate + collectors are required. The `embark-consult' package (documented + near the end of this manual) contains a few: one for imenu items and + one for outline headings as used by `outline-minor-mode'. Those + collectors really do give `embark-live' a table-of-contents feel. + + +1.4 Switching to a different command without losing what you've typed +───────────────────────────────────────────────────────────────────── + + Embark also has the `embark-become' command which is useful for when + you run a command, start typing at the minibuffer and realize you + meant a different command. The most common case for me is that I run + `switch-to-buffer', start typing a buffer name and realize I haven't + opened the file I had in mind yet! I'll use this situation as a + running example to illustrate `embark-become'. When this happens I + can, of course, press `C-g' and then run `find-file' and open the + file, but this requires retyping the portion of the file name you + already typed. This process can be streamlined with `embark-become': + while still in the `switch-to-buffer' you can run `embark-become' and + effectively make the `switch-to-buffer' command become `find-file' for + this run. + + You can bind `embark-become' to a key in `minibuffer-local-map', but + it is also available as an action under the letter `B' (uppercase), so + you don't need a binding if you already have one for `embark-act'. So, + assuming I have `embark-act' bound to, say, `C-.', once I realize I + haven't open the file I can type `C-. B C-x C-f' to have + `switch-to-buffer' become `find-file' without losing what I have + already typed in the minibuffer. + + But for even more convenience, `embark-become' offers shorter key + bindings for commands you are likely to want the current command to + become. When you use `embark-become' it looks for the current command + in all keymaps named in the list `embark-become-keymaps' and then + activates all keymaps that contain it. For example, the default value + of `embark-become-keymaps' contains a keymap + `embark-become-file+buffer-map' with bindings for several commands + related to files and buffers, in particular, it binds + `switch-to-buffer' to `b' and `find-file' to `f'. So when I + accidentally try to switch to a buffer for a file I haven't opened + yet, `embark-become' finds that the command I ran, `switch-to-buffer', + is in the keymap `embark-become-file+buffer-map', so it activates that + keymap (and any others that also contain a binding for + `switch-to-buffer'). The end result is that I can type `C-. B f' to + switch to `find-file'. + + +2 Quick start +═════════════ + + The easiest way to install Embark is from GNU ELPA, just run `M-x + package-install RET embark RET'. (It is also available on MELPA.) It + is highly recommended to also install [Marginalia] (also available on + GNU ELPA), so that Embark can offer you preconfigured actions in more + contexts. For `use-package' users, the following is a very reasonable + starting configuration: + + ┌──── + │ (use-package marginalia + │ :ensure t + │ :config + │ (marginalia-mode)) + │ + │ (use-package embark + │ :ensure t + │ + │ :bind + │ (("C-." . embark-act) ;; pick some comfortable binding + │ ("C-;" . embark-dwim) ;; good alternative: M-. + │ ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' + │ + │ :init + │ + │ ;; Optionally replace the key help with a completing-read interface + │ (setq prefix-help-command #'embark-prefix-help-command) + │ + │ ;; Show the Embark target at point via Eldoc. You may adjust the + │ ;; Eldoc strategy, if you want to see the documentation from + │ ;; multiple providers. Beware that using this can be a little + │ ;; jarring since the message shown in the minibuffer can be more + │ ;; than one line, causing the modeline to move up and down: + │ + │ ;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target) + │ ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) + │ + │ :config + │ + │ ;; Hide the mode line of the Embark live/completions buffers + │ (add-to-list 'display-buffer-alist + │ '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" + │ nil + │ (window-parameters (mode-line-format . none))))) + │ + │ ;; Consult users will also want the embark-consult package. + │ (use-package embark-consult + │ :ensure t ; only need to install it, embark loads it after consult if found + │ :hook + │ (embark-collect-mode . consult-preview-at-point-mode)) + └──── + + About the suggested key bindings for `embark-act' and `embark-dwim': + • Those key bindings are unlikely to work in the terminal, but + terminal users are probably well aware of this and will know to + select different bindings. + • The suggested `C-.' binding is used by default in (at least some + installations of) GNOME to input emojis, and Emacs doesn't even get + a chance to respond to the binding. You can select a different key + binding for `embark-act' or use `ibus-setup' to change the shortcut + for emoji insertion (Emacs 29 will likely use `C-x 8 e e', in case + you want to set the same one system-wide). + • The suggested alternative of `M-.' for `embark-dwim' is bound by + default to `xref-find-definitions'. That is a very useful command + but overwriting it with `embark-dwim' is sensible since in Embark's + default configuration, `embark-dwim' will also find the definition + of the identifier at point. (Note that `xref-find-definitions' with + a prefix argument prompts you for an identifier, `embark-dwim' does + not cover this case). + + Other Embark commands such as `embark-act-all', `embark-become', + `embark-collect', and `embark-export' can be run through `embark-act' + as actions bound to `A', `B', `S' (for "snapshot"), and `E' + respectively, and thus don't really need a dedicated key binding, but + feel free to bind them directly if you so wish. If you do choose to + bind them directly, you'll probably want to bind them in + `minibuffer-local-map', since they are most useful in the minibuffer + (in fact, `embark-become' only works in the minibuffer). + + The command `embark-dwim' executes the default action at + point. Another good keybinding for `embark-dwim' is `M-.' since + `embark-dwim' acts like `xref-find-definitions' on the symbol at + point. `C-.' can be seen as a right-click context menu at point and + `M-.' acts like left-click. The keybindings are mnemonic, both act at + the point (`.'). + + Embark needs to know what your minibuffer completion system considers + to be the list of candidates and which one is the current candidate. + Embark works out of the box if you use Emacs's default tab completion, + the built-in `icomplete-mode' or `fido-mode', or the third-party + packages [Vertico] or [Ivy]. + + If you are a [Helm] or [Ivy] user you are unlikely to want Embark + since those packages include comprehensive functionality for acting on + minibuffer completion candidates. (Embark does come with Ivy + integration despite this.) + + +[Marginalia] + +[Vertico] + +[Ivy] + +[Helm] + + +3 Advanced configuration +════════════════════════ + +3.1 Showing information about available targets and actions +─────────────────────────────────────────────────────────── + + By default, if you run `embark-act' and do not immediately select an + action, after a short delay Embark will pop up a buffer called + `*Embark Actions*' containing a list of available actions with their + key bindings. You can scroll that buffer with the mouse of with the + usual commands `scroll-other-window' and `scroll-other-window-down' + (bound by default to `C-M-v' and `C-M-S-v'). + + That functionality is provided by the `embark-mixed-indicator', but + Embark has other indicators that can provide information about the + target and its type, what other targets you can cycle to, and which + actions have key bindings in the action map for the current type of + target. Any number of indicators can be active at once and the user + option `embark-indicators' should be set to a list of the desired + indicators. + + Embark comes with the following indicators: + + • `embark-minimal-indicator': shows a messages in the echo area or + minibuffer prompt showing the current target and the types of all + targets starting with the current one. + + • `embark-highlight-indicator': highlights the target at point; on by + default. + + • `embark-verbose-indicator': displays a table of actions and their + key bindings in a buffer; this is not on by default, in favor of the + mixed indicator described next. + + • `embark-mixed-indicator': starts out by behaving as the minimal + indicator but after a short delay acts as the verbose indicator; + this is on by default. + + • `embark-isearch-highlight-indicator': this only does something when + the current target is the symbol at point, in which case it lazily + highlights all occurrences of that symbol in the current buffer, + like isearch; also on by default. + + Users of the popular [which-key] package may prefer to use the + `embark-which-key-indicator' from the [Embark wiki]. Just copy its + definition from the wiki into your configuration and customize the + `embark-indicators' user option to exclude the mixed and verbose + indicators and to include `embark-which-key-indicator'. + + If you use [Vertico], there is an even easier way to get a + `which-key'-like display that also lets you use completion to narrow + down the list of alternatives, described at the end of the next + section. + + +[which-key] + +[Embark wiki] + + +[Vertico] + + +3.2 Selecting commands via completions instead of key bindings +────────────────────────────────────────────────────────────── + + As an alternative to reading the list of actions in the verbose or + mixed indicators (see the previous section for a description of + these), you can press the `embark-help-key', which is `C-h' by default + (but you may prefer `?' to free up `C-h' for use as a prefix) after + running `embark-act'. Pressing the help key will prompt you for the + name of an action with completion (but feel free to enter a command + that is not among the offered candidates!), and will also remind you + of the key bindings. You can press `embark-keymap-prompter-key', which + is `@' by default, at the prompt and then one of the key bindings to + enter the name of the corresponding action. + + You may think that with the `*Embark Actions*' buffer popping up to + remind you of the key bindings you'd never want to use completion to + select an action by name, but personally I find that typing a small + portion of the action name to narrow down the list of candidates feels + significantly faster than visually scanning the entire list of + actions. + + If you find you prefer selecting actions that way, you can configure + embark to always prompt you for actions by setting the variable + `embark-prompter' to `embark-completing-read-prompter'. + + On the other hand, you may wish to continue using key bindings for the + actions you perform most often, and to use completion only to explore + what further actions are available or when you've forgotten a key + binding. In that case, you may prefer to use the minimal indicator, + which does not pop-up an `*Embark Actions*' buffer at all, and to use + the `embark-help-key' whenever you need help. This unobtrusive setup + is achieved with the following configuration: + + ┌──── + │ (setq embark-indicators + │ '(embark-minimal-indicator ; default is embark-mixed-indicator + │ embark-highlight-indicator + │ embark-isearch-highlight-indicator)) + └──── + + [Vertico] users may wish to configure a grid display for the actions + and key-bindings, reminiscent of the popular package [which-key], but, + of course, enhanced by the use of completion to narrow the list of + commands. In order to get the grid display, put the following in your + Vertico configuration: + + ┌──── + │ (add-to-list 'vertico-multiform-categories '(embark-keybinding grid)) + │ (vertico-multiform-mode) + └──── + + This will make the available keys be shown in a compact grid like in + `which-key'. The `vertico-multiform-mode' also enables keys such as + `M-V', `M-G', `M-B', and `M-U' for manually switching between layouts + in Vertico buffers. + + +[Vertico] + +[which-key] + +3.2.1 Selecting commands via completion outside of Embark +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + If you like this completion interface for exploring key bindings for + Embark actions, you may want to use it elsewhere in Emacs. You can use + Embark's completion-based command prompter to list: + + • key bindings under a prefix, + • local key bindings, or + • all key bindings. + + To use it for key bindings under a prefix (you can use this to replace + the `which-key' package, for example), use this configuration: + + ┌──── + │ (setq prefix-help-command #'embark-prefix-help-command) + └──── + + Now, when you have started on a prefix sequence such as `C-x' or + `C-c', pressing `C-h' will bring up the Embark version of the built-in + `prefix-help-command', which will list the keys under that prefix and + their bindings, and lets you select the one you wanted with + completion, or by key binding if you press + `embark-keymap-prompter-key'. + + To list local or global key bindings, use the command + `embark-bindings'. You can bind that to `C-h b', which is the default + key binding for the built-in `describe-bindings' command, which this + command can replace. By default, `embark-bindings' lists local key + bindings, typically those bound in the major mode keymap; to get + global bindings as well, call it with a `C-u' prefix argument. + + +3.3 Quitting the minibuffer after an action +─────────────────────────────────────────── + + By default, if you call `embark-act' from the minibuffer it quits the + minibuffer after performing the action. You can change this by setting + the user option `embark-quit-after-action' to `nil'. Having + `embark-act' /not/ quit the minibuffer can be useful to turn commands + into little "thing managers". For example, you can use `find-file' as + a little file manager or `describe-package' as a little package + manager: you can run those commands, perform a series of actions, and + then quit the command. + + If you want to control the quitting behavior in a fine-grained manner + depending on the action, you can set `embark-quit-after-action' to an + alist, associating commands to either `t' for quitting or `nil' for + not quitting. When using an alist, you can use the special key `t' to + specify the default behavior. For example, to specify that by default + actions should not quit the minibuffer but that using `kill-buffer' as + an action should quit, you can use the following configuration: + + ┌──── + │ (setq embark-quit-after-action '((kill-buffer . t) (t . nil))) + └──── + + The variable `embark-quit-after-action' only specifies a default, that + is, it only controls whether or not `embark-act' quits the minibuffer + when you call it without a prefix argument, and you can select the + opposite behavior to what the variable says by calling `embark-act' + with `C-u'. Also note that both the variable + `embark-quit-after-action' and `C-u' have no effect when you call + `embark-act' outside the minibuffer. + + If you find yourself using the quitting and non-quitting variants of + `embark-act' about equally often, independently of the action, you may + prefer to simply have separate commands for them instead of a single + command that you call with `C-u' half the time. You could, for + example, keep the default exiting behavior of `embark-act' and define + a non-quitting version as follows: + + ┌──── + │ (defun embark-act-noquit () + │ "Run action but don't quit the minibuffer afterwards." + │ (interactive) + │ (let ((embark-quit-after-action nil)) + │ (embark-act))) + └──── + + +3.4 Running some setup after injecting the target +───────────────────────────────────────────────── + + You can customize what happens after the target is inserted at the + minibuffer prompt of an action. There are + `embark-target-injection-hooks', that are run by default after + injecting the target into the minibuffer. The variable + `embark-target-injection-hooks' is an alist associating commands to + their setup hooks. There are two special keys: if no setup hook is + specified for a given action, the hook associated to `t' is run; and + the hook associated to `:always' is run regardless of the + action. (This variable used to have the less explicit name of + `embark-setup-action-hooks', so please update your configuration.) + + For example, consider using `shell-command' as an action during file + completion. It would be useful to insert a space before the target + file name and to leave the point at the beginning, so you can + immediately type the shell command to run on that file. That's why in + Embark's default configuration there is an entry in + `embark-target-injection-hooks' associating `shell-command' to a hook + that includes `embark--shell-prep', a simple helper function that + quotes all the spaces in the file name, inserts an extra space at the + beginning of the line and leaves point to the left of it. + + Now, the preparation that `embark--shell-prep' does would be useless + if Embark did what it normally does after it inserts the target of the + action at the minibuffer prompt, which is to "press `RET'" for you, + accepting the target as is; if Embark did that for `shell-command' you + wouldn't get a chance to type in the command to execute! That is why + in Embark's default configuration the entry for `shell-command' in + `embark-target-injection-hooks' also contains the function + `embark--allow-edit'. + + Embark used to have a dedicated variable `embark-allow-edit-actions' + to which you could add commands for which Embark should forgo pressing + `RET' for you after inserting the target. Since its effect can also be + achieved via the general `embark-target-injection-hooks' mechanism, + that variable has been removed to simplify Embark. Be sure to update + your configuration; if you had something like: + + ┌──── + │ (add-to-list 'embark-allow-edit-actions 'my-command) + └──── + + you should replace it with: + + ┌──── + │ (push 'embark--allow-edit + │ (alist-get 'my-command embark-target-injection-hooks)) + └──── + + + Also note that while you could abuse `embark--allow-edit' so that you + have to confirm "dangerous" actions such as `delete-file', it is + better to implement confirmation by adding the `embark--confirm' + function to the appropriate entry of a different hook alist, namely, + `embark-pre-action-hooks'. + + Besides `embark--allow-edit', Embark comes with another function that + is of general utility in action setup hooks: + `embark--ignore-target'. Use it for commands that do prompt you in the + minibuffer but for which inserting the target would be + inappropriate. This is not a common situation but does occasionally + arise. For example it is used by default for + `shell-command-on-region': that command is used as an action for + region targets, and it prompts you for a shell command; you typically + do /not/ want the target, that is the contents of the region, to be + entered at that prompt! + + +3.5 Running hooks before, after or around an action +─────────────────────────────────────────────────── + + Embark has three variables, `embark-pre-action-hooks', + `embark-post-action-hooks' and `embark-around-action-hooks', which are + alists associating commands to hooks that should run before or after + or as around advice for the command when used as an action. As with + `embark-target-injection-hooks', there are two special keys for the + alists: `t' designates the default hook to run when no specific hook + is specified for a command; and the hook associated to `:always' runs + regardless. + + The default values of those variables are fairly extensive, adding + creature comforts to make running actions a smooth experience. Embark + comes with several functions intended to be added to these hooks, and + used in the default values of `embark-pre-action-hooks', + `embark-post-action-hooks' and `embark-around-action-hooks'. + + For pre-action hooks: + + `embark--confirm' + Prompt the user for confirmation before executing the + action. This is used be default for commands deemed "dangerous", + or, more accurately, hard to undo, such as `delete-file' and + `kill-buffer'. + + `embark--unmark-target' + Unmark the active region. Use this for commands you want to act + on the region contents but without the region being active. The + default configuration uses this function as a pre-action hook + for `occur' and `query-replace', for example, so that you can + use them as actions with region targets to search the whole + buffer for the text contained in the region. Without this + pre-action hook using `occur' as an action for a region target + would be pointless: it would search for the the region contents + /in the region/, (typically, due to the details of regexps) + finding only one match! + + `embark--beginning-of-target' + Move to the beginning of the target (for targets that report + bounds). This is used by default for backward motion commands + such as `backward-sexp', so that they don't accidentally leave + you on the current target. + + `embark--end-of-target' + Move to the end of the target. This is used similarly to the + previous function, but also for commands that act on the last + s-expression like `eval-last-sexp'. This allow you to act on an + s-expression from anywhere inside it and still use + `eval-last-sexp' as an action. + + `embark--xref-push-markers' + Push the current location on the xref marker stack. Use this for + commands that take you somewhere and for which you'd like to be + able to come back to where you were using + `xref-pop-marker-stack'. This is used by default for + `find-library'. + + For post-action hooks: + + `embark--restart' + Restart the command currently prompting in the minibuffer, so + that the list of completion candidates is updated. This is + useful as a post action hook for commands that delete or rename + a completion candidate; for example the default value of + `embark-post-action-hooks' uses it for `delete-file', + `kill-buffer', `rename-file', `rename-buffer', etc. + + For around-action hooks: + + `embark--mark-target' + Save existing mark and point location, mark the target and run + the action. Most targets at point outside the minibuffer report + which region of the buffer they correspond to (this is the + information used by `embark-highlight-indicator' to know what + portion of the buffer to highlight); this function marks that + region. It is useful as an around action hook for commands that + expect a region to be marked, for example, it is used by default + for `indent-region' so that it works on s-expression targets, or + for `fill-region' so that it works on paragraph targets. + + `embark--cd' + Run the action with `default-directory' set to the directory + associated to the current target. The target should be of type + `file', `buffer', `bookmark' or `library', and the associated + directory is what you'd expect in each case. + + `embark--narrow-to-target' + Run the action with buffer narrowed to current target. Use this + as an around hook to localize the effect of actions that don't + already work on just the region. In the default configuration it + is used for `repunctuate-sentences'. + + `embark--save-excursion' + Run the action restoring point at the end. The current default + configuration doesn't use this but it is available for users. + + +3.6 Creating your own keymaps +───────────────────────────── + + All internal keymaps are defined with the standard helper macro + `defvar-keymap'. For example a simple version of the file action + keymap could be defined as follows: + + ┌──── + │ (defvar-keymap embark-file-map + │ :doc "Example keymap with a few file actions" + │ :parent embark-general-map + │ "d" #'delete-file + │ "r" #'rename-file + │ "c" #'copy-file) + └──── + + These action keymaps are perfectly normal Emacs keymaps. You may want + to inherit from the `embark-general-map' if you want to access the + default Embark actions. Note that `embark-collect' and `embark-export' + are also made available via `embark-general-map'. + + +3.7 Defining actions for new categories of targets +────────────────────────────────────────────────── + + It is easy to configure Embark to provide actions for new types of + targets, either in the minibuffer or outside it. I present below two + very detailed examples of how to do this. At several points I'll + explain more than one way to proceed, typically with the easiest + option first. I include the alternative options since there will be + similar situations where the easiest option is not available. + + +3.7.1 New minibuffer target example - tab-bar tabs +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + As an example, take the new [tab bars] from Emacs 27. I'll explain how + to configure Embark to offer tab-specific actions when you use the + tab-bar-mode commands that mention tabs by name. The configuration + explained here is now built-in to Embark (and Marginalia), but it's + still a good self-contained example. In order to setup up tab actions + you would need to: (1) make sure Embark knows those commands deal with + tabs, (2) define a keymap for tab actions and configure Embark so it + knows that's the keymap you want. + + +[tab bars] + + +◊ 3.7.1.1 Telling Embark about commands that prompt for tabs by name + + For step (1), it would be great if the `tab-bar-mode' commands + reported the completion category `tab' when asking you for a tab with + completion. (All built-in Emacs commands that prompt for file names, + for example, do have metadata indicating that they want a `file'.) + They do not, unfortunately, and I will describe a couple of ways to + deal with this. + + Maybe the easiest thing is to configure [Marginalia] to enhance those + commands. All of the `tab-bar-*-tab-by-name' commands have the words + "tab by name" in the minibuffer prompt, so you can use: + + ┌──── + │ (add-to-list 'marginalia-prompt-categories '("tab by name" . tab)) + └──── + + That's it! But in case you are ever in a situation where you don't + already have commands that prompt for the targets you want, I'll + describe how writing your own command with appropriate `category' + metadata looks: + + ┌──── + │ (defun my-select-tab-by-name (tab) + │ (interactive + │ (list + │ (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) + │ (tab-bar-tabs)) + │ (user-error "No tabs found")))) + │ (completing-read + │ "Tabs: " + │ (lambda (string predicate action) + │ (if (eq action 'metadata) + │ '(metadata (category . tab)) + │ (complete-with-action + │ action tab-list string predicate))))))) + │ (tab-bar-select-tab-by-name tab)) + └──── + + As you can see, the built-in support for setting the category + meta-datum is not very easy to use or pretty to look at. To help with + this I recommend the `consult--read' function from the excellent + [Consult] package. With that function we can rewrite the command as + follows: + + ┌──── + │ (defun my-select-tab-by-name (tab) + │ (interactive + │ (list + │ (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) + │ (tab-bar-tabs)) + │ (user-error "No tabs found")))) + │ (consult--read tab-list + │ :prompt "Tabs: " + │ :category 'tab)))) + │ (tab-bar-select-tab-by-name tab)) + └──── + + Much nicer! No matter how you define the `my-select-tab-by-name' + command, the first approach with Marginalia and prompt detection has + the following advantages: you get the `tab' category for all the + `tab-bar-*-bar-by-name' commands at once, also, you enhance built-in + commands, instead of defining new ones. + + + [Marginalia] + + [Consult] + + +◊ 3.7.1.2 Defining and configuring a keymap for tab actions + + Let's say we want to offer select, rename and close actions for tabs + (in addition to Embark general actions, such as saving the tab name to + the kill-ring, which you get for free). Then this will do: + + ┌──── + │ (defvar-keymap embark-tab-actions + │ :doc "Keymap for actions for tab-bar tabs (when mentioned by name)." + │ :parent embark-general-map + │ "s" #'tab-bar-select-tab-by-name + │ "r" #'tab-bar-rename-tab-by-name + │ "k" #'tab-bar-close-tab-by-name) + │ + │ (add-to-list 'embark-keymap-alist '(tab . embark-tab-actions)) + └──── + + What if after using this for a while you feel closing the tab without + confirmation is dangerous? You have a couple of options: + + 1. You can keep using the `tab-bar-close-tab-by-name' command, but + have Embark ask you for confirmation: + ┌──── + │ (push #'embark--confirm + │ (alist-get 'tab-bar-close-tab-by-name + │ embark-pre-action-hooks)) + └──── + + 2. You can write your own command that prompts for confirmation and + use that instead of `tab-bar-close-tab-by-name' in the above + keymap: + ┌──── + │ (defun my-confirm-close-tab-by-name (tab) + │ (interactive "sTab to close: ") + │ (when (y-or-n-p (format "Close tab '%s'? " tab)) + │ (tab-bar-close-tab-by-name tab))) + └──── + + Notice that this is a command you can also use directly from `M-x' + independently of Embark. Using it from `M-x' leaves something to be + desired, though, since you don't get completion for the tab names. + You can fix this if you wish as described in the previous section. + + +3.7.2 New target example in regular buffers - short Wikipedia links +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + Say you want to teach Embark to treat text of the form + `wikipedia:Garry_Kasparov' in any regular buffer as a link to + Wikipedia, with actions to open the Wikipedia page in eww or an + external browser or to save the URL of the page in the kill-ring. We + can take advantage of the actions that Embark has preconfigured for + URLs, so all we need to do is teach Embark that + `wikipedia:Garry_Kasparov' stands for the URL + `https://en.wikipedia.org/wiki/Garry_Kasparov'. + + You can be as fancy as you want with the recognized syntax. Here, to + keep the example simple, I'll assume the link matches the regexp + `wikipedia:[[:alnum:]_]+'. We will write a function that looks for a + match surrounding point, and returns a dotted list of the form `'(url + URL-OF-THE-PAGE START . END)' where `START' and `END' are the buffer + positions bounding the target, and are used by Embark to highlight it + if you have `embark-highlight-indicator' included in the list + `embark-indicators'. (There are a couple of other options for the + return value of a target finder: the bounding positions are optional + and a single target finder is allowed to return multiple targets; see + the documentation for `embark-target-finders' for details.) + + ┌──── + │ (defun my-short-wikipedia-link () + │ "Target a link at point of the form wikipedia:Page_Name." + │ (save-excursion + │ (let* ((start (progn (skip-chars-backward "[:alnum:]_:") (point))) + │ (end (progn (skip-chars-forward "[:alnum:]_:") (point))) + │ (str (buffer-substring-no-properties start end))) + │ (save-match-data + │ (when (string-match "wikipedia:\\([[:alnum:]_]+\\)" str) + │ `(url + │ ,(format "https://en.wikipedia.org/wiki/%s" + │ (match-string 1 str)) + │ ,start . ,end)))))) + │ + │ (add-to-list 'embark-target-finders 'my-short-wikipedia-link) + └──── + + +4 How does Embark call the actions? +═══════════════════════════════════ + + Embark actions are normal Emacs commands, that is, functions with an + interactive specification. In order to execute an action, Embark calls + the command with `call-interactively', so the command reads user input + exactly as if run directly by the user. For example the command may + open a minibuffer and read a string (`read-from-minibuffer') or open a + completion interface (`completing-read'). If this happens, Embark + takes the target string and inserts it automatically into the + minibuffer, simulating user input this way. After inserting the + string, Embark exits the minibuffer, submitting the input. (The + immediate minibuffer exit can be disabled for specific actions in + order to allow editing the input; this is done by adding the + `embark--allow-edit' function to the appropriate entry of + `embark-target-injection-hooks'). Embark inserts the target string at + the first minibuffer opened by the action command, and if the command + happens to prompt the user for input more than once, the user still + interacts with the second and further prompts in the normal + fashion. Note that if a command does not prompt the user for input in + the minibuffer, Embark still allows you to use it as an action, but of + course, never inserts the target anywhere. (There are plenty of + examples in the default configuration of commands that do not prompt + the user bound to keys in the action maps, most of the region actions, + for instance.) + + This is how Embark manages to reuse normal commands as actions. The + mechanism allows you to use as Embark actions commands that were not + written with Embark in mind (and indeed almost all actions that are + bound by default in Embark's action keymaps are standard Emacs + commands). It also allows you to write new custom actions in such a + way that they are useful even without Embark. + + Staring from version 28.1, Emacs has a variable + `y-or-n-p-use-read-key', which when set to `t' causes `y-or-n-p' to + use `read-key' instead of `read-from-minibuffer'. Setting + `y-or-n-p-use-read-key' to `t' is recommended for Embark users because + it keeps Embark from attempting to insert the target at a `y-or-n-p' + prompt, which would almost never be sensible. Also consider this as a + warning to structure your own action commands so that if they use + `y-or-n-p', they do so only after the prompting for the target. + + Here is a simple example illustrating the various ways of reading + input from the user mentioned above. Bind the following commands to + the `embark-symbol-map' to be used as actions, then put the point on + some symbol and run them with `embark-act': + + ┌──── + │ (defun example-action-command1 () + │ (interactive) + │ (message "The input was `%s'." (read-from-minibuffer "Input: "))) + │ + │ (defun example-action-command2 (arg input1 input2) + │ (interactive "P\nsInput 1: \nsInput 2: ") + │ (message "The first input %swas `%s', and the second was `%s'." + │ (if arg "truly " "") + │ input1 + │ input2)) + │ + │ (defun example-action-command3 () + │ (interactive) + │ (message "Your selection was `%s'." + │ (completing-read "Select: " '("E" "M" "B" "A" "R" "K")))) + │ + │ (defun example-action-command4 () + │ (interactive) + │ (message "I don't prompt you for input and thus ignore the target!")) + │ + │ (keymap-set embark-symbol-map "X 1" #'example-action-command1) + │ (keymap-set embark-symbol-map "X 2" #'example-action-command2) + │ (keymap-set embark-symbol-map "X 3" #'example-action-command3) + │ (keymap-set embark-symbol-map "X 4" #'example-action-command4) + └──── + + Also note that if you are using the key bindings to call actions, you + can pass prefix arguments to actions in the normal way. For example, + you can use `C-u X2' with the above demonstration actions to make the + message printed by `example-action-command2' more emphatic. This + ability to pass prefix arguments to actions is useful for some actions + in the default configuration, such as + `embark-shell-command-on-buffer'. + + +4.1 Non-interactive functions as actions +──────────────────────────────────────── + + Alternatively, Embark does support one other type of action: a + non-interactive function of a single argument. The target is passed as + argument to the function. For example: + + ┌──── + │ (defun example-action-function (target) + │ (message "The target was `%s'." target)) + │ + │ (keymap-set embark-symbol-map "X 4" #'example-action-function) + └──── + + Note that normally binding non-interactive functions in a keymap is + useless, since when attempting to run them using the key binding you + get an error message similar to "Wrong type argument: commandp, + example-action-function". In general it is more flexible to write any + new Embark actions as commands, that is, as interactive functions, + because that way you can also run them directly, without Embark. But + there are a couple of reasons to use non-interactive functions as + actions: + + 1. You may already have the function lying around, and it is + convenient to simply reuse it. + + 2. For command actions the targets can only be simple string, with no + text properties. For certain advanced uses you may want the action + to receive a string /with/ some text properties, or even a + non-string target. + + +5 Embark, Marginalia and Consult +════════════════════════════════ + + Embark cooperates well with the [Marginalia] and [Consult] packages. + Neither of those packages is a dependency of Embark, but both are + highly recommended companions to Embark, for opposite reasons: + Marginalia greatly enhances Embark's usefulness, while Embark can help + enhance Consult. + + In the remainder of this section I'll explain what exactly Marginalia + does for Embark, and what Embark can do for Consult. + + +[Marginalia] + +[Consult] + +5.1 Marginalia +────────────── + + Embark comes with actions for symbols (commands, functions, variables + with actions such as finding the definition, looking up the + documentation, evaluating, etc.) in the `embark-symbol-map' keymap, + and for packages (actions like install, delete, browse url, etc.) in + the `embark-package-keymap'. + + Unfortunately Embark does not automatically offers you these keymaps + when relevant, because many built-in Emacs commands don't report + accurate category metadata. For example, a command like + `describe-package', which reads a package name from the minibuffer, + does not have metadata indicating this fact. + + In an earlier Embark version, there were functions to supply this + missing metadata, but they have been moved to Marginalia, which + augments many Emacs command to report accurate category metadata. + Simply activating `marginalia-mode' allows Embark to offer you the + package and symbol actions when appropriate again. Candidate + annotations in the Embark collect buffer are also provided by the + Marginalia package: + + • If you install Marginalia and activate `marginalia-mode', Embark + Collect buffers will use the Marginalia annotations automatically. + + • If you don't install Marginalia, you will see only the annotations + that come with Emacs (such as key bindings in `M-x', or the unicode + characters in `C-x 8 RET'). + + +5.2 Consult +─────────── + + The excellent Consult package provides many commands that use + minibuffer completion, via the `completing-read' function; plenty of + its commands can be considered enhanced versions of built-in Emacs + commands, and some are completely new functionality. One common + enhancement provided in all commands for which it makes sense is + preview functionality, for example `consult-buffer' will show you a + quick preview of a buffer before you actually switch to it. + + If you use both Consult and Embark you should install the + `embark-consult' package which provides integration between the + two. It provides exporters for several Consult commands and also + tweaks the behavior of many Consult commands when used as actions with + `embark-act' in subtle ways that you may not even notice, but make for + a smoother experience. You need only install it to get these benefits: + Embark will automatically load it after Consult if found. + + The `embark-consult' package provides the following exporters: + + • You can use `embark-export' from `consult-line', `consult-outline', + or `consult-mark' to obtain an `occur-mode' buffer. As with the + built-in `occur' command you use that buffer to jump to a match and + after that, you can then use `next-error' and `previous-error' to + navigate to other matches. You can also press `e' to activate + `occur-edit-mode' and edit the matches in place! + + • You can export from any of the Consult asynchronous search commands, + `consult-grep', `consult-git-grep', or `consult-ripgrep' to get a + `grep-mode' buffer. Here too you can use `next-error' and + `previous-error' to navigate among matches, and, if you install the + [wgrep] package, you can use it to edit the matches in place. + + In both cases, pressing `g' will rerun the Consult command you had + exported from and re-enter the input you had typed (which is similar + to reverting but a little more flexible). You can then proceed to + re-export if that's what you want, but you can also edit the input + changing the search terms or simply cancel if you see you are done + with that search. + + The `embark-consult' also contains some candidates collectors that + allow you to run `embark-live' to get a live-updating table of + contents for your buffer: + + • `embark-consult-outline-candidates' produces the outline headings of + the current buffer, using `consult-outline'. + • `embark-consult-imenu-candidates' produces the imenu items of the + current buffer, using `consult-imenu'. + • `embark-consult-imenu-or-outline-candidates' is a simple combination + of the two previous functions: it produces imenu items in buffers + deriving from `prog-mode' and otherwise outline headings. + + The way to configure `embark-live' (or `embark-collect' and + `embark-export' for that matter) to use one of these function is to + add it at the end of the `embark-candidate-collectors' list. The + `embark-consult' package by default adds the last one, which seems to + be the most sensible default. + + Besides those exporters and candidate collectors, the `embark-consult' + package provides many subtle tweaks and small integrations between + Embark and Consult. Some examples are: + + • When used as actions, the asynchronous search commands will search + only the files associated to the targets: if the targets /are/ + files, it searches those files; for buffers it will search either + the associated file if there is one, else all files in the buffer's + `default-directory'; for bookmarks it will search the file they + point to, same for Emacs Lisp libraries. This is particularly + powerful when using `embark-act-all' to act on multiple files at + once, for example you can use `consult-find' to search among file + /names/ and then `embark-act-all' and `consult-grep' to search + within the matching files. + + • For all other target types, those that do not have a sensible + notion of associated file, a Consult search command (asynchronous + or not) will search for the text of the target but leave the + minibuffer open so you can interact with the Consult command. + + • `consult-imenu' will search for the target and take you directly to + the location if it matches a unique imenu entry, otherwise it will + leave the minibuffer open so you can navigate among the matches. + + +[wgrep] + + +6 Related Packages +══════════════════ + + There are several packages that offer functionality similar to + Embark's. + + Acting on minibuffer completion candidates + The popular Ivy and Helm packages have support for acting on the + completion candidates of commands written using their APIs, and + there is an extensive ecosystem of packages meant for Helm and + for Ivy (the Ivy ones usually have "counsel" in the name) + providing commands and appropriate actions. + Acting on things at point + The built-in `context-menu-mode' provides a mouse-driven + context-sensitive configurable menu. The `do-at-point' package + by Philip Kaludercic (available on GNU ELPA), on the other hand + is keyboard-driven. + Collecting completion candidates into a buffer + The Ivy package has the command `ivy-occur' which is similar to + `embark-collect'. As with Ivy actions, `ivy-occur' only works + for commands written using the Ivy API. + + +7 Resources +═══════════ + + If you want to learn more about how others have used Embark here are + some links to read: + + • [Fifteen ways to use Embark], a blog post by Karthik Chikmagalur. + • [Protesilaos Stavrou's dotemacs], look for the section called + "Extended minibuffer actions and more (embark.el and + prot-embark.el)" + + And some videos to watch: + + • [Embark and my extras] by Protesilaos Stavrou. + • [Embark – Key features and tweaks] by Raoul Comninos on the + Emacs-Elements YouTube channel. + • [Livestreamed: Adding an Embark context action to send a stream + message] by Sacha Chua. + • [System Crafters Live! - The Many Uses of Embark] by David Wilson. + • [Using Emacs Episode 80 - Vertico, Marginalia, Consult and Embark] + by Mike Zamansky. + + +[Fifteen ways to use Embark] + + +[Protesilaos Stavrou's dotemacs] + +[Embark and my extras] + + +[Embark – Key features and tweaks] + +[Livestreamed: Adding an Embark context action to send a stream message] + + +[System Crafters Live! - The Many Uses of Embark] + + +[Using Emacs Episode 80 - Vertico, Marginalia, Consult and Embark] + + + +8 Contributions +═══════════════ + + Contributions to Embark are very welcome. There is a [wish list] for + actions, target finders, candidate collectors and exporters. For other + ideas you have for Embark, feel free to open an issue on the [issue + tracker]. Any neat configuration tricks you find might be a good fit + for the [wiki]. + + Code contributions are very welcome too, but since Embark is now on + GNU ELPA, copyright assignment to the FSF is required before you can + contribute code. + + +[wish list] + +[issue tracker] + +[wiki] + + +9 Acknowledgments +═════════════════ + + While I, Omar Antolín Camarena, have written most of the Embark code + and remain very stubborn about some of the design decisions, Embark + has received substantial help from a number of other people which this + document has neglected to mention for far too long. In particular, + Daniel Mendler has been absolutely invaluable, implementing several + important features, and providing a lot of useful advice. + + Code contributions: + + • [Daniel Mendler] + • [Clemens Radermacher] + • [José Antonio Ortega Ruiz] + • [Itai Y. Efrat] + • [a13] + • [jakanakaevangeli] + • [mihakam] + • [Brian Leung] + • [Karthik Chikmagalur] + • [Roshan Shariff] + • [condy0919] + • [Damien Cassou] + • [JimDBh] + + Advice and useful discussions: + + • [Daniel Mendler] + • [Protesilaos Stavrou] + • [Clemens Radermacher] + • [Howard Melman] + • [Augusto Stoffel] + • [Bruce d'Arcus] + • [JD Smith] + • [Karthik Chikmagalur] + • [jakanakaevangeli] + • [Itai Y. Efrat] + • [Mohsin Kaleem] + + +[Daniel Mendler] + +[Clemens Radermacher] + +[José Antonio Ortega Ruiz] + +[Itai Y. Efrat] + +[a13] + +[jakanakaevangeli] + +[mihakam] + +[Brian Leung] + +[Karthik Chikmagalur] + +[Roshan Shariff] + +[condy0919] + +[Damien Cassou] + +[JimDBh] + +[Protesilaos Stavrou] + +[Howard Melman] + +[Augusto Stoffel] + +[Bruce d'Arcus] + +[JD Smith] + +[Mohsin Kaleem] blob - /dev/null blob + 7af6a2f487aac027b13b6f61f94a81fcf8776255 (mode 644) --- /dev/null +++ elpa/embark-1.1/README.org @@ -0,0 +1,1276 @@ +#+TITLE: Embark: Emacs Mini-Buffer Actions Rooted in Keymaps +#+OPTIONS: d:nil +#+EXPORT_FILE_NAME: embark.texi +#+TEXINFO_DIR_CATEGORY: Emacs misc features +#+TEXINFO_DIR_TITLE: Embark: (embark). +#+TEXINFO_DIR_DESC: Emacs Mini-Buffer Actions Rooted in Keymaps + +#+html: GNU ELPA +#+html: GNU-devel ELPA +#+html: MELPA +#+html: MELPA Stable + +* Overview + +Embark makes it easy to choose a command to run based on what is near +point, both during a minibuffer completion session (in a way familiar +to Helm or Counsel users) and in normal buffers. Bind the command +=embark-act= to a key and it acts like prefix-key for a keymap of +/actions/ (commands) relevant to the /target/ around point. With point on +an URL in a buffer you can open the URL in a browser or eww or +download the file it points to. If while switching buffers you spot an +old one, you can kill it right there and continue to select another. +Embark comes preconfigured with over a hundred actions for common +types of targets such as files, buffers, identifiers, s-expressions, +sentences; and it is easy to add more actions and more target types. +Embark can also collect all the candidates in a minibuffer to an +occur-like buffer or export them to a buffer in a major-mode specific +to the type of candidates, such as dired for a set of files, ibuffer +for a set of buffers, or customize for a set of variables. + +** Acting on targets + +You can think of =embark-act= as a keyboard-based version of a +right-click contextual menu. The =embark-act= command (which you should +bind to a convenient key), acts as a prefix for a keymap offering you +relevant /actions/ to use on a /target/ determined by the context: + +- In the minibuffer, the target is the current top completion + candidate. +- In the =*Completions*= buffer the target is the completion at point. +- In a regular buffer, the target is the region if active, or else the + file, symbol, URL, s-expression or defun at point. + +Multiple targets can be present at the same location and you can cycle +between them by repeating the =embark-act= key binding. The type of +actions offered depend on the type of the target. Here is a sample of +a few of the actions offered in the default configuration: + +- For files you get offered actions like deleting, copying, + renaming, visiting in another window, running a shell command on the + file, etc. +- For buffers the actions include switching to or killing the buffer. +- For package names the actions include installing, removing or + visiting the homepage. +- For Emacs Lisp symbols the actions include finding the definition, + looking up documentation, evaluating (which for a variable + immediately shows the value, but for a function lets you pass it + some arguments first). There are some actions specific to variables, + such as setting the value directly or though the customize system, + and some actions specific to commands, such as binding it to a key. + +By default when you use =embark-act= if you don't immediately select an +action, after a short delay Embark will pop up a buffer showing a list +of actions and their corresponding key bindings. If you are using +=embark-act= outside the minibuffer, Embark will also highlight the +current target. These behaviors are configurable via the variable +=embark-indicators=. Instead of selecting an action via its key binding, +you can select it by name with completion by typing =C-h= after +=embark-act=. + +Everything is easily configurable: determining the current target, +classifying it, and deciding which actions are offered for each type +in the classification. The above introduction just mentions part of +the default configuration. + +Configuring which actions are offered for a type is particularly easy +and requires no programming: the variable =embark-keymap-alist= +associates target types with variables containing keymaps, and those +keymaps containing bindings for the actions. (To examine the available +categories and their associated keymaps, you can use =C-h v +embark-keymap-alist= or customize that variable.) For example, in the +default configuration the type =file= is associated with the symbol +=embark-file-map=. That symbol names a keymap with single-letter key +bindings for common Emacs file commands, for instance =c= is bound to +=copy-file=. This means that if you are in the minibuffer after running +a command that prompts for a file, such as =find-file= or =rename-file=, +you can copy a file by running =embark-act= and then pressing =c=. + +These action keymaps are very convenient but not strictly necessary +when using =embark-act=: you can use any command that reads from the +minibuffer as an action and the target of the action will be inserted +at the first minibuffer prompt. After running =embark-act= all of your +key bindings and even =execute-extended-command= can be used to run a +command. For example, if you want to replace all occurrences of the +symbol at point, just use =M-%= as the action, there is no need to bind +=query-replace= in one of Embark's keymaps. Also, those action keymaps +are normal Emacs keymaps and you should feel free to bind in them +whatever commands you find useful as actions and want to be available +through convenient bindings. + +The actions in =embark-general-map= are available no matter what type +of completion you are in the middle of. By default this includes +bindings to save the current candidate in the kill ring and to insert +the current candidate in the previously selected buffer (the buffer +that was current when you executed a command that opened up the +minibuffer). + +Emacs's minibuffer completion system includes metadata indicating the +/category/ of what is being completed. For example, =find-file='s +metadata indicates a category of =file= and =switch-to-buffer='s metadata +indicates a category of =buffer=. Embark has the related notion of the +/type/ of a target for actions, and by default when category metadata +is present it is taken to be the type of minibuffer completion +candidates when used as targets. Emacs commands often do not set +useful category metadata so the [[https://github.com/minad/marginalia][Marginalia]] package, which supplies +this missing metadata, is highly recommended for use with Embark. + +Embark's default configuration has actions for the following target +types: files, buffers, symbols, packages, URLs, bookmarks, and as a +somewhat special case, actions for when the region is active. You can +read about the [[https://github.com/oantolin/embark/wiki/Default-Actions][default actions and their key bindings]] on the GitHub +project wiki. + +** The default action on a target + +Embark has a notion of default action for a target: + +- If the target is a minibuffer completion candidate, then the default + action is whatever command opened the minibuffer in the first place. + For example if you run =kill-buffer=, then the default action will be + to kill buffers. +- If the target comes from a regular buffer (i.e., not a minibuffer), + then the default action is whatever is bound to =RET= in the keymap of + actions for that type of target. For example, in Embark's default + configuration for a URL found at point the default action is + =browse-url=, because =RET= is bound to =browse-url= in the =embark-url-map= + keymap. + +To run the default action you can press =RET= after running =embark-act=. +Note that if there are several different targets at a given location, +each has its own default action, so first cycle to the target you want +and then press =RET= to run the corresponding default action. + +There is also =embark-dwim= which runs the default action for the first +target found. It's pretty handy in non-minibuffer buffers: with +Embark's default configuration it will: + +- Open the file at point. +- Open the URL at point in a web browser (using the =browse-url= + command). +- Compose a new email to the email address at point. +- In an Emacs Lisp buffer, if point is on an opening parenthesis or + right after a closing one, it will evaluate the corresponding + expression. +- Go to the definition of an Emacs Lisp function, variable or macro at + point. +- Find the file corresponding to an Emacs Lisp library at point. + +** Working with sets of possible targets + +Besides acting individually on targets, Embark lets you work +collectively on a set of target /candidates/. For example, while you are +in the minibuffer the candidates are simply the possible completions +of your input. Embark provides three main commands to work on candidate +sets: + +- The =embark-act-all= command runs the same action on each of the + current candidates. It is just like using =embark-act= on each + candidate in turn. (Because you can easily act on many more + candidates than you meant to, by default Embark asks you to confirm + uses of =embark-act-all=; you can turn this off by setting the user + option =embark-confirm-act-all= to =nil=.) + +- The =embark-collect= command produces a buffer listing all the current + candidates, for you to peruse and run actions on at your leisure. + The candidates are displayed as a list showing additional + annotations. If any of the candidates contain newlines, then + horizontal lines are used to separate candidates. + + The Embark Collect buffer is somewhat "dired-like": you can select + and deselect candidates through =embark-select= (available as an + action in =embark-act=, bound to =SPC=; but you could also give it a + global key binding). In an Embark Collect buffer =embark-act= is bound + to =a= and =embark-act-all= is bound to =A=; =embark-act-all= will act on + all currently marked candidates if there any, and will act on all + candidates if none are marked. In particular, this means that =a SPC= + will toggle whether the candidate at point is selected, and =A SPC= + will select all candidates if none are selected, or deselect all + selected candidates if there are some. + +- The =embark-export= command tries to open a buffer in an appropriate + major mode for the set of candidates. If the candidates are files + export produces a Dired buffer; if they are buffers, you get an + Ibuffer buffer; and if they are packages you get a buffer in + package menu mode. + + If you use the grepping commands from the [[https://github.com/minad/consult/][Consult]] package, + =consult-grep=, =consult-git-grep= or =consult-ripgrep=, then you should + install the =embark-consult= package, which adds support for exporting a + list of grep results to an honest grep-mode buffer, on which you can + even use [[https://github.com/mhayashi1120/Emacs-wgrep][wgrep]] if you wish. + +When in doubt choosing between exporting and collecting, a good rule +of thumb is to always prefer =embark-export= since when an exporter to a +special major mode is available for a given type of target, it will be +more featureful than an Embark collect buffer, and if no such exporter +is configured the =embark-export= command falls back to the generic +=embark-collect=. + +These commands are always available as "actions" (although they do not +act on just the current target but on all candidates) for =embark-act= +and are bound to =A=, =S= (for "snapshot"), and =E=, respectively, in +=embark-general-map=. This means that you do not have to bind your own +key bindings for these (although you can, of course!), just a key +binding for =embark-act=. + +In Embark Collect or Embark Export buffers that were obtained by +running =embark-collect= or =embark-export= from within a minibuffer +completion session, =g= is bound to a command that restarts the +completion session, that is, the command that opened the minibuffer is +run again and the minibuffer contents restored. You can then interact +normally with the command, perhaps editing the minibuffer contents, +and, if you wish, you can rerun =embark-collect= or =embark-export= to get +an updated buffer. + +*** Selecting some targets to make an ad hoc candidate set + +The commands for working with sets of candidates just described, +namely =embark-act-all=, =embark-export= and =embark-collect= by default +work with all candidates defined in the current context. For example, +in the minibuffer they operate on all currently completion candidates, +or in a dired buffer they work on all marked files (or all files if +none are marked). Embark also has a notion of /selection/, where you can +accumulate an ad hoc list of targets for these commands to work on. + +The selection is controlled by using the =embark-select= action, bound +to =SPC= in =embark-general-map= so that it is always available (you can +also give =embark-select= a global key binding if you wish; when called +directly, not as an action for =embark-act=, it will select the first +target at point). Calling this action on a target toggles its +membership in the current buffer's Embark selection; that is, it adds +it to selection if not selected and removes it from the selection if +it was selected. Whenever the selection for a buffer is non-empty, the +commands =embark-act-all=, =embark-export= and =embark-collect= will act on +the selection. + +To deselect all selected targets, you can use the =embark-select= action +through =embark-act-all=, since this will run =embark-select= on each +member of the current selection. Similarly if no targets are selected +and you are in a minibuffer completion session, running =embark-select= +from =embark-act-all= will select all the current completion candidates. + +By default, whenever some targets are selected in the current buffer, +a count of selected targets appears in the mode line. This can be +turned off or customized through the =embark-selection-indicator= user +option. + +The selection functionality is supported in every buffer: + +- In the minibuffer this gives a convenient way to act on several + completion candidates that don't follow any simple pattern: just go + through the completions selecting the ones you want, then use + =embark-act-all=. For example, you could attach several files at once + to an email. +- For Embark Collect buffers this functionality enables a dired-like + workflow, in which you mark various candidates and apply an action + to all at once. (It supersedes a previous ad hoc dired-like + interface that was implemented only in Embark Collect buffers, with + a slightly different interface.) +- In a eww buffer you could use this to select various links you wish + to follow up on, and then collect them into a buffer. Similarly, + while reading Emacs's info manual you could select some symbols you + want to read more about and export them to an =apropos-mode= buffer. +- You can use selections in regular text or programming buffers to do + complex editing operations. For example, if you have three + paragraphs scattered over a file and you want to bring them + together, you can select each one, insert them all somewhere and + finally delete all of them (from their original locations). + +*** =embark-live= a live-updating variant of =embark-collect= + +Finally, there is also an =embark-live= variant of the =embark-collect= +command which automatically updates the collection after each change +in the source buffer. Users of a completion UI that automatically +updates and displays the candidate list (such as Vertico, Icomplete, +Fido-mode, or MCT) will probably not want to use +=embark-live= from the minibuffer as they will then have two live +updating displays of the completion candidates! + +A more likely use of =embark-live= is to be called from a regular buffer +to display a sort of live updating "table of contents" for the buffer. +This depends on having appropriate candidate collectors configured in +=embark-candidate-collectors=. There are not many in Embark's default +configuration, but you can try this experiment: open a dired buffer in +a directory that has very many files, mark a few, and run =embark-live=. +You'll get an Embark Collect buffer containing only the marked files, +which updates as you mark or unmark files in dired. To make +=embark-live= genuinely useful other candidate collectors are required. +The =embark-consult= package (documented near the end of this manual) +contains a few: one for imenu items and one for outline headings as +used by =outline-minor-mode=. Those collectors really do give +=embark-live= a table-of-contents feel. + +** Switching to a different command without losing what you've typed + +Embark also has the =embark-become= command which is useful for when +you run a command, start typing at the minibuffer and realize you +meant a different command. The most common case for me is that I run +=switch-to-buffer=, start typing a buffer name and realize I haven't +opened the file I had in mind yet! I'll use this situation as a +running example to illustrate =embark-become=. When this happens I can, +of course, press =C-g= and then run =find-file= and open the file, but +this requires retyping the portion of the file name you already +typed. This process can be streamlined with =embark-become=: while still +in the =switch-to-buffer= you can run =embark-become= and effectively +make the =switch-to-buffer= command become =find-file= for this run. + +You can bind =embark-become= to a key in =minibuffer-local-map=, but it is +also available as an action under the letter =B= (uppercase), so you +don't need a binding if you already have one for =embark-act=. So, +assuming I have =embark-act= bound to, say, =C-.=, once I realize I +haven't open the file I can type =C-. B C-x C-f= to have +=switch-to-buffer= become =find-file= without losing what I have already +typed in the minibuffer. + +But for even more convenience, =embark-become= offers shorter key +bindings for commands you are likely to want the current command to +become. When you use =embark-become= it looks for the current command in +all keymaps named in the list =embark-become-keymaps= and then activates +all keymaps that contain it. For example, the default value of +=embark-become-keymaps= contains a keymap =embark-become-file+buffer-map= +with bindings for several commands related to files and buffers, in +particular, it binds =switch-to-buffer= to =b= and =find-file= to =f=. So when +I accidentally try to switch to a buffer for a file I haven't opened +yet, =embark-become= finds that the command I ran, =switch-to-buffer=, is +in the keymap =embark-become-file+buffer-map=, so it activates that +keymap (and any others that also contain a binding for +=switch-to-buffer=). The end result is that I can type =C-. B f= to +switch to =find-file=. + +* Quick start + +The easiest way to install Embark is from GNU ELPA, just run =M-x +package-install RET embark RET=. (It is also available on MELPA.) It is +highly recommended to also install [[https://github.com/minad/marginalia][Marginalia]] (also available on GNU +ELPA), so that Embark can offer you preconfigured actions in more +contexts. For =use-package= users, the following is a very reasonable +starting configuration: + +#+begin_src emacs-lisp + (use-package marginalia + :ensure t + :config + (marginalia-mode)) + + (use-package embark + :ensure t + + :bind + (("C-." . embark-act) ;; pick some comfortable binding + ("C-;" . embark-dwim) ;; good alternative: M-. + ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' + + :init + + ;; Optionally replace the key help with a completing-read interface + (setq prefix-help-command #'embark-prefix-help-command) + + ;; Show the Embark target at point via Eldoc. You may adjust the + ;; Eldoc strategy, if you want to see the documentation from + ;; multiple providers. Beware that using this can be a little + ;; jarring since the message shown in the minibuffer can be more + ;; than one line, causing the modeline to move up and down: + + ;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target) + ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) + + :config + + ;; Hide the mode line of the Embark live/completions buffers + (add-to-list 'display-buffer-alist + '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" + nil + (window-parameters (mode-line-format . none))))) + + ;; Consult users will also want the embark-consult package. + (use-package embark-consult + :ensure t ; only need to install it, embark loads it after consult if found + :hook + (embark-collect-mode . consult-preview-at-point-mode)) +#+end_src + +About the suggested key bindings for =embark-act= and =embark-dwim=: +- Those key bindings are unlikely to work in the terminal, but + terminal users are probably well aware of this and will know to + select different bindings. +- The suggested =C-.= binding is used by default in (at least some + installations of) GNOME to input emojis, and Emacs doesn't even get + a chance to respond to the binding. You can select a different key + binding for =embark-act= or use =ibus-setup= to change the shortcut for + emoji insertion (Emacs 29 will likely use =C-x 8 e e=, in case you + want to set the same one system-wide). +- The suggested alternative of =M-.= for =embark-dwim= is bound by default + to =xref-find-definitions=. That is a very useful command but + overwriting it with =embark-dwim= is sensible since in Embark's + default configuration, =embark-dwim= will also find the definition of + the identifier at point. (Note that =xref-find-definitions= with a + prefix argument prompts you for an identifier, =embark-dwim= does not + cover this case). + +Other Embark commands such as =embark-act-all=, =embark-become=, +=embark-collect=, and =embark-export= can be run through =embark-act= as +actions bound to =A=, =B=, =S= (for "snapshot"), and =E= respectively, and +thus don't really need a dedicated key binding, but feel free to bind +them directly if you so wish. If you do choose to bind them directly, +you'll probably want to bind them in =minibuffer-local-map=, since they +are most useful in the minibuffer (in fact, =embark-become= only works +in the minibuffer). + +The command =embark-dwim= executes the default action at point. Another good +keybinding for =embark-dwim= is =M-.= since =embark-dwim= acts like +=xref-find-definitions= on the symbol at point. =C-.= can be seen as a +right-click context menu at point and =M-.= acts like left-click. The +keybindings are mnemonic, both act at the point (=.=). + +Embark needs to know what your minibuffer completion system considers +to be the list of candidates and which one is the current candidate. +Embark works out of the box if you use Emacs's default tab completion, +the built-in =icomplete-mode= or =fido-mode=, or the third-party packages +[[https://github.com/minad/vertico][Vertico]] or [[https://github.com/abo-abo/swiper][Ivy]]. + +If you are a [[https://emacs-helm.github.io/helm/][Helm]] or [[https://github.com/abo-abo/swiper][Ivy]] user you are unlikely to want Embark since +those packages include comprehensive functionality for acting on +minibuffer completion candidates. (Embark does come with Ivy +integration despite this.) + +* Advanced configuration +** Showing information about available targets and actions + +By default, if you run =embark-act= and do not immediately select an +action, after a short delay Embark will pop up a buffer called =*Embark +Actions*= containing a list of available actions with their key +bindings. You can scroll that buffer with the mouse of with the usual +commands =scroll-other-window= and =scroll-other-window-down= (bound by +default to =C-M-v= and =C-M-S-v=). + +That functionality is provided by the =embark-mixed-indicator=, but +Embark has other indicators that can provide information about the +target and its type, what other targets you can cycle to, and which +actions have key bindings in the action map for the current type of +target. Any number of indicators can be active at once and the user +option =embark-indicators= should be set to a list of the desired +indicators. + +Embark comes with the following indicators: + +- =embark-minimal-indicator=: shows a messages in the echo area or + minibuffer prompt showing the current target and the types of all + targets starting with the current one. + +- =embark-highlight-indicator=: highlights the target at point; on by + default. + +- =embark-verbose-indicator=: displays a table of actions and their key + bindings in a buffer; this is not on by default, in favor of the + mixed indicator described next. + +- =embark-mixed-indicator=: starts out by behaving as the minimal + indicator but after a short delay acts as the verbose indicator; + this is on by default. + +- =embark-isearch-highlight-indicator=: this only does something when + the current target is the symbol at point, in which case it + lazily highlights all occurrences of that symbol in the current + buffer, like isearch; also on by default. + +Users of the popular [[https://github.com/justbur/emacs-which-key][which-key]] package may prefer to use the +=embark-which-key-indicator= from the [[https://github.com/oantolin/embark/wiki/Additional-Configuration#use-which-key-like-a-key-menu-prompt][Embark wiki]]. Just copy its +definition from the wiki into your configuration and customize the +=embark-indicators= user option to exclude the mixed and verbose +indicators and to include =embark-which-key-indicator=. + +If you use [[https://github.com/minad/vertico][Vertico]], there is an even easier way to get a +=which-key=-like display that also lets you use completion to narrow +down the list of alternatives, described at the end of the next +section. + +** Selecting commands via completions instead of key bindings + +As an alternative to reading the list of actions in the verbose or +mixed indicators (see the previous section for a description of +these), you can press the =embark-help-key=, which is =C-h= by default +(but you may prefer =?= to free up =C-h= for use as a prefix) after +running =embark-act=. Pressing the help key will prompt you for the name +of an action with completion (but feel free to enter a command that is +not among the offered candidates!), and will also remind you of the +key bindings. You can press =embark-keymap-prompter-key=, which is =@= by +default, at the prompt and then one of the key bindings to enter the +name of the corresponding action. + +You may think that with the =*Embark Actions*= buffer popping up to +remind you of the key bindings you'd never want to use completion to +select an action by name, but personally I find that typing a small +portion of the action name to narrow down the list of candidates feels +significantly faster than visually scanning the entire list of actions. + +If you find you prefer selecting actions that way, you can configure +embark to always prompt you for actions by setting the variable +=embark-prompter= to =embark-completing-read-prompter=. + +On the other hand, you may wish to continue using key bindings for the +actions you perform most often, and to use completion only to explore +what further actions are available or when you've forgotten a key +binding. In that case, you may prefer to use the minimal indicator, +which does not pop-up an =*Embark Actions*= buffer at all, and to use +the =embark-help-key= whenever you need help. This unobtrusive setup is +achieved with the following configuration: + +#+begin_src emacs-lisp + (setq embark-indicators + '(embark-minimal-indicator ; default is embark-mixed-indicator + embark-highlight-indicator + embark-isearch-highlight-indicator)) +#+end_src + +[[https://github.com/minad/vertico][Vertico]] users may wish to configure a grid display for the actions and +key-bindings, reminiscent of the popular package [[https://github.com/justbur/emacs-which-key][which-key]], but, of +course, enhanced by the use of completion to narrow the list of +commands. In order to get the grid display, put the following in your +Vertico configuration: + +#+begin_src emacs-lisp + (add-to-list 'vertico-multiform-categories '(embark-keybinding grid)) + (vertico-multiform-mode) +#+end_src + +This will make the available keys be shown in a compact grid like in +=which-key=. The =vertico-multiform-mode= also enables keys such as =M-V=, +=M-G=, =M-B=, and =M-U= for manually switching between layouts in Vertico +buffers. + +*** Selecting commands via completion outside of Embark + +If you like this completion interface for exploring key bindings for +Embark actions, you may want to use it elsewhere in Emacs. You can use +Embark's completion-based command prompter to list: + +- key bindings under a prefix, +- local key bindings, or +- all key bindings. + +To use it for key bindings under a prefix (you can use this to replace +the =which-key= package, for example), use this configuration: + +#+begin_src emacs-lisp + (setq prefix-help-command #'embark-prefix-help-command) +#+end_src + +Now, when you have started on a prefix sequence such as =C-x= or =C-c=, +pressing =C-h= will bring up the Embark version of the built-in +=prefix-help-command=, which will list the keys under that prefix and +their bindings, and lets you select the one you wanted with completion, +or by key binding if you press =embark-keymap-prompter-key=. + +To list local or global key bindings, use the command =embark-bindings=. +You can bind that to =C-h b=, which is the default key binding for the +built-in =describe-bindings= command, which this command can replace. By +default, =embark-bindings= lists local key bindings, typically those +bound in the major mode keymap; to get global bindings as well, call +it with a =C-u= prefix argument. + +** Quitting the minibuffer after an action + +By default, if you call =embark-act= from the minibuffer it quits the +minibuffer after performing the action. You can change this by setting +the user option =embark-quit-after-action= to =nil=. Having =embark-act= /not/ +quit the minibuffer can be useful to turn commands into little "thing +managers". For example, you can use =find-file= as a little file manager +or =describe-package= as a little package manager: you can run those +commands, perform a series of actions, and then quit the command. + +If you want to control the quitting behavior in a fine-grained manner +depending on the action, you can set =embark-quit-after-action= to an +alist, associating commands to either =t= for quitting or =nil= for not +quitting. When using an alist, you can use the special key =t= to +specify the default behavior. For example, to specify that by default +actions should not quit the minibuffer but that using =kill-buffer= as +an action should quit, you can use the following configuration: + +#+begin_src emacs-lisp + (setq embark-quit-after-action '((kill-buffer . t) (t . nil))) +#+end_src + +The variable =embark-quit-after-action= only specifies a default, that +is, it only controls whether or not =embark-act= quits the minibuffer +when you call it without a prefix argument, and you can select the +opposite behavior to what the variable says by calling =embark-act= with +=C-u=. Also note that both the variable =embark-quit-after-action= and =C-u= +have no effect when you call =embark-act= outside the minibuffer. + +If you find yourself using the quitting and non-quitting variants of +=embark-act= about equally often, independently of the action, you may +prefer to simply have separate commands for them instead of a single +command that you call with =C-u= half the time. You could, for example, +keep the default exiting behavior of =embark-act= and define a +non-quitting version as follows: + +#+begin_src emacs-lisp + (defun embark-act-noquit () + "Run action but don't quit the minibuffer afterwards." + (interactive) + (let ((embark-quit-after-action nil)) + (embark-act))) +#+end_src + +** Running some setup after injecting the target + +You can customize what happens after the target is inserted at the +minibuffer prompt of an action. There are +=embark-target-injection-hooks=, that are run by default after injecting +the target into the minibuffer. The variable +=embark-target-injection-hooks= is an alist associating commands to +their setup hooks. There are two special keys: if no setup hook is +specified for a given action, the hook associated to =t= is run; and the +hook associated to =:always= is run regardless of the action. (This +variable used to have the less explicit name of +=embark-setup-action-hooks=, so please update your configuration.) + +For example, consider using =shell-command= as an action during file +completion. It would be useful to insert a space before the target +file name and to leave the point at the beginning, so you can +immediately type the shell command to run on that file. That's why in +Embark's default configuration there is an entry in +=embark-target-injection-hooks= associating =shell-command= to a hook that +includes =embark--shell-prep=, a simple helper function that quotes all +the spaces in the file name, inserts an extra space at the beginning +of the line and leaves point to the left of it. + +Now, the preparation that =embark--shell-prep= does would be useless if +Embark did what it normally does after it inserts the target of the +action at the minibuffer prompt, which is to "press =RET=" for you, +accepting the target as is; if Embark did that for =shell-command= you +wouldn't get a chance to type in the command to execute! That is why +in Embark's default configuration the entry for =shell-command= in +=embark-target-injection-hooks= also contains the function +=embark--allow-edit=. + +Embark used to have a dedicated variable =embark-allow-edit-actions= to +which you could add commands for which Embark should forgo pressing +=RET= for you after inserting the target. Since its effect can also be +achieved via the general =embark-target-injection-hooks= mechanism, that +variable has been removed to simplify Embark. Be sure to update your +configuration; if you had something like: + +#+begin_src emacs-lisp + (add-to-list 'embark-allow-edit-actions 'my-command) +#+end_src + +you should replace it with: + +#+begin_src emacs-lisp + (push 'embark--allow-edit + (alist-get 'my-command embark-target-injection-hooks)) +#+end_src + + +Also note that while you could abuse =embark--allow-edit= so that you +have to confirm "dangerous" actions such as =delete-file=, it is better +to implement confirmation by adding the =embark--confirm= function to +the appropriate entry of a different hook alist, namely, +=embark-pre-action-hooks=. + +Besides =embark--allow-edit=, Embark comes with another function that is +of general utility in action setup hooks: =embark--ignore-target=. Use +it for commands that do prompt you in the minibuffer but for which +inserting the target would be inappropriate. This is not a common +situation but does occasionally arise. For example it is used by +default for =shell-command-on-region=: that command is used as an action +for region targets, and it prompts you for a shell command; you +typically do /not/ want the target, that is the contents of the region, +to be entered at that prompt! + +** Running hooks before, after or around an action + +Embark has three variables, =embark-pre-action-hooks=, +=embark-post-action-hooks= and =embark-around-action-hooks=, which are +alists associating commands to hooks that should run before or after +or as around advice for the command when used as an action. As with +=embark-target-injection-hooks=, there are two special keys for the +alists: =t= designates the default hook to run when no specific hook is +specified for a command; and the hook associated to =:always= runs +regardless. + +The default values of those variables are fairly extensive, adding +creature comforts to make running actions a smooth experience. Embark +comes with several functions intended to be added to these hooks, and +used in the default values of =embark-pre-action-hooks=, +=embark-post-action-hooks= and =embark-around-action-hooks=. + +For pre-action hooks: + +- =embark--confirm= :: Prompt the user for confirmation before executing + the action. This is used be default for commands deemed "dangerous", + or, more accurately, hard to undo, such as =delete-file= and + =kill-buffer=. + +- =embark--unmark-target= :: Unmark the active region. Use this for + commands you want to act on the region contents but without the + region being active. The default configuration uses this function as + a pre-action hook for =occur= and =query-replace=, for example, so that + you can use them as actions with region targets to search the whole + buffer for the text contained in the region. Without this pre-action + hook using =occur= as an action for a region target would be + pointless: it would search for the the region contents /in the + region/, (typically, due to the details of regexps) finding only one + match! + +- =embark--beginning-of-target= :: Move to the beginning of the target + (for targets that report bounds). This is used by default for + backward motion commands such as =backward-sexp=, so that they don't + accidentally leave you on the current target. + +- =embark--end-of-target= :: Move to the end of the target. This is used + similarly to the previous function, but also for commands that act + on the last s-expression like =eval-last-sexp=. This allow you to act + on an s-expression from anywhere inside it and still use + =eval-last-sexp= as an action. + +- =embark--xref-push-markers= :: Push the current location on the xref + marker stack. Use this for commands that take you somewhere and for + which you'd like to be able to come back to where you were using + =xref-pop-marker-stack=. This is used by default for =find-library=. + +For post-action hooks: + +- =embark--restart= :: Restart the command currently prompting in the + minibuffer, so that the list of completion candidates is updated. + This is useful as a post action hook for commands that delete or + rename a completion candidate; for example the default value of + =embark-post-action-hooks= uses it for =delete-file=, =kill-buffer=, + =rename-file=, =rename-buffer=, etc. + +For around-action hooks: + +- =embark--mark-target= :: Save existing mark and point location, mark + the target and run the action. Most targets at point outside the + minibuffer report which region of the buffer they correspond to + (this is the information used by =embark-highlight-indicator= to + know what portion of the buffer to highlight); this function marks + that region. It is useful as an around action hook for commands that + expect a region to be marked, for example, it is used by default for + =indent-region= so that it works on s-expression targets, or for + =fill-region= so that it works on paragraph targets. + +- =embark--cd= :: Run the action with =default-directory= set to the + directory associated to the current target. The target should be of + type =file=, =buffer=, =bookmark= or =library=, and the associated directory + is what you'd expect in each case. + +- =embark--narrow-to-target= :: Run the action with buffer narrowed to + current target. Use this as an around hook to localize the effect of + actions that don't already work on just the region. In the default + configuration it is used for =repunctuate-sentences=. + +- =embark--save-excursion= :: Run the action restoring point at the end. + The current default configuration doesn't use this but it is + available for users. + +** Creating your own keymaps + +All internal keymaps are defined with the standard helper macro +=defvar-keymap=. For example a simple version of the file action keymap +could be defined as follows: + +#+BEGIN_SRC emacs-lisp + (defvar-keymap embark-file-map + :doc "Example keymap with a few file actions" + :parent embark-general-map + "d" #'delete-file + "r" #'rename-file + "c" #'copy-file) +#+END_SRC + +These action keymaps are perfectly normal Emacs +keymaps. You may want to inherit from the =embark-general-map= if you +want to access the default Embark actions. Note that =embark-collect= +and =embark-export= are also made available via =embark-general-map=. + +** Defining actions for new categories of targets + +It is easy to configure Embark to provide actions for new types of +targets, either in the minibuffer or outside it. I present below two +very detailed examples of how to do this. At several points I'll +explain more than one way to proceed, typically with the easiest +option first. I include the alternative options since there will be +similar situations where the easiest option is not available. + +*** New minibuffer target example - tab-bar tabs + +As an example, take the new [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Tab-Bars.html][tab bars]] from Emacs 27. I'll explain how +to configure Embark to offer tab-specific actions when you use the +tab-bar-mode commands that mention tabs by name. The configuration +explained here is now built-in to Embark (and Marginalia), but it's +still a good self-contained example. In order to setup up tab actions +you would need to: (1) make sure Embark knows those commands deal with +tabs, (2) define a keymap for tab actions and configure Embark so it +knows that's the keymap you want. + +**** Telling Embark about commands that prompt for tabs by name + +For step (1), it would be great if the =tab-bar-mode= commands reported +the completion category =tab= when asking you for a tab with +completion. (All built-in Emacs commands that prompt for file names, +for example, do have metadata indicating that they want a =file=.) They +do not, unfortunately, and I will describe a couple of ways to deal +with this. + +Maybe the easiest thing is to configure [[https://github.com/minad/marginalia][Marginalia]] to enhance those +commands. All of the =tab-bar-*-tab-by-name= commands have the words +"tab by name" in the minibuffer prompt, so you can use: + +#+begin_src emacs-lisp + (add-to-list 'marginalia-prompt-categories '("tab by name" . tab)) +#+end_src + +That's it! But in case you are ever in a situation where you don't +already have commands that prompt for the targets you want, I'll +describe how writing your own command with appropriate =category= +metadata looks: + +#+begin_src emacs-lisp + (defun my-select-tab-by-name (tab) + (interactive + (list + (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) + (tab-bar-tabs)) + (user-error "No tabs found")))) + (completing-read + "Tabs: " + (lambda (string predicate action) + (if (eq action 'metadata) + '(metadata (category . tab)) + (complete-with-action + action tab-list string predicate))))))) + (tab-bar-select-tab-by-name tab)) +#+end_src + +As you can see, the built-in support for setting the category +meta-datum is not very easy to use or pretty to look at. To help with +this I recommend the =consult--read= function from the excellent +[[https://github.com/minad/consult/][Consult]] package. With that function we can rewrite the command as +follows: + +#+begin_src emacs-lisp + (defun my-select-tab-by-name (tab) + (interactive + (list + (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) + (tab-bar-tabs)) + (user-error "No tabs found")))) + (consult--read tab-list + :prompt "Tabs: " + :category 'tab)))) + (tab-bar-select-tab-by-name tab)) +#+end_src + +Much nicer! No matter how you define the =my-select-tab-by-name= +command, the first approach with Marginalia and prompt detection has +the following advantages: you get the =tab= category for all the +=tab-bar-*-bar-by-name= commands at once, also, you enhance built-in +commands, instead of defining new ones. + +**** Defining and configuring a keymap for tab actions + + Let's say we want to offer select, rename and close actions for tabs + (in addition to Embark general actions, such as saving the tab name to + the kill-ring, which you get for free). Then this will do: + + #+begin_src emacs-lisp + (defvar-keymap embark-tab-actions + :doc "Keymap for actions for tab-bar tabs (when mentioned by name)." + :parent embark-general-map + "s" #'tab-bar-select-tab-by-name + "r" #'tab-bar-rename-tab-by-name + "k" #'tab-bar-close-tab-by-name) + + (add-to-list 'embark-keymap-alist '(tab . embark-tab-actions)) + #+end_src + + What if after using this for a while you feel closing the tab + without confirmation is dangerous? You have a couple of options: + + 1. You can keep using the =tab-bar-close-tab-by-name= command, but have + Embark ask you for confirmation: + #+begin_src emacs-lisp + (push #'embark--confirm + (alist-get 'tab-bar-close-tab-by-name + embark-pre-action-hooks)) + #+end_src + + 2. You can write your own command that prompts for confirmation and + use that instead of =tab-bar-close-tab-by-name= in the above keymap: + #+begin_src emacs-lisp + (defun my-confirm-close-tab-by-name (tab) + (interactive "sTab to close: ") + (when (y-or-n-p (format "Close tab '%s'? " tab)) + (tab-bar-close-tab-by-name tab))) + #+end_src + + Notice that this is a command you can also use directly from =M-x= + independently of Embark. Using it from =M-x= leaves something to be + desired, though, since you don't get completion for the tab names. + You can fix this if you wish as described in the previous section. + +*** New target example in regular buffers - short Wikipedia links + +Say you want to teach Embark to treat text of the form +=wikipedia:Garry_Kasparov= in any regular buffer as a link to Wikipedia, +with actions to open the Wikipedia page in eww or an external browser +or to save the URL of the page in the kill-ring. We can take advantage +of the actions that Embark has preconfigured for URLs, so all we need +to do is teach Embark that =wikipedia:Garry_Kasparov= stands for the URL +=https://en.wikipedia.org/wiki/Garry_Kasparov=. + +You can be as fancy as you want with the recognized syntax. Here, to +keep the example simple, I'll assume the link matches the regexp +=wikipedia:[[:alnum:]_]+=. We will write a function that looks for a +match surrounding point, and returns a dotted list of the form ='(url +URL-OF-THE-PAGE START . END)= where =START= and =END= are the buffer +positions bounding the target, and are used by Embark to highlight it +if you have =embark-highlight-indicator= included in the list +=embark-indicators=. (There are a couple of other options for the return +value of a target finder: the bounding positions are optional and a +single target finder is allowed to return multiple targets; see the +documentation for =embark-target-finders= for details.) + +#+begin_src emacs-lisp + (defun my-short-wikipedia-link () + "Target a link at point of the form wikipedia:Page_Name." + (save-excursion + (let* ((start (progn (skip-chars-backward "[:alnum:]_:") (point))) + (end (progn (skip-chars-forward "[:alnum:]_:") (point))) + (str (buffer-substring-no-properties start end))) + (save-match-data + (when (string-match "wikipedia:\\([[:alnum:]_]+\\)" str) + `(url + ,(format "https://en.wikipedia.org/wiki/%s" + (match-string 1 str)) + ,start . ,end)))))) + + (add-to-list 'embark-target-finders 'my-short-wikipedia-link) +#+end_src + +* How does Embark call the actions? + + Embark actions are normal Emacs commands, that is, functions with an + interactive specification. In order to execute an action, Embark + calls the command with =call-interactively=, so the command reads user + input exactly as if run directly by the user. For example the + command may open a minibuffer and read a string + (=read-from-minibuffer=) or open a completion interface + (=completing-read=). If this happens, Embark takes the target string + and inserts it automatically into the minibuffer, simulating user + input this way. After inserting the string, Embark exits the + minibuffer, submitting the input. (The immediate minibuffer exit can + be disabled for specific actions in order to allow editing the + input; this is done by adding the =embark--allow-edit= function to the + appropriate entry of =embark-target-injection-hooks=). Embark inserts + the target string at the first minibuffer opened by the action + command, and if the command happens to prompt the user for input + more than once, the user still interacts with the second and further + prompts in the normal fashion. Note that if a command does not + prompt the user for input in the minibuffer, Embark still allows you + to use it as an action, but of course, never inserts the target + anywhere. (There are plenty of examples in the default configuration + of commands that do not prompt the user bound to keys in the action + maps, most of the region actions, for instance.) + + This is how Embark manages to reuse normal commands as actions. The + mechanism allows you to use as Embark actions commands that were not + written with Embark in mind (and indeed almost all actions that are + bound by default in Embark's action keymaps are standard Emacs + commands). It also allows you to write new custom actions in such a + way that they are useful even without Embark. + + Staring from version 28.1, Emacs has a variable + =y-or-n-p-use-read-key=, which when set to =t= causes =y-or-n-p= to use + =read-key= instead of =read-from-minibuffer=. Setting + =y-or-n-p-use-read-key= to =t= is recommended for Embark users because + it keeps Embark from attempting to insert the target at a =y-or-n-p= + prompt, which would almost never be sensible. Also consider this as + a warning to structure your own action commands so that if they use + =y-or-n-p=, they do so only after the prompting for the target. + + Here is a simple example illustrating the various ways of reading + input from the user mentioned above. Bind the following commands to + the =embark-symbol-map= to be used as actions, then put the point on + some symbol and run them with =embark-act=: + + #+begin_src emacs-lisp + (defun example-action-command1 () + (interactive) + (message "The input was `%s'." (read-from-minibuffer "Input: "))) + + (defun example-action-command2 (arg input1 input2) + (interactive "P\nsInput 1: \nsInput 2: ") + (message "The first input %swas `%s', and the second was `%s'." + (if arg "truly " "") + input1 + input2)) + + (defun example-action-command3 () + (interactive) + (message "Your selection was `%s'." + (completing-read "Select: " '("E" "M" "B" "A" "R" "K")))) + + (defun example-action-command4 () + (interactive) + (message "I don't prompt you for input and thus ignore the target!")) + + (keymap-set embark-symbol-map "X 1" #'example-action-command1) + (keymap-set embark-symbol-map "X 2" #'example-action-command2) + (keymap-set embark-symbol-map "X 3" #'example-action-command3) + (keymap-set embark-symbol-map "X 4" #'example-action-command4) + #+end_src + + Also note that if you are using the key bindings to call actions, + you can pass prefix arguments to actions in the normal way. For + example, you can use =C-u X2= with the above demonstration actions to + make the message printed by =example-action-command2= more emphatic. + This ability to pass prefix arguments to actions is useful for some + actions in the default configuration, such as + =embark-shell-command-on-buffer=. + +** Non-interactive functions as actions + + Alternatively, Embark does support one other type of action: a + non-interactive function of a single argument. The target is passed + as argument to the function. For example: + + #+begin_src emacs-lisp + (defun example-action-function (target) + (message "The target was `%s'." target)) + + (keymap-set embark-symbol-map "X 4" #'example-action-function) + #+end_src + + Note that normally binding non-interactive functions in a keymap is + useless, since when attempting to run them using the key binding you + get an error message similar to "Wrong type argument: commandp, + example-action-function". In general it is more flexible to write + any new Embark actions as commands, that is, as interactive + functions, because that way you can also run them directly, without + Embark. But there are a couple of reasons to use non-interactive + functions as actions: + + 1. You may already have the function lying around, and it is + convenient to simply reuse it. + + 2. For command actions the targets can only be simple string, with + no text properties. For certain advanced uses you may want the + action to receive a string /with/ some text properties, or even a + non-string target. + +* Embark, Marginalia and Consult + +Embark cooperates well with the [[https://github.com/minad/marginalia][Marginalia]] and [[https://github.com/minad/consult][Consult]] packages. +Neither of those packages is a dependency of Embark, but both are +highly recommended companions to Embark, for opposite reasons: +Marginalia greatly enhances Embark's usefulness, while Embark can help +enhance Consult. + +In the remainder of this section I'll explain what exactly Marginalia +does for Embark, and what Embark can do for Consult. + +** Marginalia + +Embark comes with actions for symbols (commands, functions, variables +with actions such as finding the definition, looking up the +documentation, evaluating, etc.) in the =embark-symbol-map= keymap, and +for packages (actions like install, delete, browse url, etc.) in the +=embark-package-keymap=. + +Unfortunately Embark does not automatically offers you these keymaps +when relevant, because many built-in Emacs commands don't report +accurate category metadata. For example, a command like +=describe-package=, which reads a package name from the minibuffer, +does not have metadata indicating this fact. + +In an earlier Embark version, there were functions to supply this +missing metadata, but they have been moved to Marginalia, which +augments many Emacs command to report accurate category metadata. +Simply activating =marginalia-mode= allows Embark to offer you the +package and symbol actions when appropriate again. Candidate +annotations in the Embark collect buffer are also provided by the +Marginalia package: + +- If you install Marginalia and activate =marginalia-mode=, Embark + Collect buffers will use the Marginalia annotations automatically. + +- If you don't install Marginalia, you will see only the annotations + that come with Emacs (such as key bindings in =M-x=, or the unicode + characters in =C-x 8 RET=). + +** Consult + +The excellent Consult package provides many commands that use +minibuffer completion, via the =completing-read= function; plenty of its +commands can be considered enhanced versions of built-in Emacs +commands, and some are completely new functionality. One common +enhancement provided in all commands for which it makes sense is +preview functionality, for example =consult-buffer= will show you a +quick preview of a buffer before you actually switch to it. + +If you use both Consult and Embark you should install the +=embark-consult= package which provides integration between the two. It +provides exporters for several Consult commands and also tweaks the +behavior of many Consult commands when used as actions with =embark-act= +in subtle ways that you may not even notice, but make for a smoother +experience. You need only install it to get these benefits: Embark +will automatically load it after Consult if found. + +The =embark-consult= package provides the following exporters: + +- You can use =embark-export= from =consult-line=, =consult-outline=, or + =consult-mark= to obtain an =occur-mode= buffer. As with the built-in + =occur= command you use that buffer to jump to a match and after that, + you can then use =next-error= and =previous-error= to navigate to other + matches. You can also press =e= to activate =occur-edit-mode= and edit + the matches in place! + +- You can export from any of the Consult asynchronous search commands, + =consult-grep=, =consult-git-grep=, or =consult-ripgrep= to get a + =grep-mode= buffer. Here too you can use =next-error= and =previous-error= + to navigate among matches, and, if you install the [[http://github.com/mhayashi1120/Emacs-wgrep/raw/master/wgrep.el ][wgrep]] package, + you can use it to edit the matches in place. + +In both cases, pressing =g= will rerun the Consult command you had +exported from and re-enter the input you had typed (which is similar +to reverting but a little more flexible). You can then proceed to +re-export if that's what you want, but you can also edit the input +changing the search terms or simply cancel if you see you are done +with that search. + +The =embark-consult= also contains some candidates collectors that allow +you to run =embark-live= to get a live-updating table of contents for +your buffer: + +- =embark-consult-outline-candidates= produces the outline headings of + the current buffer, using =consult-outline=. +- =embark-consult-imenu-candidates= produces the imenu items of + the current buffer, using =consult-imenu=. +- =embark-consult-imenu-or-outline-candidates= is a simple combination + of the two previous functions: it produces imenu items in buffers + deriving from =prog-mode= and otherwise outline headings. + +The way to configure =embark-live= (or =embark-collect= and =embark-export= +for that matter) to use one of these function is to add it at the end +of the =embark-candidate-collectors= list. The =embark-consult= package by +default adds the last one, which seems to be the most sensible +default. + +Besides those exporters and candidate collectors, the =embark-consult= +package provides many subtle tweaks and small integrations between +Embark and Consult. Some examples are: + +- When used as actions, the asynchronous search commands will search + only the files associated to the targets: if the targets /are/ files, + it searches those files; for buffers it will search either the + associated file if there is one, else all files in the buffer's + =default-directory=; for bookmarks it will search the file they point + to, same for Emacs Lisp libraries. This is particularly powerful + when using =embark-act-all= to act on multiple files at once, for + example you can use =consult-find= to search among file /names/ and then + =embark-act-all= and =consult-grep= to search within the matching files. + + - For all other target types, those that do not have a sensible + notion of associated file, a Consult search command (asynchronous + or not) will search for the text of the target but leave the + minibuffer open so you can interact with the Consult command. + +- =consult-imenu= will search for the target and take you directly to + the location if it matches a unique imenu entry, otherwise it will + leave the minibuffer open so you can navigate among the matches. + +* Related Packages + +There are several packages that offer functionality similar +to Embark's. + +- Acting on minibuffer completion candidates :: The popular Ivy and + Helm packages have support for acting on the completion candidates + of commands written using their APIs, and there is an extensive + ecosystem of packages meant for Helm and for Ivy (the Ivy ones + usually have "counsel" in the name) providing commands and + appropriate actions. +- Acting on things at point :: The built-in =context-menu-mode= provides + a mouse-driven context-sensitive configurable menu. The =do-at-point= + package by Philip Kaludercic (available on GNU ELPA), on the other + hand is keyboard-driven. +- Collecting completion candidates into a buffer :: The Ivy package + has the command =ivy-occur= which is similar to =embark-collect=. As + with Ivy actions, =ivy-occur= only works for commands written using + the Ivy API. + +* Resources + +If you want to learn more about how others have used Embark here are +some links to read: + +- [[https://karthinks.com/software/fifteen-ways-to-use-embark/][Fifteen ways to use Embark]], a blog post by Karthik Chikmagalur. +- [[https://protesilaos.com/dotemacs/][Protesilaos Stavrou's dotemacs]], look for the section called + "Extended minibuffer actions and more (embark.el and + prot-embark.el)" + +And some videos to watch: + +- [[https://protesilaos.com/codelog/2021-01-09-emacs-embark-extras/][Embark and my extras]] by Protesilaos Stavrou. +- [[https://youtu.be/qpoQiiinCtY][Embark -- Key features and tweaks]] by Raoul Comninos on the + Emacs-Elements YouTube channel. +- [[https://youtu.be/WsxXr1ncukY][Livestreamed: Adding an Embark context action to send a stream + message]] by Sacha Chua. +- [[https://youtu.be/qk2Is_sC8Lk][System Crafters Live! - The Many Uses of Embark]] by David Wilson. +- [[https://youtu.be/5ffb2at2d7w][Using Emacs Episode 80 - Vertico, Marginalia, Consult and Embark]] by + Mike Zamansky. + +* Contributions + +Contributions to Embark are very welcome. There is a [[https://github.com/oantolin/embark/issues/95][wish list]] for +actions, target finders, candidate collectors and exporters. For other +ideas you have for Embark, feel free to open an issue on the [[https://github.com/oantolin/embark/issues][issue +tracker]]. Any neat configuration tricks you find might be a good fit +for the [[https://github.com/oantolin/embark/wiki][wiki]]. + +Code contributions are very welcome too, but since Embark is now on +GNU ELPA, copyright assignment to the FSF is required before you can +contribute code. + +* Acknowledgments + +While I, Omar Antolín Camarena, have written most of the Embark code +and remain very stubborn about some of the design decisions, Embark +has received substantial help from a number of other people which this +document has neglected to mention for far too long. In particular, +Daniel Mendler has been absolutely invaluable, implementing several +important features, and providing a lot of useful advice. + +Code contributions: + +- [[https://github.com/minad][Daniel Mendler]] +- [[https://github.com/clemera/][Clemens Radermacher]] +- [[https://codeberg.org/jao/][José Antonio Ortega Ruiz]] +- [[https://github.com/iyefrat][Itai Y. Efrat]] +- [[https://github.com/a13][a13]] +- [[https://github.com/jakanakaevangeli][jakanakaevangeli]] +- [[https://github.com/mihakam][mihakam]] +- [[https://github.com/leungbk][Brian Leung]] +- [[https://github.com/karthink][Karthik Chikmagalur]] +- [[https://github.com/roshanshariff][Roshan Shariff]] +- [[https://github.com/condy0919][condy0919]] +- [[https://github.com/DamienCassou][Damien Cassou]] +- [[https://github.com/JimDBh][JimDBh]] + +Advice and useful discussions: + +- [[https://github.com/minad][Daniel Mendler]] +- [[https://gitlab.com/protesilaos/][Protesilaos Stavrou]] +- [[https://github.com/clemera/][Clemens Radermacher]] +- [[https://github.com/hmelman/][Howard Melman]] +- [[https://github.com/astoff][Augusto Stoffel]] +- [[https://github.com/bdarcus][Bruce d'Arcus]] +- [[https://github.com/jdtsmith][JD Smith]] +- [[https://github.com/karthink][Karthik Chikmagalur]] +- [[https://github.com/jakanakaevangeli][jakanakaevangeli]] +- [[https://github.com/iyefrat][Itai Y. Efrat]] +- [[https://github.com/mohkale][Mohsin Kaleem]] blob - /dev/null blob + 818863058651d778ade67038f9bcad5661f59022 (mode 644) --- /dev/null +++ elpa/embark-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 +* Embark: (embark). Emacs Mini-Buffer Actions Rooted in Keymaps. blob - /dev/null blob + 98111a655213e6973411d6a0ababf40e760ae5bb (mode 644) --- /dev/null +++ elpa/embark-1.1/embark-autoloads.el @@ -0,0 +1,210 @@ +;;; embark-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 embark.el + +(defun embark--record-this-command nil "\ +Record command which opened the minibuffer. +We record this because it will be the default action. +This function is meant to be added to `minibuffer-setup-hook'." (setq-local embark--command this-command)) +(add-hook 'minibuffer-setup-hook #'embark--record-this-command) +(autoload 'embark-eldoc-first-target "embark" "\ +Eldoc function reporting the first Embark target at point. +This function uses the eldoc REPORT callback and is meant to be +added to `eldoc-documentation-functions'. + +(fn REPORT &rest _)") +(autoload 'embark-eldoc-target-types "embark" "\ +Eldoc function reporting the types of all Embark targets at point. +This function uses the eldoc REPORT callback and is meant to be +added to `eldoc-documentation-functions'. + +(fn REPORT &rest _)") +(autoload 'embark-bindings-in-keymap "embark" "\ +Explore command key bindings in KEYMAP with `completing-read'. +The selected command will be executed. Interactively, prompt the +user for a KEYMAP variable. + +(fn KEYMAP)" t) +(autoload 'embark-bindings "embark" "\ +Explore current command key bindings with `completing-read'. +The selected command will be executed. + +This shows key bindings from minor mode maps and the local +map (usually set by the major mode), but also less common keymaps +such as those from a text property or overlay, or the overriding +maps: `overriding-terminal-local-map' and `overriding-local-map'. + +Additionally, if GLOBAL is non-nil (interactively, if called with +a prefix argument), this command includes global key bindings. + +(fn GLOBAL)" t) +(autoload 'embark-bindings-at-point "embark" "\ +Explore all key bindings at point with `completing-read'. +The selected command will be executed. + +This command lists key bindings found in keymaps specified by the +text properties `keymap' or `local-map', from either buffer text +or an overlay. These are not widely used in Emacs, and when they +are used can be somewhat hard to discover. Examples of locations +that have such a keymap are links and images in `eww' buffers, +attachment links in `gnus' article buffers, and the stash line +in a `vc-dir' buffer." t) +(autoload 'embark-prefix-help-command "embark" "\ +Prompt for and run a command bound in the prefix used for this command. +The prefix described consists of all but the last event of the +key sequence that ran this command. This function is intended to +be used as a value for `prefix-help-command'. + +In addition to using completion to select a command, you can also +type @ and the key binding (without the prefix)." t) +(autoload 'embark-act "embark" "\ +Prompt the user for an action and perform it. +The targets of the action are chosen by `embark-target-finders'. +By default, if called from a minibuffer the target is the top +completion candidate. When called from a non-minibuffer buffer +there can multiple targets and you can cycle among them by using +`embark-cycle' (which is bound by default to the same key +binding `embark-act' is, but see `embark-cycle-key'). + +This command uses `embark-prompter' to ask the user to specify an +action, and calls it injecting the target at the first minibuffer +prompt. + +If you call this from the minibuffer, it can optionally quit the +minibuffer. The variable `embark-quit-after-action' controls +whether calling `embark-act' with nil ARG quits the minibuffer, +and if ARG is non-nil it will do the opposite. Interactively, +ARG is the prefix argument. + +If instead you call this from outside the minibuffer, the first +ARG targets are skipped over (if ARG is negative the skipping is +done by cycling backwards) and cycling starts from the following +target. + +(fn &optional ARG)" t) +(autoload 'embark-act-all "embark" "\ +Prompt the user for an action and perform it on each candidate. +The candidates are chosen by `embark-candidate-collectors'. By +default, if `embark-select' has been used to select some +candidates, then `embark-act-all' will act on those candidates; +otherwise, if the selection is empty and `embark-act-all' is +called from a minibuffer, then the candidates are the completion +candidates. + +This command uses `embark-prompter' to ask the user to specify an +action, and calls it injecting the target at the first minibuffer +prompt. + +If you call this from the minibuffer, it can optionally quit the +minibuffer. The variable `embark-quit-after-action' controls +whether calling `embark-act' with nil ARG quits the minibuffer, +and if ARG is non-nil it will do the opposite. Interactively, +ARG is the prefix argument. + +(fn &optional ARG)" t) +(autoload 'embark-dwim "embark" "\ +Run the default action on the current target. +The target of the action is chosen by `embark-target-finders'. + +If the target comes from minibuffer completion, then the default +action is the command that opened the minibuffer in the first +place, unless overridden by `embark-default-action-overrides'. + +For targets that do not come from minibuffer completion +(typically some thing at point in a regular buffer) and whose +type is not listed in `embark-default-action-overrides', the +default action is given by whatever binding RET has in the action +keymap for the target's type. + +See `embark-act' for the meaning of the prefix ARG. + +(fn &optional ARG)" t) +(autoload 'embark-become "embark" "\ +Make current command become a different command. +Take the current minibuffer input as initial input for new +command. The new command can be run normally using key bindings or +\\[execute-extended-command], but if the current command is found in a keymap in +`embark-become-keymaps', that keymap is activated to provide +convenient access to the other commands in it. + +If FULL is non-nil (interactively, if called with a prefix +argument), the entire minibuffer contents are used as the initial +input of the new command. By default only the part of the +minibuffer contents between the current completion boundaries is +taken. What this means is fairly technical, but (1) usually +there is no difference: the completion boundaries include the +entire minibuffer contents, and (2) the most common case where +these notions differ is file completion, in which case the +completion boundaries single out the path component containing +point. + +(fn &optional FULL)" t) +(autoload 'embark-collect "embark" "\ +Create an Embark Collect buffer. + +To control the display, add an entry to `display-buffer-alist' +with key \"Embark Collect\". + +In Embark Collect buffers `revert-buffer' is remapped to +`embark-rerun-collect-or-export', which has slightly unusual +behavior if the buffer was obtained by running `embark-collect' +from within a minibuffer completion session. In that case +rerunning just restarts the completion session, that is, the +command that opened the minibuffer is run again and the +minibuffer contents restored. You can then interact normally with +the command, perhaps editing the minibuffer contents, and, if you +wish, you can rerun `embark-collect' to get an updated buffer." t) +(autoload 'embark-live "embark" "\ +Create a live-updating Embark Collect buffer. + +To control the display, add an entry to `display-buffer-alist' +with key \"Embark Live\"." t) +(autoload 'embark-export "embark" "\ +Create a type-specific buffer to manage current candidates. +The variable `embark-exporters-alist' controls how to make the +buffer for each type of completion. + +In Embark Export buffers `revert-buffer' is remapped to +`embark-rerun-collect-or-export', which has slightly unusual +behavior if the buffer was obtained by running `embark-export' +from within a minibuffer completion session. In that case +reverting just restarts the completion session, that is, the +command that opened the minibuffer is run again and the +minibuffer contents restored. You can then interact normally +with the command, perhaps editing the minibuffer contents, and, +if you wish, you can rerun `embark-export' to get an updated +buffer." t) +(autoload 'embark-select "embark" "\ +Add or remove the target from the current buffer's selection. +You can act on all selected targets at once with `embark-act-all'. +When called from outside `embark-act' this command will select +the first target at point." t) +(register-definition-prefixes "embark" '("embark-")) + + +;;; Generated autoloads from embark-org.el + +(register-definition-prefixes "embark-org" '("embark-org-")) + +;;; End of scraped data + +(provide 'embark-autoloads) + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; no-native-compile: t +;; coding: utf-8-emacs-unix +;; End: + +;;; embark-autoloads.el ends here blob - /dev/null blob + f2816b1930297c187141f0c648558957b786dd20 (mode 644) --- /dev/null +++ elpa/embark-1.1/embark-org.el @@ -0,0 +1,716 @@ +;;; embark-org.el --- Embark targets and actions for Org Mode -*- lexical-binding: t; -*- + +;; Copyright (C) 2022-2023 Free Software Foundation, Inc. + +;; 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: + +;; This package configures the Embark package for use in Org Mode +;; buffers. It teaches Embark a number of Org related targets and +;; appropriate actions. Currently it has table cells, whole tables, +;; source blocks and links. Targets to add: headings (Embark already +;; has generic support for outlines, so we just nee to add Org +;; specific actions), timestamps, etc. + +;;; Code: + +(require 'embark) +(require 'org) +(require 'org-element) + +;;; Basic target finder for Org + +;; There are very many org element and objects types, we'll only +;; recognize those for which there are specific actions we can put in +;; a keymap, or even if there aren't any specific actions, if it's +;; important to be able to kill, delete or duplicate (embark-insert) +;; them conveniently. I'll start conservatively and we can add more +;; later + +(defconst embark-org--types + '( + babel-call + ;; bold + ;; center-block + ;; citation + ;; citation-reference + ;; clock + ;; code + ;; comment + ;; comment-block + ;; diary-sexp + ;; drawer + ;; dynamic-block + ;; entity + ;; example-block + ;; export-block + ;; export-snippet + ;; fixed-width + footnote-definition + footnote-reference + ;; headline ; the bounds include the entire subtree! + ;; horizontal-rule + ;; inline-babel-call + inline-src-block + ;; inlinetask + ;; italic + item + ;; keyword + ;; latex-environment + ;; latex-fragment + ;; line-break + link + ;; macro + ;; node-property + ;; paragraph ; the existing general support seems fine + plain-list + ;; planning + ;; property-drawer + ;; quote-block + ;; radio-target + ;; section + ;; special-block + src-block + ;; statistics-cookie + ;; strike-through + ;; subscript + ;; superscript + table ; supported via a specific target finder + table-cell + ;; table-row ; we'll put row & column actions in the cell map + ;; target ; I think there are no useful actions for radio targets + timestamp + ;; underline + ;; verbatim + ;; verse-block + ) + "Supported Org object and element types.") + +(defun embark-org-target-element-context () + "Target all Org elements or objects around point." + (when (derived-mode-p 'org-mode) + (cl-loop + for elt = (org-element-lineage (org-element-context) embark-org--types t) + then (org-element-lineage elt embark-org--types) + while elt + ;; clip bounds to narrowed portion of buffer + for begin = (max (org-element-property :begin elt) (point-min)) + for end = (min (org-element-property :end elt) (point-max)) + for target = (buffer-substring begin end) + ;; Adjust table-cell to exclude final |. (Why is that there?) + ;; Note: We are not doing this as an embark transformer because we + ;; want to adjust the bounds too. + ;; TODO? If more adjustments like this become necessary, add a + ;; nice mechanism for doing them. + when (and (eq (car elt) 'table-cell) (string-suffix-p "|" target)) + do (setq target (string-trim (string-remove-suffix "|" target)) + end (1- end)) + collect `(,(intern (format "org-%s" (car elt))) ,target ,begin . ,end)))) + +(unless (memq 'embark-org-target-element-context embark-target-finders) + (if-let ((tail (memq 'embark-target-active-region embark-target-finders))) + (push 'embark-org-target-element-context (cdr tail)) + (push 'embark-org-target-element-context embark-target-finders))) + +;;; Custom Org actions + +(defvar org-export-with-toc) + +(defun embark-org-copy-as-markdown (start end) + "Export the region from START to END to markdown and save on the `kill-ring'." + (interactive "r") + (require 'ox) + (kill-new + (let (org-export-with-toc) + (string-trim + (org-export-string-as (buffer-substring-no-properties start end) 'md t)))) + (deactivate-mark)) + +(add-to-list 'embark-pre-action-hooks + '(embark-org-copy-as-markdown embark--mark-target)) + +(keymap-set embark-region-map "M" #'embark-org-copy-as-markdown) ; good idea? + +;;; Tables + +(dolist (motion '(org-table-move-cell-up org-table-move-cell-down + org-table-move-cell-left org-table-move-cell-right + org-table-move-row org-table-move-column + org-table-move-row-up org-table-move-row-down + org-table-move-column-left org-table-move-column-right)) + (add-to-list 'embark-repeat-actions motion)) + +(dolist (cmd '(org-table-eval-formula org-table-edit-field)) + (push 'embark--ignore-target (alist-get cmd embark-target-injection-hooks))) + +(defvar-keymap embark-org-table-cell-map + :doc "Keymap for actions the current cells, column or row of an Org table." + :parent embark-general-map + "RET" #'org-table-align ; harmless default + "" #'org-table-move-cell-up + "" #'org-table-move-cell-down + "" #'org-table-move-cell-left + "" #'org-table-move-cell-right + "d" #'org-table-kill-row + "c" #'org-table-copy-down + "D" #'org-table-delete-column ; capital = column + "^" #'org-table-move-row-up + "v" #'org-table-move-row-down + "<" #'org-table-move-column-left + ">" #'org-table-move-column-right + "o" #'org-table-insert-row + "O" #'org-table-insert-column ; capital = column + "h" #'org-table-insert-hline + "=" #'org-table-eval-formula + "e" #'org-table-edit-field + "g" #'org-table-recalculate) + +(defvar-keymap embark-org-table-map + :doc "Keymap for actions on entire Org table." + :parent embark-general-map + "RET" #'org-table-align ; harmless default + "=" #'org-table-edit-formulas + "s" #'org-table-sort-lines + "t" #'org-table-transpose-table-at-point + "c" #'org-table-convert + "f" #'org-table-follow-field-mode + "y" #'org-table-paste-rectangle + "d" #'org-table-toggle-formula-debugger + "o" #'org-table-toggle-coordinate-overlays + "g" #'org-table-iterate + "e" #'org-table-export) + +(push 'embark--ignore-target ; prompts for file name + (alist-get 'org-table-export embark-target-injection-hooks)) + +(add-to-list 'embark-keymap-alist '(org-table embark-org-table-map)) + +(add-to-list 'embark-keymap-alist '(org-table-cell embark-org-table-cell-map)) + +;;; Links + +;; The link support has a slightly complicated design in order to +;; achieve the following goals: + +;; 1. RET should simply be org-open-at-point + +;; 2. When the link is to a file, URL, email address or elisp +;; expression or command, we want to offer the user actions for +;; that underlying type. + +;; 3. Even in those cases, we still want some actions to apply to the +;; entire link including description: actions to copy the link as +;; markdown, or just the link description or target. + +;; So the strategy is as follows (illustrated with file links): + +;; - The target will be just the file, without the description and +;; also without the "file:" prefix nor the "::line-number or search" +;; suffix. That way, file actions will correctly apply to it. + +;; - The type will not be 'file, but 'org-file-link; that way we can +;; register a keymap for 'org-file-link that inherits from both +;; embark-org-link-map (with RET bound to org-open-at-point and a +;; few other generic link actions) and embark-file-map. + +;; - The commands to copy the link at point in some format will be +;; written as commands that act on the Org link at point. This way +;; they are independently (plausibly) useful, and we circumvent the +;; problem that the whole Org link is not actually the target (just +;; the inner file is!). + +;; Alternative design I considered: separate each target into two, a +;; whole link target which includes the description and brackets and +;; what not; and an "inner target" which is just the file or URL or +;; whatever. Cons of this approach: much target cycling is required! +;; First of all, an unadorned embark-dwim definitely should be +;; org-open-at-point, which means the whole link target would need +;; priority. That means that any file, URL, etc. actions would require +;; you to cycle first. This sounds very inconvenient, the above +;; slightly more complex design allows both whole-link and inner +;; target actions to work without cycling. + +(defun embark-org-target-link () + "Target Org link at point. +This targets Org links in any buffer, not just buffers in +`org-mode' or `org-agenda-mode'. Org links in any buffer can be +opened with `org-open-at-point-global', which is the default +Embark action for Org links." + (pcase (org-in-regexp org-link-any-re) + (`(,start . ,end) + ;; We won't recognize unadorned http(s) or mailto links, as those + ;; already have target finders (but if these links have either a + ;; description, double brackets or angle brackets, then we do + ;; recognize them as org links) + (unless (save-excursion (goto-char start) (looking-at "http\\|mailto")) + `(org-link ,(buffer-substring start end) ,start . ,end))))) + +(let ((tail (memq 'embark-target-active-region embark-target-finders))) + (cl-pushnew 'embark-org-target-link (cdr tail))) + +(autoload 'org-attach-dir "org-attach") + +(defun embark-org--refine-link-type (_type target) + "Refine type of link TARGET if we have more specific actions available." + (when (string-match org-link-any-re target) + (let ((target (or (match-string-no-properties 2 target) + (match-string-no-properties 0 target)))) + (cond + ((string-prefix-p "http" target) + (cons 'org-url-link target)) + ((string-prefix-p "mailto:" target) + (cons 'org-email-link (string-remove-prefix "mailto:" target))) + ((string-prefix-p "file:" target) + (cons 'org-file-link + (replace-regexp-in-string + "::.*" "" (string-remove-prefix "file:" target)))) + ((string-prefix-p "attachment:" target) + (cons 'org-file-link + (expand-file-name + (replace-regexp-in-string + "::.*" "" (string-remove-prefix "attachment:" target)) + (org-attach-dir)))) + ((string-match-p "^[./]" target) + (cons 'org-file-link (abbreviate-file-name (expand-file-name target)))) + ((string-prefix-p "elisp:(" target) + (cons 'org-expression-link (string-remove-prefix "elisp:" target))) + ((string-prefix-p "elisp:" target) + (cons 'command (string-remove-prefix "elisp:" target))) + (t (cons 'org-link target)))))) + +(add-to-list 'embark-transformer-alist + '(org-link . embark-org--refine-link-type)) + +(defmacro embark-org-define-link-copier (name formula description) + "Define a command that copies the Org link at point according to FORMULA. +The command's name is formed by appending NAME to +embark-org-copy-link. The docstring includes the DESCRIPTION of +what part or in what format the link is copied." + `(defun ,(intern (format "embark-org-copy-link-%s" name)) () + ,(format "Copy to the kill-ring the Org link at point%s." description) + (interactive) + (when (org-in-regexp org-link-any-re) + (let* ((full (match-string-no-properties 0)) + (target (or (match-string-no-properties 2) + (match-string-no-properties 0))) + (description (match-string-no-properties 3)) + (kill ,formula)) + (ignore full target description) + (when kill + (message "Saved '%s'" kill) + (kill-new kill)))))) + +(embark-org-define-link-copier in-full full " in full") +(embark-org-define-link-copier description description "'s description") +(embark-org-define-link-copier target target "'s target") + +(defalias 'embark-org-copy-link-inner-target #'kill-new + "Copy inner part of the Org link at point's target. +For mailto and elisp links, the inner part is the portion of the +target after `mailto:' or `elisp:'. + +For file links the inner part is the file name, without the +`file:' prefix and without `::' suffix (used for line numbers, +IDs or search terms). + +For URLs the inner part is the whole target including the `http:' +or `https:' prefix. For any other type of link the inner part is +also the whole target.") + +(defvar-keymap embark-org-link-copy-map + :doc "Keymap for different ways to copy Org links to the kill-ring. + +You should bind w in this map to your most frequently used link +copying function. The default is for w to copy the \"inner +target\" (see `embark-org-copy-link-inner-target'); which is also +bound to i." + :parent nil + "w" #'embark-org-copy-link-inner-target + "f" #'embark-org-copy-link-in-full + "d" #'embark-org-copy-link-description + "t" #'embark-org-copy-link-target + "i" #'embark-org-copy-link-inner-target + "m" #'embark-org-copy-as-markdown) + +(fset 'embark-org-link-copy-map embark-org-link-copy-map) + +(defvar-keymap embark-org-link-map + :doc "Keymap for actions on Org links." + :parent embark-general-map + "RET" #'org-open-at-point-global + "'" #'org-insert-link + "n" #'org-next-link + "p" #'org-previous-link + "w" #'embark-org-link-copy-map) + +(dolist (motion '(org-next-link org-previous-link)) + (cl-pushnew motion embark-repeat-actions)) + +;; The reason for this is left as an exercise to the reader. +;; Solution: Na ryvfc gnetrg znl cebzcg gur hfre sbe fbzrguvat! +(cl-pushnew 'embark--ignore-target + (alist-get 'org-open-at-point embark-target-injection-hooks)) +(cl-pushnew 'embark--ignore-target + (alist-get 'org-insert-link embark-target-injection-hooks)) + +(add-to-list 'embark-keymap-alist + '(org-link embark-org-link-map)) +(add-to-list 'embark-keymap-alist + '(org-url-link embark-org-link-map embark-url-map)) +(add-to-list 'embark-keymap-alist + '(org-email-link embark-org-link-map embark-email-map)) +(add-to-list 'embark-keymap-alist + '(org-file-link embark-org-link-map embark-file-map)) +(add-to-list 'embark-keymap-alist + '(org-expression-link embark-org-link-map embark-expression-map)) + +;;; Org headings + +(defun embark-org--refine-heading (type target) + "Refine TYPE of heading TARGET in Org buffers." + (cons + (if (derived-mode-p 'org-mode) 'org-heading type) + target)) + +(add-to-list 'embark-transformer-alist '(heading . embark-org--refine-heading)) + +(defvar-keymap embark-org-heading-map + :doc "Keymap for actions on Org headings." + :parent embark-heading-map + "RET" #'org-todo + "TAB" #'org-cycle + "t" #'org-todo + "s" #'org-schedule + "d" #'org-deadline + "," #'org-priority + ":" #'org-set-tags-command + "P" #'org-set-property + "D" #'org-delete-property + "k" #'org-cut-subtree + "N" #'org-narrow-to-subtree + "T" #'org-tree-to-indirect-buffer + "" #'org-do-promote + "" #'org-do-demote + "o" #'org-sort + "r" #'org-refile + "R" #'embark-org-refile-here + "I" #'org-clock-in + "O" #'org-clock-out + "a" #'org-archive-subtree-default-with-confirmation + "h" #'org-insert-heading-respect-content + "H" #'org-insert-todo-heading-respect-content + "l" #'org-store-link + "j" #'embark-org-insert-link-to) + +(dolist (cmd '(org-todo org-metaright org-metaleft org-metaup org-metadown + org-shiftmetaleft org-shiftmetaright org-cycle org-shifttab)) + (cl-pushnew cmd embark-repeat-actions)) + +(dolist (cmd '(org-set-tags-command org-set-property + org-delete-property org-refile org-schedule)) + (cl-pushnew 'embark--ignore-target + (alist-get cmd embark-target-injection-hooks))) + +(add-to-list 'embark-keymap-alist '(org-heading embark-org-heading-map)) + +;;; Source blocks + +(defun embark-org-copy-block-contents () + "Save contents of source block at point to the `kill-ring'." + (interactive) + (when (org-in-src-block-p) + (let ((contents (nth 2 (org-src--contents-area (org-element-at-point))))) + (with-temp-buffer + (insert contents) + (org-do-remove-indentation) + (kill-new (buffer-substring (point-min) (point-max))))))) + +(defvar-keymap embark-org-src-block-map + :doc "Keymap for actions on Org source blocks." + :parent embark-general-map + "RET" #'org-babel-execute-src-block + "C-SPC" #'org-babel-mark-block + "TAB" #'org-indent-block + "c" #'embark-org-copy-block-contents + "h" #'org-babel-check-src-block + "k" #'org-babel-remove-result-one-or-many + "p" #'org-babel-previous-src-block + "n" #'org-babel-next-src-block + "t" #'org-babel-tangle + "s" #'org-babel-switch-to-session + "l" #'org-babel-load-in-session + "'" #'org-edit-special + "/" #'org-babel-demarcate-block + "N" #'org-narrow-to-block) + +(cl-defun embark-org--at-block-head + (&rest rest &key run bounds &allow-other-keys) + "Save excursion and RUN the action at the head of the current block. +If BOUNDS are given, use them to locate the block (useful for +when acting on a selection of blocks). Applies RUN to the REST +of the arguments." + (save-excursion + (when bounds (goto-char (car bounds))) + (org-babel-goto-src-block-head) + (apply run rest))) + +(cl-pushnew #'embark-org--at-block-head + (alist-get 'org-indent-block embark-around-action-hooks)) + +(dolist (motion '(org-babel-next-src-block org-babel-previous-src-block)) + (add-to-list 'embark-repeat-actions motion)) + +(dolist (cmd '(org-babel-execute-maybe + org-babel-lob-execute-maybe + org-babel-execute-src-block + org-babel-execute-src-block-maybe + org-babel-execute-buffer + org-babel-execute-subtree)) + (cl-pushnew #'embark--ignore-target + (alist-get cmd embark-target-injection-hooks))) + +(add-to-list 'embark-keymap-alist '(org-src-block embark-org-src-block-map)) + +;;; Inline source blocks + +(defvar-keymap embark-org-inline-src-block-map + :doc "Keymap for actions on Org inline source blocks." + :parent embark-general-map + "RET" #'org-babel-execute-src-block + "'" #'org-edit-inline-src-code + "k" #'org-babel-remove-inline-result) + +(add-to-list 'embark-keymap-alist + '(org-inline-src-block embark-org-inline-src-block-map)) + +;;; Babel calls + +(defvar-keymap embark-org-babel-call-map + :doc "Keymap for actions on Org babel calls." + :parent embark-general-map + "RET" #'org-babel-lob-execute-maybe + "k" #'org-babel-remove-result) + +(add-to-list 'embark-keymap-alist + '(org-babel-call embark-org-babel-call-map)) + +;;; List items + +(defvar-keymap embark-org-item-map + :doc "Keymap for actions on Org list items." + :parent embark-general-map + "RET" #'org-toggle-checkbox + "c" #'org-toggle-checkbox + "t" #'org-toggle-item + "n" #'org-next-item + "p" #'org-previous-item + "" #'org-outdent-item + "" #'org-indent-item + "" #'org-move-item-up + "" #'org-move-item-down + ">" #'org-indent-item-tree + "<" #'org-outdent-item-tree) + +(dolist (cmd '(org-toggle-checkbox + org-toggle-item + org-next-item + org-previous-item + org-outdent-item + org-indent-item + org-move-item-up + org-move-item-down + org-indent-item-tree + org-outdent-item-tree)) + (add-to-list 'embark-repeat-actions cmd)) + +(add-to-list 'embark-keymap-alist '(org-item embark-org-item-map)) + +;;; Org plain lists + +(defvar-keymap embark-org-plain-list-map + :doc "Keymap for actions on plain Org lists." + :parent embark-general-map + "RET" #'org-list-repair + "r" #'org-list-repair + "s" #'org-sort-list + "b" #'org-cycle-list-bullet + "t" #'org-list-make-subtree + "c" #'org-toggle-checkbox) + +(add-to-list 'embark-repeat-actions 'org-cycle-list-bullet) + +(add-to-list 'embark-keymap-alist '(org-plain-list embark-org-plain-list-map)) + +(cl-defun embark-org--toggle-checkboxes + (&rest rest &key run type &allow-other-keys) + "Around action hook for `org-toggle-checkbox'. +See `embark-around-action-hooks' for the keyword arguments RUN and TYPE. +REST are the remaining arguments." + (apply (if (eq type 'org-plain-list) #'embark--mark-target run) + :type type + rest)) + +(cl-pushnew #'embark-org--toggle-checkboxes + (alist-get 'org-toggle-checkbox embark-around-action-hooks)) + +;;; "Encode" region using Org export in place + +(defvar-keymap embark-org-export-in-place-map + :doc "Keymap for actions which replace the region by an exported version." + :parent embark-general-map + "m" #'org-md-convert-region-to-md + "h" #'org-html-convert-region-to-html + "a" #'org-ascii-convert-region-to-ascii + "l" #'org-latex-convert-region-to-latex) + +(fset 'embark-org-export-in-place-map embark-org-export-in-place-map) + +(keymap-set embark-encode-map "o" 'embark-org-export-in-place-map) + +;;; References to Org headings, such as agenda items + +;; These are targets that represent an org heading but not in the +;; current buffer, instead they have a text property named +;; `org-marker' that points to the actual heading. + +(defun embark-org-target-agenda-item () + "Target Org agenda item at point." + (when (and (derived-mode-p 'org-agenda-mode) + (get-text-property (line-beginning-position) 'org-marker)) + (let ((start (+ (line-beginning-position) (current-indentation))) + (end (line-end-position))) + `(org-heading ,(buffer-substring start end) ,start . ,end)))) + +(let ((tail (memq 'embark-org-target-element-context embark-target-finders))) + (cl-pushnew 'embark-org-target-agenda-item (cdr tail))) + +(cl-defun embark-org--at-heading + (&rest rest &key run target &allow-other-keys) + "RUN the action at the location of the heading TARGET refers to. +The location is given by the `org-marker' text property of +target. Applies RUN to the REST of the arguments." + (if-let ((marker (get-text-property 0 'org-marker target))) + (org-with-point-at marker + (apply run :target target rest)) + (apply run :target target rest))) + +(cl-defun embark-org-goto-heading (&key target &allow-other-keys) + "Jump to the org heading TARGET refers to." + (when-let ((marker (get-text-property 0 'org-marker target))) + (pop-to-buffer (marker-buffer marker)) + (widen) + (goto-char marker) + (org-reveal) + (pulse-momentary-highlight-one-line))) + +(defun embark-org-heading-default-action (target) + "Default action for Org headings. +There are two types of heading TARGETs: the heading at point in a +normal org buffer, and references to org headings in some other +buffer (for example, org agenda items). For references the +default action is to jump to the reference, and for the heading +at point, the default action is whatever is bound to RET in +`embark-org-heading-map', or `org-todo' if RET is unbound." + (if (get-text-property 0 'org-marker target) + (embark-org-goto-heading :target target) + (command-execute + (or (keymap-lookup embark-org-heading-map "RET") #'org-todo)))) + +(defconst embark-org--invisible-jump-to-heading + '(org-tree-to-indirect-buffer + org-refile + org-clock-in + org-clock-out + org-archive-subtree-default-with-confirmation + org-store-link) + "Org heading actions which won't display the heading's buffer.") + +(defconst embark-org--no-jump-to-heading + '(embark-org-insert-link-to embark-org-refile-here) + "Org heading actions which shouldn't be executed with point at the heading.") + +(setf (alist-get 'org-heading embark-default-action-overrides) + #'embark-org-heading-default-action) + +(map-keymap + (lambda (_key cmd) + (unless (or (where-is-internal cmd (list embark-general-map)) + (memq cmd embark-org--no-jump-to-heading) + (memq cmd embark-org--invisible-jump-to-heading)) + (cl-pushnew 'embark-org-goto-heading + (alist-get cmd embark-pre-action-hooks)))) + embark-org-heading-map) + +(dolist (cmd embark-org--invisible-jump-to-heading) + (cl-pushnew 'embark-org--at-heading + (alist-get cmd embark-around-action-hooks))) + +(defun embark-org--in-source-window (target function) + "Call FUNCTION, in the source window, on TARGET's `org-marker'. + +If TARGET does not have an `org-marker' property a `user-error' +is signaled. In case the TARGET comes from an org agenda buffer +and the `other-window-for-scrolling' is an org mode buffer, then +the FUNCTION is called with that other window temporarily +selected; otherwise the FUNCTION is called in the selected +window." + (if-let ((marker (get-text-property 0 'org-marker target))) + (with-selected-window + (or (and (derived-mode-p 'org-agenda-mode) + (let ((window (ignore-errors (other-window-for-scrolling)))) + (with-current-buffer (window-buffer window) + (when (derived-mode-p 'org-mode) window)))) + (selected-window)) + (funcall function marker)) + (user-error "The target is an org heading rather than a reference to one"))) + +(defun embark-org-refile-here (target) + "Refile the heading at point in the source window to TARGET. + +If TARGET is an agenda item and `other-window-for-scrolling' is +displaying an org mode buffer, then that is the source window. +If TARGET is a minibuffer completion candidate, then the source +window is the window selected before the command that opened the +minibuffer ran." + (embark-org--in-source-window target + (lambda (marker) + (org-refile nil nil + ;; The RFLOC argument: + (list + ;; Name + (org-with-point-at marker + (nth 4 (org-heading-components))) + ;; File + (buffer-file-name (marker-buffer marker)) + ;; nil + nil + ;; Position + marker))))) + +(defun embark-org-insert-link-to (target) + "Insert a link to the TARGET in the source window. + +If TARGET is an agenda item and `other-window-for-scrolling' is +displaying an org mode buffer, then that is the source window. +If TARGET is a minibuffer completion candidate, then the source +window is the window selected before the command that opened the +minibuffer ran." + (embark-org--in-source-window target + (lambda (marker) + (org-with-point-at marker (org-store-link nil t)) + (org-insert-all-links 1 "" "")))) + +(provide 'embark-org) +;;; embark-org.el ends here blob - /dev/null blob + 46a156c5664210bface6266e1662ecbc64c1973c (mode 644) --- /dev/null +++ elpa/embark-1.1/embark-pkg.el @@ -0,0 +1,2 @@ +;; Generated package description from embark.el -*- no-byte-compile: t -*- +(define-package "embark" "1.1" "Conveniently act on minibuffer completions" '((emacs "27.1") (compat "29.1.4.0")) :commit "195add1f1ccd1059472c9df7334c97c4d155425e" :authors '(("Omar Antolín Camarena" . "omar@matem.unam.mx")) :maintainer '("Omar Antolín Camarena" . "omar@matem.unam.mx") :keywords '("convenience") :url "https://github.com/oantolin/embark") blob - /dev/null blob + 660d9324f9e84a9e4145d13b65c9bd30ff7fd0ff (mode 644) --- /dev/null +++ elpa/embark-1.1/embark.el @@ -0,0 +1,4604 @@ +;;; embark.el --- Conveniently act on minibuffer completions -*- lexical-binding: t; -*- + +;; Copyright (C) 2021-2023 Free Software Foundation, Inc. + +;; Author: Omar Antolín Camarena +;; Maintainer: Omar Antolín Camarena +;; Keywords: convenience +;; Version: 1.1 +;; Homepage: https://github.com/oantolin/embark +;; Package-Requires: ((emacs "27.1") (compat "29.1.4.0")) + +;; This file is 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: + +;; This package provides a sort of right-click contextual menu for +;; Emacs, accessed through the `embark-act' command (which you should +;; bind to a convenient key), offering you relevant actions to use on +;; a target determined by the context: + +;; - In the minibuffer, the target is the current best completion +;; candidate. +;; - In the `*Completions*' buffer the target is the completion at point. +;; - In a regular buffer, the target is the region if active, or else the +;; file, symbol or url at point. + +;; The type of actions offered depend on the type of the target: + +;; - For files you get offered actions like deleting, copying, +;; renaming, visiting in another window, running a shell command on the +;; file, etc. +;; - For buffers the actions include switching to or killing the buffer. +;; - For package names the actions include installing, removing or +;; visiting the homepage. + +;; Everything is easily configurable: determining the current target, +;; classifying it, and deciding with actions are offered for each type +;; in the classification. The above introduction just mentions part of +;; the default configuration. + +;; Configuring which actions are offered for a type is particularly +;; easy and requires no programming: the `embark-keymap-alist' +;; variable associates target types with variable containing keymaps, +;; and those keymaps containing binds for the actions. For example, +;; in the default configuration the type `file' is associated with the +;; symbol `embark-file-map'. That symbol names a keymap with +;; single-letter key bindings for common Emacs file commands, for +;; instance `c' is bound to `copy-file'. This means that if while you +;; are in the minibuffer after running a command that prompts for a +;; file, such as `find-file' or `rename-file', you can copy a file by +;; running `embark-act' and then pressing `c'. + +;; These action keymaps are very convenient but not strictly necessary +;; when using `embark-act': you can use any command that reads from the +;; minibuffer as an action and the target of the action will be inserted +;; at the first minibuffer prompt. After running `embark-act' all of your +;; key bindings and even `execute-extended-command' can be used to run a +;; command. The action keymaps are normal Emacs keymaps and you should +;; feel free to bind in them whatever commands you find useful as actions. + +;; The actions in `embark-general-map' are available no matter what +;; type of completion you are in the middle of. By default this +;; includes bindings to save the current candidate in the kill ring +;; and to insert the current candidate in the previously selected +;; buffer (the buffer that was current when you executed a command +;; that opened up the minibuffer). + +;; You can read about the Embark GitHub project wiki: +;; https://github.com/oantolin/embark/wiki/Default-Actions + +;; Besides acting individually on targets, Embark lets you work +;; collectively on a set of target candidates. For example, while +;; you are in the minibuffer the candidates are simply the possible +;; completions of your input. Embark provides three commands to work +;; on candidate sets: + +;; - The `embark-act-all' command runs the same action on each of the +;; current candidates. It is just like using `embark-act' on each +;; candidate in turn. + +;; - The `embark-collect' command produces a buffer listing all +;; candidates, for you to peruse and run actions on at your leisure. +;; The candidates are displayed as a list showing additional +;; annotations. + +;; - The `embark-export' command tries to open a buffer in an +;; appropriate major mode for the set of candidates. If the +;; candidates are files export produces a Dired buffer; if they are +;; buffers, you get an Ibuffer buffer; and if they are packages you +;; get a buffer in package menu mode. + +;; These are always available as "actions" (although they do not act +;; on just the current target but on all candidates) for embark-act +;; and are bound to A, S (for "snapshot") and E, respectively, in +;; embark-general-map. This means that you do not have to bind your +;; own key bindings for these (although you can, of course), just a +;; key binding for `embark-act'. + +;;; Code: + +(require 'compat) +(eval-when-compile (require 'subr-x)) + +(require 'ffap) ; used to recognize file and url targets + +;;; User facing options + +(defgroup embark nil + "Emacs Mini-Buffer Actions Rooted in Keymaps." + :link '(info-link :tag "Info Manual" "(embark)") + :link '(url-link :tag "Homepage" "https://github.com/oantolin/embark") + :link '(emacs-library-link :tag "Library Source" "embark.el") + :group 'minibuffer + :prefix "embark-") + +(defcustom embark-keymap-alist + '((file embark-file-map) + (library embark-library-map) + (environment-variables embark-file-map) ; they come up in file completion + (url embark-url-map) + (email embark-email-map) + (buffer embark-buffer-map) + (tab embark-tab-map) + (expression embark-expression-map) + (identifier embark-identifier-map) + (defun embark-defun-map) + (symbol embark-symbol-map) + (face embark-face-map) + (command embark-command-map) + (variable embark-variable-map) + (function embark-function-map) + (minor-mode embark-command-map) + (unicode-name embark-unicode-name-map) + (package embark-package-map) + (bookmark embark-bookmark-map) + (region embark-region-map) + (sentence embark-sentence-map) + (paragraph embark-paragraph-map) + (kill-ring embark-kill-ring-map) + (heading embark-heading-map) + (flymake embark-flymake-map) + (smerge smerge-basic-map embark-general-map) + (t embark-general-map)) + "Alist of action types and corresponding keymaps. +The special key t is associated with the default keymap to use. +Each value can be either a single symbol whose value is a keymap, +or a list of such symbols." + :type '(alist :key-type (symbol :tag "Target type") + :value-type (choice (variable :tag "Keymap") + (repeat :tag "Keymaps" variable)))) + +(defcustom embark-target-finders + '(embark-target-top-minibuffer-candidate + embark-target-active-region + embark-target-collect-candidate + embark-target-completion-list-candidate + embark-target-text-heading-at-point + embark-target-bug-reference-at-point + embark-target-flymake-at-point + embark-target-smerge-at-point + embark-target-package-at-point + embark-target-email-at-point + embark-target-url-at-point + embark-target-file-at-point + embark-target-custom-variable-at-point + embark-target-identifier-at-point + embark-target-guess-file-at-point + embark-target-expression-at-point + embark-target-sentence-at-point + embark-target-paragraph-at-point + embark-target-defun-at-point + embark-target-prog-heading-at-point) + "List of functions to determine the target in current context. +Each function should take no arguments and return one of: + +1. a cons (TYPE . TARGET) where TARGET is a string and TYPE is a + symbol (which is looked up in `embark-keymap-alist' to + determine which additional keybindings for actions to setup); + +2. a dotted list of the form (TYPE TARGET START . END), where + START and END are the buffer positions bounding TARGET, used + for highlighting; or + +3. a possibly empty list of targets, each of type 1 or 2 (in + particular if a target finder does not find any targets, it + should return nil)." + :type 'hook) + +(defcustom embark-transformer-alist + '((minor-mode . embark--lookup-lighter-minor-mode) + (embark-keybinding . embark--keybinding-command) + (project-file . embark--project-file-full-path) + (package . embark--remove-package-version) + (multi-category . embark--refine-multi-category) + (file . embark--simplify-path)) + "Alist associating type to functions for transforming targets. +Each function should take a type and a target string and return a +pair of the form a `cons' of the new type and the new target." + :type '(alist :key-type symbol :value-type function)) + +(defcustom embark-become-keymaps + '(embark-become-help-map + embark-become-file+buffer-map + embark-become-shell-command-map + embark-become-match-map) + "List of keymaps for `embark-become'. +Each keymap groups a set of related commands that can +conveniently become one another." + :type '(repeat variable)) + +(defcustom embark-prompter 'embark-keymap-prompter + "Function used to prompt the user for actions. +This should be set to a function that prompts the use for an +action and returns the symbol naming the action command. The +default value, `embark-keymap-prompter' activates the type +specific action keymap given in `embark-keymap-alist'. +There is also `embark-completing-read-prompter' which +prompts for an action with completion." + :type '(choice (const :tag "Use action keymaps" embark-keymap-prompter) + (const :tag "Read action with completion" + embark-completing-read-prompter) + (function :tag "Other"))) + +(defcustom embark-keymap-prompter-key "@" + "Key to switch to the keymap prompter from `embark-completing-read-prompter'. + +The key must be either nil or a string. The +string must be accepted by `key-valid-p'." + :type '(choice key (const :tag "None" nil))) + +(defcustom embark-cycle-key nil + "Key used for `embark-cycle'. + +If the key is set to nil it defaults to the global binding of +`embark-act'. The key must be a string which is accepted by +`key-valid-p'." + :type '(choice key (const :tag "Use embark-act key" nil))) + +(defcustom embark-help-key "C-h" + "Key used for help. + +The key must be either nil or a string. The +string must be accepted by `key-valid-p'." + :type '(choice (const "C-h") + (const "?") + (const :tag "None" nil) + key)) + +(defcustom embark-keybinding-repeat + (propertize "*" 'face 'embark-keybinding-repeat) + "Indicator string for repeatable keybindings. +Keybindings are formatted by the `completing-read' prompter and +the verbose indicator." + :type 'string) + +(defface embark-keybinding-repeat + '((t :inherit font-lock-builtin-face)) + "Face used to indicate keybindings as repeatable.") + +(defface embark-keybinding '((t :inherit success)) + "Face used to display key bindings. +Used by `embark-completing-read-prompter' and `embark-keymap-help'.") + +(defface embark-keymap '((t :slant italic)) + "Face used to display keymaps. +Used by `embark-completing-read-prompter' and `embark-keymap-help'.") + +(defface embark-target '((t :inherit highlight)) + "Face used to highlight the target at point during `embark-act'.") + +(defcustom embark-quit-after-action t + "Should `embark-act' quit the minibuffer? +This controls whether calling `embark-act' without a prefix +argument quits the minibuffer or not. You can always get the +opposite behavior to that indicated by this variable by calling +`embark-act' with \\[universal-argument]. + +Note that `embark-act' can also be called from outside the +minibuffer and this variable is irrelevant in that case. + +In addition to t or nil this variable can also be set to an +alist to specify the minibuffer quitting behavior per command. +In the alist case one can additionally use the key t to +prescribe a default for commands not used as alist keys." + :type '(choice (const :tag "Always quit" t) + (const :tag "Never quit" nil) + (alist :tag "Configure per action" + :key-type (choice (function :tag "Action") + (const :tag "All other actions" t)) + :value-type (choice (const :tag "Quit" t) + (const :tag "Do not quit" nil))))) + +(defcustom embark-confirm-act-all t + "Should `embark-act-all' prompt the user for confirmation? +Even if this variable is nil you may still be prompted to confirm +some uses of `embark-act-all', namely, for those actions whose +entry in `embark-pre-action-hooks' includes `embark--confirm'." + :type 'boolean) + +(defcustom embark-default-action-overrides nil + "Alist associating target types with overriding default actions. +When the source of a target is minibuffer completion, the default +action for it is usually the command that opened the minibuffer +in the first place but this can be overridden for a given type by +an entry in this list. + +For example, if you run `delete-file' the default action for its +completion candidates is `delete-file' itself. You may prefer to +make `find-file' the default action for all files, even if they +were obtained from a `delete-file' prompt. In that case you can +configure that by adding an entry to this variable pairing `file' +with `find-file'. + +In addition to target types, you can also use as keys in this alist, +pairs of a target type and a command name. Such a pair indicates that +the override only applies if the target was obtained from minibuffer +completion from that command. For example adding an +entry (cons (cons \\='file \\='delete-file) \\='find-file) to this alist would +indicate that for files at the prompt of the `delete-file' command, +`find-file' should be used as the default action." + :type '(alist :key-type (choice (symbol :tag "Type") + (cons (symbol :tag "Type") + (symbol :tag "Command"))) + :value-type (function :tag "Default action"))) + +(defcustom embark-target-injection-hooks + '((async-shell-command embark--allow-edit embark--shell-prep) + (shell-command embark--allow-edit embark--shell-prep) + (pp-eval-expression embark--eval-prep) + (eval-expression embark--eval-prep) + (package-delete embark--force-complete) + ;; commands evaluating code found in the buffer, which may in turn prompt + (embark-pp-eval-defun embark--ignore-target) + (eval-defun embark--ignore-target) + (eval-last-sexp embark--ignore-target) + (embark-eval-replace embark--ignore-target) + ;; commands which prompt for something that is *not* the target + (write-region embark--ignore-target) + (append-to-file embark--ignore-target) + (append-to-buffer embark--ignore-target) + (shell-command-on-region embark--ignore-target) + (format-encode-region embark--ignore-target) + (format-decode-region embark--ignore-target) + (xref-find-definitions embark--ignore-target) + (xref-find-references embark--ignore-target) + (sort-regexp-fields embark--ignore-target) + (align-regexp embark--ignore-target)) + "Alist associating commands with post-injection setup hooks. +For commands appearing as keys in this alist, run the +corresponding value as a setup hook after injecting the target +into in the minibuffer and before acting on it. The hooks must +accept arbitrary keyword arguments. The :action command, the +:target string and target :type are always present. For actions +at point the target :bounds are passed too. The default pre-action +hook is specified by the entry with key t. Furthermore, hooks with +the key :always are executed always." + :type '(alist :key-type + (choice symbol + (const :tag "Default" t) + (const :tag "Always" :always)) + :value-type hook)) + +(defcustom embark-pre-action-hooks + `(;; commands that need to position point at the beginning or end + (eval-last-sexp embark--end-of-target) + (indent-pp-sexp embark--beginning-of-target) + (backward-up-list embark--beginning-of-target) + (backward-list embark--beginning-of-target) + (forward-list embark--end-of-target) + (forward-sexp embark--end-of-target) + (backward-sexp embark--beginning-of-target) + (raise-sexp embark--beginning-of-target) + (kill-sexp embark--beginning-of-target) + (mark-sexp embark--beginning-of-target) + (transpose-sexps embark--end-of-target) + (transpose-sentences embark--end-of-target) + (transpose-paragraphs embark--end-of-target) + (forward-sentence embark--end-of-target) + (backward-sentence embark--beginning-of-target) + (backward-paragraph embark--beginning-of-target) + (embark-insert embark--end-of-target) + ;; commands we want to be able to jump back from + ;; (embark-find-definition achieves this by calling + ;; xref-find-definitions which pushes the markers itself) + (find-library embark--xref-push-marker) + ;; commands which prompt the user for confirmation before running + (delete-file embark--confirm) + (delete-directory embark--confirm) + (kill-buffer embark--confirm) + (embark-kill-buffer-and-window embark--confirm) + (bookmark-delete embark--confirm) + (package-delete embark--confirm) + (,'tab-bar-close-tab-by-name embark--confirm) ;; Avoid package-lint warning + ;; search for region contents outside said region + (embark-isearch-forward embark--unmark-target) + (embark-isearch-backward embark--unmark-target) + (occur embark--unmark-target) + (query-replace embark--beginning-of-target embark--unmark-target) + (query-replace-regexp embark--beginning-of-target embark--unmark-target) + (replace-string embark--beginning-of-target embark--unmark-target) + (replace-regexp embark--beginning-of-target embark--unmark-target) + ;; mark pseudo-action + (mark embark--mark-target) + ;; shells in new buffers + (shell embark--universal-argument) + (eshell embark--universal-argument)) + "Alist associating commands with pre-action hooks. +The hooks are run right before an action is embarked upon. See +`embark-target-injection-hooks' for information about the hook +arguments and more details." + :type '(alist :key-type + (choice symbol + (const :tag "Default" t) + (const :tag "Always" :always)) + :value-type hook)) + +(defcustom embark-post-action-hooks + `((bookmark-delete embark--restart) + (bookmark-rename embark--restart) + (delete-file embark--restart) + (embark-kill-ring-remove embark--restart) + (embark-recentf-remove embark--restart) + (embark-history-remove embark--restart) + (rename-file embark--restart) + (copy-file embark--restart) + (delete-directory embark--restart) + (make-directory embark--restart) + (kill-buffer embark--restart) + (embark-rename-buffer embark--restart) + (,'tab-bar-rename-tab-by-name embark--restart) ;; Avoid package-lint warning + (,'tab-bar-close-tab-by-name embark--restart) + (package-delete embark--restart)) + "Alist associating commands with post-action hooks. +The hooks are run after an embarked upon action concludes. See +`embark-target-injection-hooks' for information about the hook +arguments and more details." + :type '(alist :key-type + (choice symbol + (const :tag "Default" t) + (const :tag "Always" :always)) + :value-type hook)) + +(defcustom embark-around-action-hooks + '(;; use directory of target as default-directory + (shell embark--cd) + (eshell embark--cd) + ;; mark the target preserving point and previous mark + (kill-region embark--mark-target) + (kill-ring-save embark--mark-target) + (indent-region embark--mark-target) + (ispell-region embark--mark-target) + (fill-region embark--mark-target) + (upcase-region embark--mark-target) + (downcase-region embark--mark-target) + (capitalize-region embark--mark-target) + (count-words-region embark--mark-target) + (count-words embark--mark-target) + (delete-duplicate-lines embark--mark-target) + (shell-command-on-region embark--mark-target) + (delete-region embark--mark-target) + (format-encode-region embark--mark-target) + (format-decode-region embark--mark-target) + (write-region embark--mark-target) + (append-to-file embark--mark-target) + (append-to-buffer embark--mark-target) + (shell-command-on-region embark--mark-target) + (embark-eval-replace embark--mark-target) + (delete-indentation embark--mark-target) + (comment-dwim embark--mark-target) + (insert-parentheses embark--mark-target) + (insert-pair embark--mark-target) + (org-emphasize embark--mark-target) + ;; do the actual work of selecting & deselecting targets + (embark-select embark--select)) + "Alist associating commands with post-action hooks. +The hooks are run instead of the embarked upon action. The hook +can decide whether or not to run the action or it can run it +in some special environment, like inside a let-binding or inside +`save-excursion'. Each hook is called with keyword argument :run +providing a function encapsulating the following around hooks and +the action; the hook additionally receives the keyword arguments +used for other types of action hooks, for more details see +`embark-target-injection-hooks'." + :type '(alist :key-type + (choice symbol + (const :tag "Default" t) + (const :tag "Always" :always)) + :value-type hook)) + +(when (version-list-< (version-to-list emacs-version) '(29 1)) + ;; narrow to target for duration of action + (setf (alist-get 'repunctuate-sentences embark-around-action-hooks) + '(embark--narrow-to-target))) + +(defcustom embark-multitarget-actions '(embark-insert embark-copy-as-kill) + "Commands for which `embark-act-all' should pass a list of targets. +Normally `embark-act-all' runs the same action on each candidate +separately, but when a command included in this variable's value +is used as an action, `embark-act-all' will instead call it +non-interactively with a single argument: the list of all +candidates. For commands on this list `embark-act' behaves +similarly: it calls them non-interactively with a single +argument: a one element list containing the target." + :type '(repeat function)) + +(defcustom embark-repeat-actions + '((mark . region) + ;; outline commands + outline-next-visible-heading outline-previous-visible-heading + outline-forward-same-level outline-backward-same-level + outline-demote outline-promote + outline-show-subtree (outline-mark-subtree . region) + outline-move-subtree-up outline-move-subtree-down + outline-up-heading outline-hide-subtree outline-cycle + ;; org commands (remapped outline commands) + org-forward-heading-same-level org-backward-heading-same-level + org-next-visible-heading org-previous-visible-heading + org-demote-subtree org-promote-subtree + org-show-subtree (org-mark-subtree . region) + org-move-subtree-up org-move-subtree-down + ;; transpose commands + transpose-sexps transpose-sentences transpose-paragraphs + ;; navigation commands + flymake-goto-next-error flymake-goto-prev-error + embark-next-symbol embark-previous-symbol + backward-up-list backward-list forward-list forward-sexp + backward-sexp forward-sentence backward-sentence + forward-paragraph backward-paragraph + ;; smerge commands + smerge-refine smerge-combine-with-next smerge-prev smerge-next) + "List of repeatable actions. +When you use a command on this list as an Embark action from +outside the minibuffer, `embark-act' does not exit but instead +lets you act again on the possibly new target you reach. + +By default, after using one of these actions, when `embark-act' +looks for targets again, it will start the target cycle at the +same type as the previously acted upon target; that is, you +\"don't loose your place in the target cycle\". + +Sometimes, however, you'll want to prioritize a different type of +target to continue acting on. The main example of this that if +you use a marking command as an action, you almost always want to +act on the region next. For those cases, in addition to +commands, you can also place on this list a pair of a command and +the desired starting type for the target cycle for the next +action." + :type '(repeat (choice function + (cons function + (symbol :tag "Next target type"))))) + +;;; Overlay properties + +;; high priority to override both bug reference and the lazy +;; isearch highlights in embark-isearch-highlight-indicator +(put 'embark-target-overlay 'face 'embark-target) +(put 'embark-target-overlay 'priority 1001) +(put 'embark-selected-overlay 'face 'embark-selected) +(put 'embark-selected-overlay 'priority 1001) + +;;; Stashing information for actions in buffer local variables + +(defvar-local embark--type nil + "Cache for the completion type, meant to be set buffer-locally.") + +(defvar-local embark--target-buffer nil + "Cache for the previous buffer, meant to be set buffer-locally.") + +(defvar-local embark--target-window nil + "Cache for the previous window, meant to be set buffer-locally. +Since windows can be reused to display different buffers, this +window should only be used if it displays the buffer stored in +the variable `embark--target-buffer'.") + +(defvar-local embark--command nil + "Command that started the completion session.") + +(defvar-local embark--toggle-quit nil + "Should we toggle the default quitting behavior for the next action?") + +(defun embark--minibuffer-point () + "Return length of minibuffer contents." + (max 0 (- (point) (minibuffer-prompt-end)))) + +(defun embark--default-directory () + "Guess a reasonable default directory for the current candidates." + (if (and (minibufferp) minibuffer-completing-file-name) + (let ((end (minibuffer-prompt-end)) + (contents (minibuffer-contents))) + (expand-file-name + (substitute-in-file-name + (buffer-substring + end + (+ end + (or (cdr + (last + (completion-all-completions + contents + minibuffer-completion-table + minibuffer-completion-predicate + (embark--minibuffer-point)))) + (cl-position ?/ contents :from-end t) + 0)))))) + default-directory)) + +(defun embark--target-buffer () + "Return buffer that should be targeted by Embark actions." + (cond + ((and (minibufferp) minibuffer-completion-table (minibuffer-selected-window)) + (window-buffer (minibuffer-selected-window))) + ((and embark--target-buffer (buffer-live-p embark--target-buffer)) + embark--target-buffer) + (t (current-buffer)))) + +(defun embark--target-window (&optional display) + "Return window which should be selected when Embark actions run. +If DISPLAY is non-nil, call `display-buffer' to produce the +window if necessary." + (cond + ((and (minibufferp) minibuffer-completion-table (minibuffer-selected-window)) + (minibuffer-selected-window)) + ((and embark--target-window + (window-live-p embark--target-window) + (or (not (buffer-live-p embark--target-buffer)) + (eq (window-buffer embark--target-window) embark--target-buffer))) + embark--target-window) + ((and embark--target-buffer (buffer-live-p embark--target-buffer)) + (or (get-buffer-window embark--target-buffer) + (when display (display-buffer embark--target-buffer)))) + (display (selected-window)))) + +(defun embark--cache-info (buffer) + "Cache information needed for actions in variables local to BUFFER. +BUFFER defaults to the current buffer." + (let ((cmd embark--command) + (dir (embark--default-directory)) + (target-buffer (embark--target-buffer)) + (target-window (embark--target-window))) + (with-current-buffer buffer + (setq embark--command cmd + default-directory dir + embark--target-buffer target-buffer + embark--target-window target-window)))) + +(defun embark--cache-info--completion-list () + "Cache information needed for actions in a *Completions* buffer. +Meant to be be added to `completion-setup-hook'." + ;; when completion-setup-hook hook runs, the *Completions* buffer is + ;; available in the variable standard-output + (embark--cache-info standard-output) + (with-current-buffer standard-output + (when (minibufferp completion-reference-buffer) + (setq embark--type + (completion-metadata-get + (with-current-buffer completion-reference-buffer + (embark--metadata)) + 'category))))) + +;; We have to add this *after* completion-setup-function because that's +;; when the buffer is put in completion-list-mode and turning the mode +;; on kills all local variables! So we use a depth of 5. +(add-hook 'completion-setup-hook #'embark--cache-info--completion-list 5) + +;;;###autoload +(progn + (defun embark--record-this-command () + "Record command which opened the minibuffer. +We record this because it will be the default action. +This function is meant to be added to `minibuffer-setup-hook'." + (setq-local embark--command this-command)) + (add-hook 'minibuffer-setup-hook #'embark--record-this-command)) + +;;; Internal variables + +(defvar embark--prompter-history nil + "History used by the `embark-completing-read-prompter'.") + +;;; Core functionality + +(defconst embark--verbose-indicator-buffer " *Embark Actions*") + +(defvar embark--minimal-indicator-overlay nil) + +(defun embark--metadata () + "Return current minibuffer completion metadata." + (completion-metadata + (buffer-substring-no-properties + (minibuffer-prompt-end) + (max (minibuffer-prompt-end) (point))) + minibuffer-completion-table + minibuffer-completion-predicate)) + +(defun embark-target-active-region () + "Target the region if active." + (when (use-region-p) + (let ((start (region-beginning)) + (end (region-end))) + `(region ,(buffer-substring start end) . (,start . ,end))))) + +(autoload 'dired-get-filename "dired") +(declare-function image-dired-original-file-name "image-dired") + +(defun embark-target-guess-file-at-point () + "Target the file guessed by `ffap' at point." + (when-let ((tap-file (thing-at-point 'filename)) + ((not (ffap-url-p tap-file))) ; no URLs, those have a target finder + (bounds (bounds-of-thing-at-point 'filename)) + (file (ffap-file-at-point))) + ;; ffap doesn't make bounds available, so we use + ;; thingatpt bounds, which might be a little off + ;; adjust bounds if thingatpt gobbled punctuation around file + (when (or (string-match (regexp-quote file) tap-file) + (string-match (regexp-quote (file-name-base file)) tap-file)) + (setq bounds (cons (+ (car bounds) (match-beginning 0)) + (- (cdr bounds) (- (length tap-file) + (match-end 0)))))) + `(file ,(abbreviate-file-name (expand-file-name file)) ,@bounds))) + +(defun embark-target-file-at-point () + "Target file at point. +This function mostly relies on `ffap-file-at-point', with the +following exceptions: + +- In `dired-mode', it uses `dired-get-filename' instead. + +- In `imaged-dired-thumbnail-mode', it uses + `image-dired-original-file-name' instead." + (let (file bounds) + (or (and (derived-mode-p 'dired-mode) + (setq file (dired-get-filename t 'no-error-if-not-filep)) + (setq bounds + (cons + (save-excursion (dired-move-to-filename) (point)) + (save-excursion (dired-move-to-end-of-filename) (point))))) + (and (derived-mode-p 'image-dired-thumbnail-mode) + (setq file (image-dired-original-file-name)) + (setq bounds (cons (point) (1+ (point))))) + (when-let ((tap-file (thing-at-point 'filename)) + ((not (equal (file-name-base tap-file) tap-file))) + (guess (embark-target-guess-file-at-point))) + (setq file (cadr guess) bounds (cddr guess)))) + (when file + `(file ,(abbreviate-file-name (expand-file-name file)) ,@bounds)))) + +(defun embark-target-package-at-point () + "Target the package on the current line in a packages buffer." + (when (derived-mode-p 'package-menu-mode) + (when-let ((pkg (get-text-property (point) 'tabulated-list-id))) + `(package ,(symbol-name (package-desc-name pkg)) + ,(line-beginning-position) . ,(line-end-position))))) + +(defun embark-target-email-at-point () + "Target the email address at point." + (when-let ((email (thing-at-point 'email))) + (when (string-prefix-p "mailto:" email) + (setq email (string-remove-prefix "mailto:" email))) + `(email ,email . ,(bounds-of-thing-at-point 'email)))) + +(defun embark-target-url-at-point () + "Target the URL at point." + (if-let ((url (or (get-text-property (point) 'shr-url) + (get-text-property (point) 'image-url)))) + `(url ,url + ,(previous-single-property-change + (min (1+ (point)) (point-max)) 'mouse-face nil (point-min)) + . ,(next-single-property-change + (point) 'mouse-face nil (point-max))) + (when-let ((url (thing-at-point 'url))) + `(url ,url . ,(thing-at-point-bounds-of-url-at-point t))))) + +(declare-function widget-at "wid-edit") + +(defun embark-target-custom-variable-at-point () + "Target the variable corresponding to the customize widget at point." + (when (derived-mode-p 'Custom-mode) + (save-excursion + (beginning-of-line) + (when-let* ((widget (widget-at (point))) + (var (and (eq (car widget) 'custom-visibility) + (plist-get (cdr widget) :parent))) + (sym (and (eq (car var) 'custom-variable) + (plist-get (cdr var) :value)))) + `(variable + ,(symbol-name sym) + ,(point) + . ,(progn + (re-search-forward ":" (line-end-position) 'noerror) + (point))))))) + +;; NOTE: There is also (thing-at-point 'list), however it does +;; not work on strings and requires the point to be inside the +;; parentheses. This version here is slightly more general. +(defun embark-target-expression-at-point () + "Target expression at point." + (cl-flet ((syntax-p (class &optional (delta 0)) + (and (<= (point-min) (+ (point) delta) (point-max)) + (eq (pcase class + ('open 4) ('close 5) ('prefix 6) ('string 7)) + (syntax-class (syntax-after (+ (point) delta))))))) + (when-let + ((start + (pcase-let ((`(_ ,open _ ,string _ _ _ _ ,start _ _) (syntax-ppss))) + (ignore-errors ; set start=nil if delimiters are unbalanced + (cond + (string start) + ((or (syntax-p 'open) (syntax-p 'prefix)) + (save-excursion (backward-prefix-chars) (point))) + ((syntax-p 'close -1) + (save-excursion + (backward-sexp) (backward-prefix-chars) (point))) + ((syntax-p 'string) (point)) + ((syntax-p 'string -1) (scan-sexps (point) -1)) + (t open))))) + (end (ignore-errors (scan-sexps start 1)))) + (unless (eq start (car (bounds-of-thing-at-point 'defun))) + `(expression ,(buffer-substring start end) ,start . ,end))))) + +(defmacro embark-define-overlay-target (name prop &optional pred type target) + "Define a target finder for NAME that targets overlays with property PROP. +The function defined is named embark-target-NAME-at-point and it +returns Embark targets based on the overlays around point. An +overlay provides a target if its property named PROP is non-nil. + +If the optional PRED argument is given, it should be an +expression and it further restricts the targets to only those +overlays for which PRED evaluates to non-nil. + +The target finder returns target type NAME or optional symbol +TYPE if given. + +The target finder returns the substring of the buffer covered by +the overlay as the target string or the result of evaluating the +optional TARGET expression if given. + +PRED and TARGET are expressions (not functions) and when evaluated the +symbols `%o' and `%p' are bound to the overlay and the overlay's +property respectively." + `(defun ,(intern (format "embark-target-%s-at-point" name)) () + ,(format "Target %s at point." name) + (when-let ((%o (seq-find + (lambda (%o) + (when-let ((%p (overlay-get %o ',prop))) + (ignore %p) + ,(or pred t))) + (overlays-in (max (point-min) (1- (point))) + (min (point-max) (1+ (point)))))) + (%p (overlay-get %o ',prop))) + (ignore %p) + (cons ',(or type name) + (cons ,(or target `(buffer-substring-no-properties + (overlay-start %o) (overlay-end %o))) + (cons (overlay-start %o) (overlay-end %o))))))) + +(embark-define-overlay-target flymake flymake-diagnostic) +(embark-define-overlay-target bug-reference bug-reference-url nil url %p) +(embark-define-overlay-target smerge smerge (eq %p 'conflict)) + +(defmacro embark-define-thingatpt-target (thing &rest modes) + "Define a target finder for THING using the thingatpt library. +The function defined is named embark-target-NAME-at-point and it +uses (thing-at-point 'THING) to find its targets. + +If any MODES are given, the target finder only applies to buffers +in one of those major modes." + (declare (indent 1)) + `(defun ,(intern (format "embark-target-%s-at-point" thing)) () + ,(format "Target %s at point." thing) + (when ,(if modes `(derived-mode-p ,@(mapcar (lambda (m) `',m) modes)) t) + (when-let (bounds (bounds-of-thing-at-point ',thing)) + (cons ',thing (cons + (buffer-substring (car bounds) (cdr bounds)) + bounds)))))) + +(embark-define-thingatpt-target defun) +(embark-define-thingatpt-target sentence + text-mode help-mode Info-mode man-common) +(embark-define-thingatpt-target paragraph + text-mode help-mode Info-mode man-common) + +(defmacro embark-define-regexp-target + (name regexp &optional type target bounds limit) + "Define a target finder for matches of REGEXP around point. +The function defined is named embark-target-NAME-at-point and it +uses (thing-at-point-looking-at REGEXP) to find its targets. + +The target finder returns target type NAME or optional symbol +TYPE if given. + +The target finder returns the substring of the buffer matched by +REGEXP as the target string or the result of evaluating the +optional TARGET expression if given. In the expression TARGET +you can use `match-string' to recover the match of the REGEXP or +of any sub-expressions it has. + +BOUNDS is an optional expression to compute the bounds of the +target and defaults to (cons (match-beginning 0) (match-end 0)). + +The optional LIMIT is the number of characters before and after +point to limit the search to. If LIMIT is nil, search a little +more than the current line (more precisely, the smallest interval +centered at point that includes the current line)." + `(defun ,(intern (format "embark-target-%s-at-point" name)) () + ,(format "Target %s at point." name) + (save-match-data + (when (thing-at-point-looking-at + ,regexp + ,(or limit '(max (- (pos-eol) (point)) (- (point) (pos-bol))))) + (cons ',(or type name) + (cons ,(or target '(match-string 0)) + ,(or bounds + '(cons (match-beginning 0) (match-end 0))))))))) + +(defun embark--identifier-types (identifier) + "Return list of target types appropriate for IDENTIFIER." + (let ((symbol (intern-soft identifier))) + (if (not + (or (derived-mode-p 'emacs-lisp-mode 'inferior-emacs-lisp-mode) + (and (not (derived-mode-p 'prog-mode)) + symbol + (or (boundp symbol) (fboundp symbol) (symbol-plist symbol))))) + '(identifier) + (let* ((library (ffap-el-mode identifier)) + (types + (append + (and (commandp symbol) '(command)) + (and symbol (boundp symbol) (not (keywordp symbol)) '(variable)) + (and (fboundp symbol) (not (commandp symbol)) '(function)) + (and (facep symbol) '(face)) + (and library '(library)) + (and (featurep 'package) (embark--package-desc symbol) + '(package))))) + (when (and library + (looking-back "\\(?:require\\|use-package\\).*" + (line-beginning-position))) + (setq types (embark--rotate types (cl-position 'library types)))) + (or types '(symbol)))))) + +(defun embark-target-identifier-at-point () + "Target identifier at point. + +In Emacs Lisp and IELM buffers the identifier is promoted to a +symbol, for which more actions are available. Identifiers are +also promoted to symbols if they are interned Emacs Lisp symbols +and found in a buffer in a major mode that is not derived from +`prog-mode' (this is intended for when you might be reading or +writing about Emacs). + +As a convenience, in Org Mode an initial ' or surrounding == or +~~ are removed." + (when-let (bounds (bounds-of-thing-at-point 'symbol)) + (let ((name (buffer-substring (car bounds) (cdr bounds)))) + (when (derived-mode-p 'org-mode) + (cond ((string-prefix-p "'" name) + (setq name (substring name 1)) + (cl-incf (car bounds))) + ((string-match-p "^\\([=~]\\).*\\1$" name) + (setq name (substring name 1 -1)) + (cl-incf (car bounds)) + (cl-decf (cdr bounds))))) + (mapcar (lambda (type) `(,type ,name . ,bounds)) + (embark--identifier-types name))))) + +(defun embark-target-heading-at-point () + "Target the outline heading at point." + (let ((beg (line-beginning-position)) + (end (line-end-position))) + (when (save-excursion + (goto-char beg) + (and (bolp) + (looking-at + ;; default definition from outline.el + (or (bound-and-true-p outline-regexp) "[*\^L]+")))) + (require 'outline) ;; Ensure that outline commands are available + `(heading ,(buffer-substring beg end) ,beg . ,end)))) + +(defun embark-target-text-heading-at-point () + "Target the outline heading at point in text modes." + (when (derived-mode-p 'text-mode) + (embark-target-heading-at-point))) + +(defun embark-target-prog-heading-at-point () + "Target the outline heading at point in programming modes." + (when (derived-mode-p 'prog-mode) + (embark-target-heading-at-point))) + +(defun embark-target-top-minibuffer-candidate () + "Target the top completion candidate in the minibuffer. +Return the category metadatum as the type of the target. + +This target finder is meant for the default completion UI and +completion UI highly compatible with it, like Icomplete. +Many completion UIs can still work with Embark but will need +their own target finder. See for example +`embark--vertico-selected'." + (when (and (minibufferp) minibuffer-completion-table) + (pcase-let* ((`(,category . ,candidates) (embark-minibuffer-candidates)) + (contents (minibuffer-contents)) + (top (if (test-completion contents + minibuffer-completion-table + minibuffer-completion-predicate) + contents + (let ((completions (completion-all-sorted-completions))) + (if (null completions) + contents + (concat + (substring contents + 0 (or (cdr (last completions)) 0)) + (car completions))))))) + (cons category (or (car (member top candidates)) top))))) + +(defun embark-target-collect-candidate () + "Target the collect candidate at point." + (when (derived-mode-p 'embark-collect-mode) + (when-let ((button + (pcase (get-text-property (point) 'tabulated-list-column-name) + ("Candidate" (button-at (point))) + ("Annotation" (previous-button (point))))) + (start (button-start button)) + (end (button-end button)) + (candidate (tabulated-list-get-id))) + `(,embark--type + ,(if (eq embark--type 'file) + (abbreviate-file-name (expand-file-name candidate)) + candidate) + ,start . ,end)))) + +(defun embark-target-completion-list-candidate () + "Return the completion candidate at point in a completions buffer." + (when (derived-mode-p 'completion-list-mode) + (if (not (get-text-property (point) 'mouse-face)) + (user-error "No completion here") + ;; this fairly delicate logic is taken from `choose-completion' + (let (beg end) + (cond + ((and (not (eobp)) (get-text-property (point) 'mouse-face)) + (setq end (point) beg (1+ (point)))) + ((and (not (bobp)) + (get-text-property (1- (point)) 'mouse-face)) + (setq end (1- (point)) beg (point))) + (t (user-error "No completion here"))) + (setq beg (previous-single-property-change beg 'mouse-face)) + (setq end (or (next-single-property-change end 'mouse-face) + (point-max))) + (let ((raw (or (get-text-property beg 'completion--string) + (buffer-substring beg end)))) + `(,embark--type + ,(if (eq embark--type 'file) + (abbreviate-file-name (expand-file-name raw)) + raw) + ,beg . ,end)))))) + +(defun embark--cycle-key () + "Return the key to use for `embark-cycle'." + (if embark-cycle-key + (if (key-valid-p embark-cycle-key) + (key-parse embark-cycle-key) + (error "`embark-cycle-key' is invalid")) + (car (where-is-internal #'embark-act)))) + +(defun embark--raw-action-keymap (type) + "Return raw action map for targets of given TYPE. +This does not take into account the default action, help key or +cycling bindings, just what's registered in +`embark-keymap-alist'." + (make-composed-keymap + (mapcar #'symbol-value + (let ((actions (or (alist-get type embark-keymap-alist) + (alist-get t embark-keymap-alist)))) + (ensure-list actions))))) + +(defun embark--action-keymap (type cycle) + "Return action keymap for targets of given TYPE. +If CYCLE is non-nil bind `embark-cycle'." + (make-composed-keymap + (let ((map (make-sparse-keymap)) + (default-action (embark--default-action type))) + (define-key map [13] default-action) + (when-let ((cycle-key (and cycle (embark--cycle-key)))) + (define-key map cycle-key #'embark-cycle)) + (when embark-help-key + (keymap-set map embark-help-key #'embark-keymap-help)) + map) + (embark--raw-action-keymap type))) + +(defun embark--truncate-target (target) + "Truncate TARGET string." + (unless (stringp target) + (setq target (format "%s" target))) + (if-let (pos (string-match-p "\n" target)) + (concat (car (split-string target "\n" 'omit-nulls "\\s-*")) "…") + target)) + +;;;###autoload +(defun embark-eldoc-first-target (report &rest _) + "Eldoc function reporting the first Embark target at point. +This function uses the eldoc REPORT callback and is meant to be +added to `eldoc-documentation-functions'." + (when-let (((not (minibufferp))) + (target (car (embark--targets)))) + (funcall report + (format "Embark on %s ‘%s’" + (plist-get target :type) + (embark--truncate-target (plist-get target :target)))))) + +;;;###autoload +(defun embark-eldoc-target-types (report &rest _) + "Eldoc function reporting the types of all Embark targets at point. +This function uses the eldoc REPORT callback and is meant to be +added to `eldoc-documentation-functions'." + (when-let (((not (minibufferp))) + (targets (embark--targets))) + (funcall report + (format "Embark target types: %s" + (mapconcat + (lambda (target) (symbol-name (plist-get target :type))) + targets + ", "))))) + +(defun embark--format-targets (target shadowed-targets rep) + "Return a formatted string indicating the TARGET of an action. + +This is used internally by the minimal indicator and for the +targets section of the verbose indicator. The string will also +mention any SHADOWED-TARGETS. A non-nil REP indicates we are in +a repeating sequence of actions." + (let ((act (propertize + (cond + ((plist-get target :multi) "∀ct") + (rep "Rep") + (t "Act")) + 'face 'highlight))) + (cond + ((eq (plist-get target :type) 'embark-become) + (propertize "Become" 'face 'highlight)) + ((and (minibufferp) + (not (eq 'embark-keybinding + (completion-metadata-get + (embark--metadata) + 'category)))) + ;; we are in a minibuffer but not from the + ;; completing-read prompter, use just "Act" + act) + ((plist-get target :multi) + (format "%s on %s %ss" + act + (plist-get target :multi) + (plist-get target :type))) + (t (format + "%s on %s%s ‘%s’" + act + (plist-get target :type) + (if shadowed-targets + (format (propertize "(%s)" 'face 'shadow) + (mapconcat + (lambda (target) (symbol-name (plist-get target :type))) + shadowed-targets + ", ")) + "") + (embark--truncate-target (plist-get target :target))))))) + +(defun embark-minimal-indicator () + "Minimal indicator, appearing in the minibuffer prompt or echo area. +This indicator displays a message showing the types of all +targets, starting with the current target, and the value of the +current target. The message is displayed in the echo area, or if +the minibuffer is open, the message is added to the prompt." + (lambda (&optional keymap targets _prefix) + (if (null keymap) + (when embark--minimal-indicator-overlay + (delete-overlay embark--minimal-indicator-overlay) + (setq-local embark--minimal-indicator-overlay nil)) + (let ((indicator (embark--format-targets + (car targets) (cdr targets) + (eq (lookup-key keymap [13]) #'embark-done)))) + (if (not (minibufferp)) + (message "%s" indicator) + (unless embark--minimal-indicator-overlay + (setq-local embark--minimal-indicator-overlay + (make-overlay (point-min) (point-min) + (current-buffer) t t))) + (overlay-put embark--minimal-indicator-overlay + 'before-string (concat indicator + (if (<= (length indicator) + (* 0.4 (frame-width))) + " " + "\n")))))))) + +(defun embark--read-key-sequence (update) + "Read key sequence, call UPDATE function with prefix keys." + (let (timer prefix) + (unwind-protect + (progn + (when (functionp update) + (setq timer (run-at-time + 0.05 0.05 + (lambda () + (let ((new-prefix (this-single-command-keys))) + (unless (equal prefix new-prefix) + (setq prefix new-prefix) + (when (/= (length prefix) 0) + (funcall update prefix)))))))) + (read-key-sequence-vector nil nil nil t 'cmd-loop)) + (when timer + (cancel-timer timer))))) + +(defvar embark-indicators) ; forward declaration + +(defun embark-keymap-prompter (keymap update) + "Let the user choose an action using the bindings in KEYMAP. +Besides the bindings in KEYMAP, the user is free to use all their +key bindings and even \\[execute-extended-command] to select a command. +UPDATE is the indicator update function." + (let* ((keys (let ((overriding-terminal-local-map keymap)) + (embark--read-key-sequence update))) + (cmd (let ((overriding-terminal-local-map keymap)) + (key-binding keys 'accept-default)))) + ;; Set last-command-event as it would be from the command loop. + ;; Previously we only set it locally for digit-argument and for + ;; the mouse scroll commands handled in this function. But other + ;; commands can need it too! For example, electric-pair-mode users + ;; may wish to bind ( to self-insert-command in embark-region-map. + ;; Also, as described in issue #402, there are circumstances where + ;; you might run consult-narrow through the embark-keymap-prompter. + (setq last-command-event (aref keys (1- (length keys)))) + (pcase cmd + ((or 'embark-keymap-help + (and 'nil ; cmd is nil but last key is help-char + (guard (eq help-char (aref keys (1- (length keys))))))) + (let ((embark-indicators + (cl-set-difference embark-indicators + '(embark-verbose-indicator + embark-mixed-indicator))) + (prefix-map + (if (eq cmd 'embark-keymap-help) + keymap + (let ((overriding-terminal-local-map keymap)) + (key-binding (seq-take keys (1- (length keys))) + 'accept-default)))) + (prefix-arg prefix-arg)) ; preserve prefix arg + (when-let ((win (get-buffer-window embark--verbose-indicator-buffer + 'visible))) + (quit-window 'kill-buffer win)) + (embark-completing-read-prompter prefix-map update))) + ((or 'universal-argument 'universal-argument-more + 'negative-argument 'digit-argument 'embark-toggle-quit) + ;; prevent `digit-argument' from modifying the overriding map + (let ((overriding-terminal-local-map overriding-terminal-local-map)) + (command-execute cmd)) + (embark-keymap-prompter + (make-composed-keymap universal-argument-map keymap) + update)) + ((or 'minibuffer-keyboard-quit 'abort-recursive-edit 'abort-minibuffers) + nil) + ((guard (let ((def (lookup-key keymap keys))) ; if directly + ; bound, then obey + (and def (not (numberp def))))) ; number means "invalid prefix" + cmd) + ((and (pred symbolp) + (guard (string-suffix-p "self-insert-command" (symbol-name cmd)))) + (minibuffer-message "Not an action") + (embark-keymap-prompter keymap update)) + ((or 'scroll-other-window 'scroll-other-window-down) + (let ((minibuffer-scroll-window + ;; NOTE: Here we special case the verbose indicator! + (or (get-buffer-window embark--verbose-indicator-buffer 'visible) + minibuffer-scroll-window))) + (ignore-errors (command-execute cmd))) + (embark-keymap-prompter keymap update)) + ((or 'scroll-bar-toolkit-scroll 'mwheel-scroll + 'mac-mwheel-scroll 'pixel-scroll-precision) + (funcall cmd last-command-event) + (embark-keymap-prompter keymap update)) + ('execute-extended-command + (let ((prefix-arg prefix-arg)) ; preserve prefix arg + (intern-soft (read-extended-command)))) + ((or 'keyboard-quit 'keyboard-escape-quit) + nil) + (_ cmd)))) + +(defun embark--command-name (cmd) + "Return an appropriate name for CMD. +If CMD is a symbol, use its symbol name; for lambdas, use the +first line of the documentation string; for keyboard macros use +`key-description'; otherwise use the word \"unnamed\"." + (concat ; fresh copy, so we can freely add text properties + (cond + ((or (stringp cmd) (vectorp cmd)) (key-description cmd)) + ((stringp (car-safe cmd)) (car cmd)) + ((eq (car-safe cmd) 'menu-item) (eval (cadr cmd))) + ((keymapp cmd) + (propertize (if (symbolp cmd) (format "+%s" cmd) "") + 'face 'embark-keymap)) + ((symbolp cmd) + (let ((name (symbol-name cmd))) + (if (string-prefix-p "embark-action--" name) ; direct action mode + (format "(%s)" (string-remove-prefix "embark-action--" name)) + name))) + ((when-let (doc (and (functionp cmd) (ignore-errors (documentation cmd)))) + (save-match-data + (when (string-match "^\\(.*\\)$" doc) + (match-string 1 doc))))) + (t "")))) + +;; Taken from Marginalia, needed by the verbose indicator. +;; We cannot use the completion annotators in this case. +(defconst embark--advice-regexp + (rx bos + (1+ (seq (? "This function has ") + (or ":before" ":after" ":around" ":override" + ":before-while" ":before-until" ":after-while" + ":after-until" ":filter-args" ":filter-return") + " advice: " (0+ nonl) "\n")) + "\n") + "Regexp to match lines about advice in function documentation strings.") + +;; Taken from Marginalia, needed by the verbose indicator. +;; We cannot use the completion annotators in this case. +(defun embark--function-doc (sym) + "Documentation string of function SYM." + (let ((vstr (and (symbolp sym) (keymapp sym) (boundp sym) + (eq (symbol-function sym) (symbol-value sym)) + (documentation-property sym 'variable-documentation)))) + (when-let (str (or (ignore-errors (documentation sym)) vstr)) + ;; Replace standard description with variable documentation + (when (and vstr (string-match-p "\\`Prefix command" str)) + (setq str vstr)) + (save-match-data + (if (string-match embark--advice-regexp str) + (substring str (match-end 0)) + str))))) + +(defun embark--action-repeatable-p (action) + "Is ACTION repeatable? +When the return value is non-nil it will be the desired starting +point of the next target cycle or t to indicate the default, +namely that the target cycle for the next action should begin at +the type of the current target." + (or (cdr (assq action embark-repeat-actions)) + (and (memq action embark-repeat-actions) t))) + +(defun embark--formatted-bindings (keymap &optional nested) + "Return the formatted keybinding of KEYMAP. +The keybindings are returned in their order of appearance. +If NESTED is non-nil subkeymaps are not flattened." + (let* ((commands + (cl-loop for (key . def) in (embark--all-bindings keymap nested) + for name = (embark--command-name def) + for cmd = (keymap--menu-item-binding def) + unless (memq cmd '(nil embark-keymap-help + negative-argument digit-argument)) + collect (list name cmd key + (concat + (if (eq (car-safe def) 'menu-item) + "menu-item" + (key-description key)))))) + (width (cl-loop for (_name _cmd _key desc) in commands + maximize (length desc))) + (default) + (candidates + (cl-loop for item in commands + for (name cmd key desc) = item + for desc-rep = + (concat + (propertize desc 'face 'embark-keybinding) + (and (embark--action-repeatable-p cmd) + embark-keybinding-repeat)) + for formatted = + (propertize + (concat desc-rep + (make-string (- width (length desc-rep) -1) ?\s) + name) + 'embark-command cmd) + when (equal key [13]) + do (setq default formatted) + collect (cons formatted item)))) + (cons candidates default))) + +(defun embark--with-category (category candidates) + "Return completion table for CANDIDATES of CATEGORY with sorting disabled." + (lambda (string predicate action) + (if (eq action 'metadata) + `(metadata (display-sort-function . identity) + (cycle-sort-function . identity) + (category . ,category)) + (complete-with-action + action candidates string predicate)))) + +(defun embark-completing-read-prompter (keymap update &optional no-default) + "Prompt via completion for a command bound in KEYMAP. +If NO-DEFAULT is t, no default value is passed to`completing-read'. + +UPDATE is the indicator update function. It is not used directly +here, but if the user switches to `embark-keymap-prompter', the +UPDATE function is passed to it." + (let* ((candidates+def (embark--formatted-bindings keymap)) + (candidates (car candidates+def)) + (def (and (not no-default) (cdr candidates+def))) + (buf (current-buffer)) + (choice + (catch 'choice + (minibuffer-with-setup-hook + (lambda () + (let ((map (make-sparse-keymap))) + (define-key map "\M-q" + (lambda () + (interactive) + (with-current-buffer buf + (embark-toggle-quit)))) + (when-let (cycle (embark--cycle-key)) + ;; Rebind `embark-cycle' in order allow cycling + ;; from the `completing-read' prompter. Additionally + ;; `embark-cycle' can be selected via + ;; `completing-read'. The downside is that this breaks + ;; recursively acting on the candidates of type + ;; embark-keybinding in the `completing-read' prompter. + (define-key map cycle + (cond + ((eq (lookup-key keymap cycle) 'embark-cycle) + (lambda () + (interactive) + (throw 'choice 'embark-cycle))) + ((null embark-cycle-key) + (lambda () + (interactive) + (minibuffer-message + "No cycling possible; press `%s' again to act." + (key-description cycle)) + (define-key map cycle #'embark-act)))))) + (when embark-keymap-prompter-key + (keymap-set map embark-keymap-prompter-key + (lambda () + (interactive) + (message "Press key binding") + (let ((cmd (embark-keymap-prompter keymap update))) + (if (null cmd) + (user-error "Unknown key") + (throw 'choice cmd)))))) + (use-local-map + (make-composed-keymap map (current-local-map))))) + (completing-read + "Command: " + (embark--with-category 'embark-keybinding candidates) + nil nil nil 'embark--prompter-history def))))) + (pcase (assoc choice candidates) + (`(,_formatted ,_name ,cmd ,key ,_desc) + ;; Set last-command-event as it would be from the command loop. + (setq last-command-event (aref key (1- (length key)))) + cmd) + ('nil (intern-soft choice))))) + +;;; Verbose action indicator + +(defgroup embark-indicators nil + "Indicators display information about actions and targets." + :group 'embark) + +(defcustom embark-indicators + '(embark-mixed-indicator + embark-highlight-indicator + embark-isearch-highlight-indicator) + "Indicator functions to use when acting or becoming. +The indicator functions are called from both `embark-act' and +from `embark-become' and should display information about this to +the user, such as: which of those two commands is running; a +description of the key bindings that are available for actions or +commands to become; and, in the case of `embark-act', the type +and value of the targets, and whether other targets are available +via `embark-cycle'. The indicator function is free to display as +much or as little of this information as desired and can use any +Emacs interface elements to do so. + +Embark comes with five such indicators: + +- `embark-minimal-indicator', which does not display any + information about keybindings, but does display types and + values of action targets in the echo area or minibuffer prompt, + +- `embark-verbose-indicator', which pops up a buffer containing + detailed information including key bindings and the first line + of the docstring of the commands they run, and + +- `embark-mixed-indicator', which combines the minimal and the + verbose indicator: the minimal indicator is shown first and the + verbose popup is shown after `embark-mixed-indicator-delay' + seconds. + +- `embark-highlight-indicator', which highlights the target + at point. + +- `embark-isearch-highlight-indicator', which when the target at + point is an identifier or symbol, lazily highlights all + occurrences of it. + +The protocol for indicator functions is as follows: + +When called from `embark-act', an indicator function is called +without arguments. The indicator function should then return a +closure, which captures the indicator state. The returned +closure must accept up to three optional arguments, the action +keymap, the targets (plists as returned by `embark--targets') and +the prefix keys typed by the user so far. The keymap, targets +and prefix keys may be updated when cycling targets at point +resulting in multiple calls to the closure. When called from +`embark-become', the indicator closure will be called with the +keymap of commands to become, a fake target list containing a +single target of type `embark-become' and whose value is the +minibuffer input, and the prefix set to nil. Note, in +particular, that if an indicator function wishes to distinguish +between `embark-act' and `embark-become' it should check whether +the `car' of the first target is `embark-become'. + +After the action has been performed the indicator closure is +called without arguments, such that the indicator can perform the +necessary cleanup work. For example, if the indicator adds +overlays, it should remove these overlays. The indicator should +be written in a way that it is safe to call it for cleanup more +than once, in fact, it should be able to handle any sequence of +update and cleanup calls ending in a call for cleanup. + +NOTE: Experience shows that the indicator calling convention may +change again in order to support more action features. The +calling convention should currently be considered unstable. +Please keep this in mind when writing a custom indicator +function, or when using the `which-key' indicator function from +the wiki." + :type '(repeat + (choice + (const :tag "Verbose indicator" embark-verbose-indicator) + (const :tag "Minimal indicator" embark-minimal-indicator) + (const :tag "Mixed indicator" embark-mixed-indicator) + (const :tag "Highlight target" embark-highlight-indicator) + (const :tag "Highlight all occurrences" + embark-isearch-highlight-indicator) + (function :tag "Other")))) + +(defface embark-verbose-indicator-documentation + '((t :inherit completions-annotations)) + "Face used by the verbose action indicator to display binding descriptions. +Used by `embark-verbose-indicator'.") + +(defface embark-verbose-indicator-title '((t :height 1.1 :weight bold)) + "Face used by the verbose action indicator for the title. +Used by `embark-verbose-indicator'.") + +(defface embark-verbose-indicator-shadowed '((t :inherit shadow)) + "Face used by the verbose action indicator for the shadowed targets. +Used by `embark-verbose-indicator'.") + +(defcustom embark-verbose-indicator-display-action + '(display-buffer-reuse-window) + "Parameters added to `display-buffer-alist' to show the actions buffer. +See the docstring of `display-buffer' for information on what +display actions and parameters are available." + :type `(choice + (const :tag "Reuse some window" + (display-buffer-reuse-window)) + (const :tag "Below target buffer" + (display-buffer-below-selected + (window-height . fit-window-to-buffer))) + (const :tag "Bottom of frame (fixed-size)" + (display-buffer-at-bottom)) + (const :tag "Bottom of frame (resizes during cycling)" + (display-buffer-at-bottom + (window-height . fit-window-to-buffer))) + (const :tag "Side window on the right" + (display-buffer-in-side-window (side . right))) + (const :tag "Side window on the left" + (display-buffer-in-side-window (side . left))) + (sexp :tag "Other"))) + +(defcustom embark-verbose-indicator-excluded-actions nil + "Commands not displayed by `embark-verbose-indicator'. +This variable should be set to a list of symbols and regexps. +The verbose indicator will exclude from its listing any commands +matching an element of this list." + :type '(choice + (const :tag "Exclude nothing" nil) + (const :tag "Exclude Embark general actions" + (embark-collect embark-live embark-export + embark-cycle embark-act-all embark-keymap-help + embark-become embark-isearch-forward + embark-isearch-backward)) + (repeat :tag "Other" (choice regexp symbol)))) + +(defcustom embark-verbose-indicator-buffer-sections + `(target "\n" shadowed-targets " " cycle "\n" bindings) + "List of sections to display in the verbose indicator buffer, in order. +You can use either a symbol designating a concrete section (one +of the keywords below, but without the colon), a string literal +or a function returning a string or list of strings to insert and +that accepts the following keyword arguments: + +- `:target', the target as a cons of type and value, +- `:shadowed-targets', a list of conses for the other targets, +- `:bindings' a list returned by `embark--formatted-bindings', and +- `:cycle', a string describing the key binding of `embark-cycle'." + :type '(repeat + (choice (const :tag "Current target name" target) + (const :tag "List of other shadowed targets" shadowed-targets) + (const :tag "Key bindings" bindings) + (const :tag "Cycle indicator" cycle) + (string :tag "Literal string") + (function :tag "Custom function")))) + +(defcustom embark-verbose-indicator-nested t + "Whether the verbose indicator should use nested keymap navigation. +When this variable is non-nil the actions buffer displayed by +`embark-verbose-indicator' will include any prefix keys found in +the keymap it is displaying, and will update to show what is +bound under the prefix if the prefix is pressed. If this +variable is nil, then the actions buffer will contain a flat list +of all full key sequences bound in the keymap." + :type 'boolean) + +(defun embark--verbose-indicator-excluded-p (cmd) + "Return non-nil if CMD should be excluded from the verbose indicator." + (seq-find (lambda (x) + (if (symbolp x) + (eq cmd x) + (string-match-p x (symbol-name cmd)))) + embark-verbose-indicator-excluded-actions)) + +(cl-defun embark--verbose-indicator-section-target + (&key targets bindings &allow-other-keys) + "Format the TARGETS section for the indicator buffer. +BINDINGS is the formatted list of keybindings." + (let ((result (embark--format-targets + (car targets) + nil ; the shadowed targets section deals with these + (cl-find 'embark-done bindings :key #'caddr :test #'eq)))) + (add-face-text-property 0 (length result) + 'embark-verbose-indicator-title + 'append + result) + result)) + +(cl-defun embark--verbose-indicator-section-cycle + (&key cycle shadowed-targets &allow-other-keys) + "Format the CYCLE key section for the indicator buffer. +SHADOWED-TARGETS is the list of other targets." + (concat + (and cycle (propertize (format "(%s to cycle)" cycle) + 'face 'embark-verbose-indicator-shadowed)) + (and shadowed-targets "\n"))) + +(cl-defun embark--verbose-indicator-section-shadowed-targets + (&key shadowed-targets &allow-other-keys) + "Format the SHADOWED-TARGETS section for the indicator buffer." + (when shadowed-targets + (propertize (format "Shadowed targets at point: %s" + (string-join shadowed-targets ", ")) + 'face 'embark-verbose-indicator-shadowed))) + +(cl-defun embark--verbose-indicator-section-bindings + (&key bindings &allow-other-keys) + "Format the BINDINGS section for the indicator buffer." + (let* ((max-width (apply #'max (cons 0 (mapcar (lambda (x) + (string-width (car x))) + bindings)))) + (fmt (format "%%-%ds" (1+ max-width))) + (result nil)) + (dolist (binding bindings (string-join (nreverse result))) + (let ((cmd (caddr binding))) + (unless (embark--verbose-indicator-excluded-p cmd) + (let ((keys (format fmt (car binding))) + (doc (embark--function-doc cmd))) + (push (format "%s%s\n" keys + (propertize + (car (split-string (or doc "") "\n")) + 'face 'embark-verbose-indicator-documentation)) + result))))))) + +(defun embark--verbose-indicator-update (keymap targets) + "Update verbose indicator buffer. +The arguments are the new KEYMAP and TARGETS." + (with-current-buffer (get-buffer-create embark--verbose-indicator-buffer) + (let* ((inhibit-read-only t) + (bindings + (embark--formatted-bindings keymap embark-verbose-indicator-nested)) + (bindings (car bindings)) + (shadowed-targets (mapcar + (lambda (x) (symbol-name (plist-get x :type))) + (cdr targets))) + (cycle (let ((ck (where-is-internal #'embark-cycle keymap))) + (and ck (key-description (car ck)))))) + (setq-local cursor-type nil) + (setq-local truncate-lines t) + (setq-local buffer-read-only t) + (erase-buffer) + (dolist (section embark-verbose-indicator-buffer-sections) + (insert + (if (stringp section) + section + (or (funcall + (let ((prefixed (intern (format + "embark--verbose-indicator-section-%s" + section)))) + (cond + ((fboundp prefixed) prefixed) + ((fboundp section) section) + (t (error "Undefined verbose indicator section `%s'" + section)))) + :targets targets :shadowed-targets shadowed-targets + :bindings bindings :cycle cycle) + "")))) + (goto-char (point-min))))) + +(defun embark-verbose-indicator () + "Indicator that displays a table of key bindings in a buffer. +The default display includes the type and value of the current +target, the list of other target types, and a table of key +bindings, actions and the first line of their docstrings. + +The order and formatting of these items is completely +configurable through the variable +`embark-verbose-indicator-buffer-sections'. + +If the keymap being shown contains prefix keys, the table of key +bindings can either show just the prefixes and update once the +prefix is pressed, or it can contain a flat list of all full key +sequences bound in the keymap. This is controlled by the +variable `embark-verbose-indicator-nested'. + +To reduce clutter in the key binding table, one can set the +variable `embark-verbose-indicator-excluded-actions' to a list +of symbols and regexps matching commands to exclude from the +table. + +To configure how a window is chosen to display this buffer, see +the variable `embark-verbose-indicator-display-action'." + (lambda (&optional keymap targets prefix) + (if (not keymap) + (when-let ((win (get-buffer-window embark--verbose-indicator-buffer + 'visible))) + (quit-window 'kill-buffer win)) + (embark--verbose-indicator-update + (if (and prefix embark-verbose-indicator-nested) + ;; Lookup prefix keymap globally if not found in action keymap + (let ((overriding-terminal-local-map keymap)) + (key-binding prefix 'accept-default)) + keymap) + targets) + (let ((display-buffer-alist + `(,@display-buffer-alist + (,(regexp-quote embark--verbose-indicator-buffer) + ,@embark-verbose-indicator-display-action)))) + (display-buffer embark--verbose-indicator-buffer))))) + +(defcustom embark-mixed-indicator-delay 0.5 + "Time in seconds after which the verbose indicator is shown. +The mixed indicator starts by showing the minimal indicator and +after this delay shows the verbose indicator." + :type '(choice (const :tag "No delay" 0) + (number :tag "Delay in seconds"))) + +(defcustom embark-mixed-indicator-both nil + "Show both indicators, even after the verbose indicator appeared." + :type 'boolean) + +(defun embark-mixed-indicator () + "Mixed indicator showing keymap and targets. +The indicator shows the `embark-minimal-indicator' by default. +After `embark-mixed-indicator-delay' seconds, the +`embark-verbose-indicator' is shown. This which-key-like approach +ensures that Embark stays out of the way for quick actions. The +helpful keybinding reminder still pops up automatically without +further user intervention." + (let ((vindicator (embark-verbose-indicator)) + (mindicator (embark-minimal-indicator)) + vindicator-active + vtimer) + (lambda (&optional keymap targets prefix) + ;; Always cancel the timer. + ;; 1. When updating, cancel timer, since the user has pressed + ;; a key before the timer elapsed. + ;; 2. For cleanup, the timer must also be canceled. + (when vtimer + (cancel-timer vtimer) + (setq vtimer nil)) + (if (not keymap) + (progn + (funcall vindicator) + (when mindicator + (funcall mindicator))) + (when mindicator + (funcall mindicator keymap targets prefix)) + (if vindicator-active + (funcall vindicator keymap targets prefix) + (setq vtimer + (run-at-time + embark-mixed-indicator-delay nil + (lambda () + (when (and (not embark-mixed-indicator-both) mindicator) + (funcall mindicator) + (setq mindicator nil)) + (setq vindicator-active t) + (funcall vindicator keymap targets prefix))))))))) + +;;;###autoload +(defun embark-bindings-in-keymap (keymap) + "Explore command key bindings in KEYMAP with `completing-read'. +The selected command will be executed. Interactively, prompt the +user for a KEYMAP variable." + (interactive + (list + (symbol-value + (intern-soft + (completing-read + "Keymap: " + (embark--with-category + 'variable + (cl-loop for x being the symbols + if (and (boundp x) (keymapp (symbol-value x))) + collect (symbol-name x))) + nil t nil 'variable-name-history + (let ((major-mode-map + (concat (symbol-name major-mode) "-map"))) + (when (intern-soft major-mode-map) major-mode-map))))))) + (when-let (command (embark-completing-read-prompter keymap nil 'no-default)) + (call-interactively command))) + +;;;###autoload +(defun embark-bindings (global) + "Explore current command key bindings with `completing-read'. +The selected command will be executed. + +This shows key bindings from minor mode maps and the local +map (usually set by the major mode), but also less common keymaps +such as those from a text property or overlay, or the overriding +maps: `overriding-terminal-local-map' and `overriding-local-map'. + +Additionally, if GLOBAL is non-nil (interactively, if called with +a prefix argument), this command includes global key bindings." + (interactive "P") + (embark-bindings-in-keymap + (make-composed-keymap + (let ((all-maps (current-active-maps t))) + (if global all-maps (remq global-map all-maps)))))) + +;;;###autoload +(defun embark-bindings-at-point () + "Explore all key bindings at point with `completing-read'. +The selected command will be executed. + +This command lists key bindings found in keymaps specified by the +text properties `keymap' or `local-map', from either buffer text +or an overlay. These are not widely used in Emacs, and when they +are used can be somewhat hard to discover. Examples of locations +that have such a keymap are links and images in `eww' buffers, +attachment links in `gnus' article buffers, and the stash line +in a `vc-dir' buffer." + (interactive) + (if-let ((keymaps (delq nil (list (get-char-property (point) 'keymap) + (get-char-property (point) 'local-map))))) + (embark-bindings-in-keymap (make-composed-keymap keymaps)) + (user-error "No key bindings found at point"))) + +;;;###autoload +(defun embark-prefix-help-command () + "Prompt for and run a command bound in the prefix used for this command. +The prefix described consists of all but the last event of the +key sequence that ran this command. This function is intended to +be used as a value for `prefix-help-command'. + +In addition to using completion to select a command, you can also +type @ and the key binding (without the prefix)." + (interactive) + (when-let ((keys (this-command-keys-vector)) + (prefix (seq-take keys (1- (length keys)))) + (keymap (key-binding prefix 'accept-default))) + (minibuffer-with-setup-hook + (lambda () + (let ((pt (- (minibuffer-prompt-end) 2))) + (overlay-put (make-overlay pt pt) 'before-string + (format " under %s" (key-description prefix))))) + (embark-bindings-in-keymap keymap)))) + +(defun embark--prompt (indicators keymap targets) + "Call the prompter with KEYMAP and INDICATORS. +The TARGETS are displayed for actions outside the minibuffer." + (mapc (lambda (i) (funcall i keymap targets)) indicators) + (condition-case nil + (minibuffer-with-setup-hook + (lambda () + ;; if the prompter opens its own minibuffer, show + ;; the indicator there too + (let ((inner-indicators (mapcar #'funcall embark-indicators))) + (mapc (lambda (i) (funcall i keymap targets)) inner-indicators) + (add-hook 'minibuffer-exit-hook + (lambda () (mapc #'funcall inner-indicators)) + nil t))) + (let ((enable-recursive-minibuffers t)) + (funcall embark-prompter keymap + (lambda (prefix) + (mapc (lambda (i) (funcall i keymap targets prefix)) + indicators))))) + (quit nil))) + +(defvar embark--run-after-command-functions nil + "Abnormal hook, used by `embark--run-after-command'.") + +(defun embark--run-after-command (fn &rest args) + "Call FN with ARGS after the current commands finishes. +If multiple functions are queued with this function during the +same command, they will be called in the order from the one +queued most recently to the one queued least recently." + ;; We don't simply add FN to `post-command-hook' because FN may recursively + ;; call this function. In that case, FN would modify `post-command-hook' + ;; from within post-command-hook, which doesn't behave properly in our case. + ;; We use our own abnormal hook and run it from PCH in a way that it is OK to + ;; modify it from within its own functions. + (unless embark--run-after-command-functions + (let (pch timer has-run) + (setq pch + (lambda () + (remove-hook 'post-command-hook pch) + (cancel-timer timer) + (unless has-run + (setq has-run t) + (while embark--run-after-command-functions + ;; The following funcall may recursively call + ;; `embark--run-after-command', modifying + ;; `embark--run-after-command-functions'. This is why this + ;; loop has to be implemented carefully. We have to pop the + ;; function off the hook before calling it. Using `dolist' + ;; on the hook would also be incorrect, because it wouldn't + ;; take modifications of this hook into account. + (with-demoted-errors "embark PCH: %S" + (condition-case nil + (funcall (pop embark--run-after-command-functions)) + (quit (message "Quit")))))))) + (add-hook 'post-command-hook pch 'append) + ;; Generally we prefer `post-command-hook' because it plays well with + ;; keyboard macros. In some cases, `post-command-hook' isn't run after + ;; exiting a recursive edit, so set up the following timer as a backup. + (setq timer (run-at-time 0 nil pch)))) + + ;; Keep the default-directory alive, since this is often overwritten, + ;; for example by Consult commands. + ;; TODO it might be necessary to add more dynamically bound variables + ;; here. What we actually want are functions `capture-dynamic-scope' + ;; and `eval-in-dynamic-scope', but this does not exist? + (let ((dir default-directory)) + (push (lambda () + (let ((default-directory dir)) + (apply fn args))) + embark--run-after-command-functions))) + +(defun embark--quit-and-run (fn &rest args) + "Quit the minibuffer and then call FN with ARGS. +If called outside the minibuffer, simply apply FN to ARGS." + (if (not (minibufferp)) + (apply fn args) + (apply #'embark--run-after-command fn args) + (embark--run-after-command #'set 'ring-bell-function ring-bell-function) + (setq ring-bell-function #'ignore) + (if (fboundp 'minibuffer-quit-recursive-edit) + (minibuffer-quit-recursive-edit) + (abort-recursive-edit)))) + +(defun embark--run-action-hooks (hooks action target quit) + "Run HOOKS for ACTION. +The HOOKS argument must be alist. The keys t and :always are +treated specially. The :always hooks are executed always and the +t hooks are the default hooks, for when there are no +command-specific hooks for ACTION. The QUIT, ACTION and TARGET +arguments are passed to the hooks as keyword arguments." + (mapc (lambda (h) (apply h :action action :quit quit target)) + (or (alist-get action hooks) + (alist-get t hooks))) + (mapc (lambda (h) (apply h :action action :quit quit target)) + (alist-get :always hooks))) + +(defun embark--run-around-action-hooks + (action target quit &optional non-interactive) + "Run the `embark-around-action-hooks' for ACTION. +All the applicable around hooks are composed in the order they +are present in `embark-around-action-hooks'. The keys t and +:always in `embark-around-action-hooks' are treated specially. +The :always hooks are executed always (outermost) and the t hooks +are the default hooks, for when there are no command-specific +hooks for ACTION. The QUIT, ACTION and TARGET arguments are +passed to the hooks as keyword arguments. + +The optional argument NON-INTERACTIVE controls whether the action +is run with `command-execute' or with `funcall' passing the +target as argument." + (apply + (seq-reduce + (lambda (fn hook) + (lambda (&rest args) (apply hook (plist-put args :run fn)))) + (let ((hooks embark-around-action-hooks)) + (reverse + (append (or (alist-get action hooks) (alist-get t hooks)) + (alist-get :always hooks)))) + (if non-interactive + (lambda (&rest args) + (funcall (plist-get args :action) + (or (plist-get args :candidates) (plist-get args :target)))) + (lambda (&rest args) + (command-execute (plist-get args :action))))) + :action action :quit quit target)) + +(defun embark--act (action target &optional quit) + "Perform ACTION injecting the TARGET. +If called from a minibuffer with non-nil QUIT, quit the +minibuffer before executing the action." + (if (memq action '(embark-become ; these actions should run in + embark-collect ; the current buffer, not the + embark-live ; target buffer + embark-export + embark-select + embark-act-all)) + (progn + (embark--run-action-hooks embark-pre-action-hooks action target quit) + (unwind-protect (embark--run-around-action-hooks action target quit) + (embark--run-action-hooks embark-post-action-hooks + action target quit))) + (let* ((command embark--command) + (prefix prefix-arg) + (action-window (embark--target-window t)) + (directory default-directory) + (inject + (lambda () + (let ((contents (minibuffer-contents))) + (delete-minibuffer-contents) + (insert + (propertize + (substring-no-properties (plist-get target :target)) + 'embark--initial-input contents))) + (if (memq 'ivy--queue-exhibit post-command-hook) + ;; Ivy has special needs: (1) for file names + ;; ivy-immediate-done is not equivalent to + ;; exit-minibuffer, (2) it needs a chance to run + ;; its post command hook first, so use depth 10 + (add-hook 'post-command-hook 'ivy-immediate-done 10 t) + (add-hook 'post-command-hook #'exit-minibuffer nil t)) + (embark--run-action-hooks embark-target-injection-hooks + action target quit))) + (dedicate (and (derived-mode-p 'embark-collect-mode) + (not (window-dedicated-p)) + (selected-window))) + (multi (memq action embark-multitarget-actions)) + (run-action + (if (and (commandp action) (not multi)) + (lambda () + (let (final-window) + (when dedicate (set-window-dedicated-p dedicate t)) + (unwind-protect + (with-selected-window action-window + (let ((enable-recursive-minibuffers t) + (embark--command command) + (prefix-arg prefix) + ;; the next two avoid mouse dialogs + (use-dialog-box nil) + (last-nonmenu-event 13) + (default-directory directory)) + (embark--run-action-hooks embark-pre-action-hooks + action target quit) + (minibuffer-with-setup-hook inject + ;; pacify commands that use (this-command-keys) + (when (= (length (this-command-keys)) 0) + (set--this-command-keys + (if (characterp last-command-event) + (string last-command-event) + "\r"))) + (setq this-command action) + (embark--run-around-action-hooks + action target quit))) + (setq final-window (selected-window))) + (embark--run-action-hooks embark-post-action-hooks + action target quit) + (when dedicate (set-window-dedicated-p dedicate nil))) + (unless (eq final-window action-window) + (select-window final-window)))) + (let ((target + (if (and multi (null (plist-get target :candidates))) + (plist-put + target :candidates (list (plist-get target :target))) + target))) + (lambda () + (with-selected-window action-window + (embark--run-action-hooks embark-pre-action-hooks + action target quit) + (unwind-protect + (let ((current-prefix-arg prefix) + (default-directory directory)) + (embark--run-around-action-hooks + action target quit :non-interactive)) + (embark--run-action-hooks embark-post-action-hooks + action target quit)))))))) + (setq prefix-arg nil) + (if quit (embark--quit-and-run run-action) (funcall run-action))))) + +(defun embark--refine-multi-category (_type target) + "Refine `multi-category' TARGET to its actual type." + (or (let ((mc (get-text-property 0 'multi-category target))) + (cond + ;; The `cdr' of the `multi-category' property can be a buffer object. + ((and (eq (car mc) 'buffer) (buffer-live-p (cdr mc))) + (cons 'buffer (buffer-name (cdr mc)))) + ((stringp (cdr mc)) mc))) + (cons 'general target))) + +(defun embark--simplify-path (_type target) + "Simplify and '//' or '~/' in the TARGET file path." + (cons 'file (substitute-in-file-name target))) + +(defun embark--keybinding-command (_type target) + "Treat an `embark-keybinding' TARGET as a command." + (when-let ((cmd (get-text-property 0 'embark-command target))) + (cons 'command (format "%s" cmd)))) + +(defun embark--lookup-lighter-minor-mode (_type target) + "If TARGET is a lighter, look up its minor mode. + +The `describe-minor-mode' command has as completion candidates +both minor-modes and their lighters. This function replaces the +lighters by their minor modes, so actions expecting a function +work on them." + (cons 'minor-mode + (let ((symbol (intern-soft target))) + (if (and symbol (boundp symbol)) + target + (symbol-name (lookup-minor-mode-from-indicator target)))))) + +(declare-function project-current "project") +(declare-function project-roots "project") +(declare-function project-root "project") + +(defun embark--project-file-full-path (_type target) + "Get full path of project file TARGET." + ;; TODO project-find-file can be called from outside all projects in + ;; which case it prompts for a project first; we don't support that + ;; case yet, since there is no current project. + (cons 'file + (if-let ((project (project-current)) + (root (if (fboundp 'project-root) + (project-root project) + (with-no-warnings + (car (project-roots project)))))) + (expand-file-name target root) + target))) + +(defun embark--remove-package-version (_type target) + "Remove version number from a versioned package TARGET." + (cons 'package (replace-regexp-in-string "-[0-9.]+$" "" target))) + +(defun embark--targets () + "Retrieve current targets. + +An initial guess at the current targets and their types is +determined by running the functions in `embark-target-finders'. +Each function should either return nil, a pair of a type symbol +and target string or a triple of a type symbol, target string and +target bounds. + +In the minibuffer only the first target finder returning non-nil +is taken into account. When finding targets at point in other +buffers, all target finder functions are executed. + +For each target, the type is then looked up as a key in the +variable `embark-transformer-alist'. If there is a transformer +for the type, it is called with the type and target, and must +return a `cons' of the transformed type and transformed target. + +The return value of `embark--targets' is a list of plists. Each +plist concerns one target, and has keys `:type', `:target', +`:orig-type', `:orig-target' and `:bounds'." + (let (targets) + (run-hook-wrapped + 'embark-target-finders + (lambda (fun) + (dolist (found (when-let (result (funcall fun)) + (if (consp (car result)) result (list result)))) + (let* ((type (or (car found) 'general)) + (target+bounds (cdr found)) + (target (if (consp target+bounds) + (car target+bounds) + target+bounds)) + (bounds (and (consp target+bounds) (cdr target+bounds))) + (full-target + (append + (list :orig-type type :orig-target target :bounds bounds) + (if-let (transform (alist-get type embark-transformer-alist)) + (let ((trans (funcall transform type target))) + (list :type (car trans) :target (cdr trans))) + (list :type type :target target))))) + (push full-target targets))) + (and targets (minibufferp)))) + (nreverse + (cl-delete-duplicates ; keeps last duplicate, but we reverse + targets + :test (lambda (t1 t2) + (and (equal (plist-get t1 :target) (plist-get t2 :target)) + (eq (plist-get t1 :type) (plist-get t2 :type)))))))) + +(defun embark--default-action (type) + "Return default action for the given TYPE of target. +The most common case is that the target comes from minibuffer +completion, in which case the default action is the command that +opened the minibuffer in the first place. This can be overridden +by `embark-default-action-overrides'. + +For targets that do not come from minibuffer completion +\(typically some thing at point in a regular buffer) and whose +type is not listed in `embark-default-action-overrides', the +default action is given by whatever binding RET has in the action +keymap for the given type." + (or (alist-get (cons type embark--command) embark-default-action-overrides + nil nil #'equal) + (alist-get type embark-default-action-overrides) + (alist-get t embark-default-action-overrides) + embark--command + (lookup-key (embark--raw-action-keymap type) "\r"))) + +(defun embark--rotate (list k) + "Rotate LIST by K elements and return the rotated list." + (setq k (mod k (length list))) + (append (seq-drop list k) (seq-take list k))) + +(defun embark--orig-target (target) + "Convert TARGET to original target." + (plist-put + (plist-put + (copy-sequence target) + :target (plist-get target :orig-target)) + :type (plist-get target :orig-type))) + +(defun embark--quit-p (action) + "Determine whether to quit the minibuffer after ACTION. +This function consults `embark-quit-after-action' to decide +whether or not the user wishes to quit the minibuffer after +performing the ACTION, assuming this is done from a minibuffer." + (let* ((cfg embark-quit-after-action) + (quit (if (consp cfg) (alist-get action cfg (alist-get t cfg)) cfg))) + (when embark--toggle-quit (setq quit (not quit))) + (setq embark--toggle-quit nil) + quit)) + +;;;###autoload +(defun embark-act (&optional arg) + "Prompt the user for an action and perform it. +The targets of the action are chosen by `embark-target-finders'. +By default, if called from a minibuffer the target is the top +completion candidate. When called from a non-minibuffer buffer +there can multiple targets and you can cycle among them by using +`embark-cycle' (which is bound by default to the same key +binding `embark-act' is, but see `embark-cycle-key'). + +This command uses `embark-prompter' to ask the user to specify an +action, and calls it injecting the target at the first minibuffer +prompt. + +If you call this from the minibuffer, it can optionally quit the +minibuffer. The variable `embark-quit-after-action' controls +whether calling `embark-act' with nil ARG quits the minibuffer, +and if ARG is non-nil it will do the opposite. Interactively, +ARG is the prefix argument. + +If instead you call this from outside the minibuffer, the first +ARG targets are skipped over (if ARG is negative the skipping is +done by cycling backwards) and cycling starts from the following +target." + (interactive "P") + (let* ((targets (or (embark--targets) (user-error "No target found"))) + (indicators (mapcar #'funcall embark-indicators)) + (default-done nil)) + (when arg + (if (minibufferp) + (embark-toggle-quit) + (setq targets (embark--rotate targets (prefix-numeric-value arg))))) + (unwind-protect + (while + (let* ((target (car targets)) + (action + (or (embark--prompt + indicators + (let ((embark-default-action-overrides + (if default-done + `((t . ,default-done)) + embark-default-action-overrides))) + (embark--action-keymap (plist-get target :type) + (cdr targets))) + targets) + (user-error "Canceled"))) + (default-action (or default-done + (embark--default-action + (plist-get target :type))))) + (cond + ;; When acting twice in the minibuffer, do not restart + ;; `embark-act'. Otherwise the next `embark-act' will + ;; find a target in the original buffer. + ((eq action #'embark-act) + (message "Press an action key")) + ((eq action #'embark-cycle) + (setq targets (embark--rotate + targets (prefix-numeric-value prefix-arg)))) + (t + ;; if the action is non-repeatable, cleanup indicator now + (let ((repeat (embark--action-repeatable-p action))) + (unless repeat (mapc #'funcall indicators)) + (condition-case err + (embark--act + action + (if (and (eq action default-action) + (eq action embark--command) + (not (memq action embark-multitarget-actions))) + (embark--orig-target target) + target) + (embark--quit-p action)) + (user-error + (funcall (if repeat #'message #'user-error) + "%s" (cadr err)))) + (when-let (new-targets (and repeat (embark--targets))) + ;; Terminate repeated prompter on default action, + ;; when repeating. Jump to the region type if the + ;; region is active after the action, or else to the + ;; current type again. + (setq default-done #'embark-done + targets + (embark--rotate + new-targets + (or (cl-position-if + (let ((desired-type + (if (eq repeat t) + (plist-get (car targets) :type) + repeat))) + (lambda (x) + (eq (plist-get x :type) desired-type))) + new-targets) + 0))))))))) + (mapc #'funcall indicators)))) + +(defun embark--maybe-transform-candidates () + "Collect candidates and see if they all transform to the same type. +Return a plist with keys `:type', `:orig-type', `:candidates', and +`:orig-candidates'." + (pcase-let* ((`(,type . ,candidates) + (run-hook-with-args-until-success 'embark-candidate-collectors)) + (bounds (mapcar #'cdr-safe candidates))) + (setq candidates + (mapcar (lambda (x) (if (consp x) (car x) x)) candidates)) + (when (eq type 'file) + (let ((dir (embark--default-directory))) + (setq candidates + (mapcar (lambda (cand) + (abbreviate-file-name + (expand-file-name (substitute-in-file-name cand) dir))) + candidates)))) + ;; TODO more systematic approach to applying substitute-in-file-name + (append + (list :orig-type type :orig-candidates candidates :bounds bounds) + (or (when candidates + (when-let ((transformer (alist-get type embark-transformer-alist))) + (pcase-let* ((`(,new-type . ,first-cand) + (funcall transformer type (car candidates)))) + (let ((new-candidates (list first-cand))) + (when (cl-every + (lambda (cand) + (pcase-let ((`(,t-type . ,t-cand) + (funcall transformer type cand))) + (when (eq t-type new-type) + (push t-cand new-candidates) + t))) + (cdr candidates)) + (list :type new-type + :candidates (nreverse new-candidates))))))) + (list :type type :candidates candidates))))) + +;;;###autoload +(defun embark-act-all (&optional arg) + "Prompt the user for an action and perform it on each candidate. +The candidates are chosen by `embark-candidate-collectors'. By +default, if `embark-select' has been used to select some +candidates, then `embark-act-all' will act on those candidates; +otherwise, if the selection is empty and `embark-act-all' is +called from a minibuffer, then the candidates are the completion +candidates. + +This command uses `embark-prompter' to ask the user to specify an +action, and calls it injecting the target at the first minibuffer +prompt. + +If you call this from the minibuffer, it can optionally quit the +minibuffer. The variable `embark-quit-after-action' controls +whether calling `embark-act' with nil ARG quits the minibuffer, +and if ARG is non-nil it will do the opposite. Interactively, +ARG is the prefix argument." + (interactive "P") + (let* ((transformed (embark--maybe-transform-candidates)) + (type (plist-get transformed :type)) + (orig-type (plist-get transformed :orig-type)) + (candidates + (or (cl-mapcar + (lambda (cand orig-cand bounds) + (list :type type :target cand + :bounds (when bounds + (cons (copy-marker (car bounds)) + (copy-marker (cdr bounds)))) + :orig-type orig-type :orig-target orig-cand)) + (plist-get transformed :candidates) + (plist-get transformed :orig-candidates) + (plist-get transformed :bounds)) + (user-error "No candidates to act on"))) + (indicators (mapcar #'funcall embark-indicators))) + (when arg (embark-toggle-quit)) + (unwind-protect + (let* ((action + (or (embark--prompt + indicators (embark--action-keymap type nil) + (list (list :type type :multi (length candidates)))) + (user-error "Canceled"))) + (prefix prefix-arg) + (act (lambda (candidate) + (cl-letf (((symbol-function 'embark--restart) #'ignore) + ((symbol-function 'embark--confirm) #'ignore)) + (let ((prefix-arg prefix)) + (when-let ((bounds (plist-get candidate :bounds))) + (goto-char (car bounds))) + (embark--act action candidate))))) + (quit (embark--quit-p action))) + (when (and (eq action (embark--default-action type)) + (eq action embark--command)) + (setq candidates (mapcar #'embark--orig-target candidates))) + (when (or (not (or embark-confirm-act-all + (memq 'embark--confirm + (alist-get action embark-pre-action-hooks)))) + (y-or-n-p (format "Run %s on %d %ss? " + action (length candidates) type))) + (if (memq action embark-multitarget-actions) + (let ((prefix-arg prefix)) + (embark--act action transformed quit)) + (save-excursion + (if quit + (embark--quit-and-run #'mapc act candidates) + (mapc act candidates)))) + (when (and (not quit) + (memq 'embark--restart + (alist-get action embark-post-action-hooks))) + (embark--restart)))) + (dolist (cand candidates) + (when-let ((bounds (plist-get cand :bounds))) + (set-marker (car bounds) nil) ; yay, manual memory management! + (set-marker (cdr bounds) nil))) + (setq prefix-arg nil) + (mapc #'funcall indicators)))) + +(defun embark-highlight-indicator () + "Action indicator highlighting the target at point." + (let (overlay) + (lambda (&optional keymap targets _prefix) + (let ((bounds (plist-get (car targets) :bounds))) + (when (and overlay (or (not keymap) (not bounds))) + (delete-overlay overlay) + (setq overlay nil)) + (when bounds + (if overlay + (move-overlay overlay (car bounds) (cdr bounds)) + (setq overlay (make-overlay (car bounds) (cdr bounds))) + (overlay-put overlay 'category 'embark-target-overlay)) + (overlay-put overlay 'window (selected-window))))))) + +(defun embark-isearch-highlight-indicator () + "Action indicator highlighting all occurrences of the identifier at point. +This indicator only does something for targets which are +identifiers or symbols. For those it uses `isearch''s lazy +highlighting feature to highlight all occurrences of the target in +the buffer. This indicator is best used in conjunction with +`embark-highlight-indicator': by using them both you get the +target and the other occurrences of it highlighted in different +colors." + (lambda (&optional _keymap targets _prefix) + (if (and (not (minibufferp)) + (memq (plist-get (car targets) :orig-type) '(symbol identifier))) + (let ((isearch-string (plist-get (car targets) :target)) + (isearch-regexp-function #'isearch-symbol-regexp)) + (isearch-lazy-highlight-new-loop)) + (setq isearch-lazy-highlight-last-string nil) + (lazy-highlight-cleanup t)))) + +(defun embark-cycle (_arg) + "Cycle over the next ARG targets at point. +If ARG is negative, cycle backwards." + (interactive "p") + (user-error "Not meant to be called directly")) + +(defun embark-done () + "Terminate sequence of repeated actions." + (interactive)) + +;;;###autoload +(defun embark-dwim (&optional arg) + "Run the default action on the current target. +The target of the action is chosen by `embark-target-finders'. + +If the target comes from minibuffer completion, then the default +action is the command that opened the minibuffer in the first +place, unless overridden by `embark-default-action-overrides'. + +For targets that do not come from minibuffer completion +\(typically some thing at point in a regular buffer) and whose +type is not listed in `embark-default-action-overrides', the +default action is given by whatever binding RET has in the action +keymap for the target's type. + +See `embark-act' for the meaning of the prefix ARG." + (interactive "P") + (if-let ((targets (embark--targets))) + (let* ((target + (or (nth + (if (or (null arg) (minibufferp)) + 0 + (mod (prefix-numeric-value arg) (length targets))) + targets))) + (type (plist-get target :type)) + (default-action (embark--default-action type)) + (action (or (command-remapping default-action) default-action))) + (unless action + (user-error "No default action for %s targets" type)) + (when (and arg (minibufferp)) (setq embark--toggle-quit t)) + (embark--act action + (if (and (eq default-action embark--command) + (not (memq default-action + embark-multitarget-actions))) + (embark--orig-target target) + target) + (embark--quit-p action))) + (user-error "No target found"))) + +(defun embark--become-keymap () + "Return keymap of commands to become for current command." + (let ((map (make-composed-keymap + (cl-loop for keymap-name in embark-become-keymaps + for keymap = (symbol-value keymap-name) + when (where-is-internal embark--command (list keymap)) + collect keymap)))) + (when embark-help-key + (keymap-set map embark-help-key #'embark-keymap-help)) + map)) + +;;;###autoload +(defun embark-become (&optional full) + "Make current command become a different command. +Take the current minibuffer input as initial input for new +command. The new command can be run normally using key bindings or +\\[execute-extended-command], but if the current command is found in a keymap in +`embark-become-keymaps', that keymap is activated to provide +convenient access to the other commands in it. + +If FULL is non-nil (interactively, if called with a prefix +argument), the entire minibuffer contents are used as the initial +input of the new command. By default only the part of the +minibuffer contents between the current completion boundaries is +taken. What this means is fairly technical, but (1) usually +there is no difference: the completion boundaries include the +entire minibuffer contents, and (2) the most common case where +these notions differ is file completion, in which case the +completion boundaries single out the path component containing +point." + (interactive "P") + (unless (minibufferp) + (user-error "Not in a minibuffer")) + (let* ((target (embark--display-string ; remove invisible portions + (if full + (minibuffer-contents) + (pcase-let ((`(,beg . ,end) (embark--boundaries))) + (substring (minibuffer-contents) beg + (+ end (embark--minibuffer-point))))))) + (keymap (embark--become-keymap)) + (targets `((:type embark-become :target ,target))) + (indicators (mapcar #'funcall embark-indicators)) + (become (unwind-protect + (embark--prompt indicators keymap targets) + (mapc #'funcall indicators)))) + (unless become + (user-error "Canceled")) + (embark--become-command become target))) + +(defun embark--become-command (command input) + "Quit current minibuffer and start COMMAND with INPUT." + (embark--quit-and-run + (lambda () + (minibuffer-with-setup-hook + (lambda () + (delete-minibuffer-contents) + (insert input)) + (let ((use-dialog-box nil) ;; avoid mouse dialogs + (last-nonmenu-event 13)) + (setq this-command command) + (command-execute command)))))) + +;;; Embark collect + +(defgroup embark-collect nil + "Buffers for acting on collected Embark targets." + :group 'embark) + +(defcustom embark-candidate-collectors + '(embark-selected-candidates + embark-minibuffer-candidates + embark-completion-list-candidates + embark-dired-candidates + embark-ibuffer-candidates + embark-embark-collect-candidates + embark-custom-candidates) + "List of functions that collect all candidates in a given context. +These are used to fill an Embark Collect buffer. Each function +should return either nil (to indicate it found no candidates) or +a list whose first element is a symbol indicating the type of +candidates and whose `cdr' is the list of candidates, each of +which should be either a string or a dotted list of the +form (TARGET START . END), where START and END are the buffer +positions bounding the TARGET string." + :type 'hook) + +(defcustom embark-exporters-alist + '((buffer . embark-export-ibuffer) + (file . embark-export-dired) + (package . embark-export-list-packages) + (bookmark . embark-export-bookmarks) + (variable . embark-export-customize-variable) + (face . embark-export-customize-face) + (symbol . embark-export-apropos) + (minor-mode . embark-export-apropos) + (function . embark-export-apropos) + (command . embark-export-apropos) + (t . embark-collect)) + "Alist associating completion types to export functions. +Each function should take a list of strings which are candidates +for actions and make a buffer appropriate to manage them. For +example, the default is to make a Dired buffer for files, and an +ibuffer for buffers. + +The key t is also allowed in the alist, and the corresponding +value indicates the default function to use for other types. The +default is `embark-collect'" + :type '(alist :key-type symbol :value-type function)) + +(defcustom embark-after-export-hook nil + "Hook run after `embark-export' in the newly created buffer." + :type 'hook) + +(defface embark-collect-candidate '((t :inherit default)) + "Face for candidates in Embark Collect buffers.") + +(defface embark-collect-group-title + '((t :inherit shadow :slant italic)) + "Face for group titles in Embark Collect buffers.") + +(defface embark-collect-group-separator + '((t :inherit shadow :strike-through t italic)) + "Face for group titles in Embark Collect buffers.") + +(defcustom embark-collect-group-format + (concat + (propertize " " 'face 'embark-collect-group-separator) + (propertize " %s " 'face 'embark-collect-group-title) + (propertize " " 'face 'completions-group-separator + 'display '(space :align-to right))) + "Format string used for the group title in Embark Collect buffers." + :type 'string) + +(defface embark-collect-annotation '((t :inherit completions-annotations)) + "Face for annotations in Embark Collect. +This is only used for annotation that are not already fontified.") + +(defvar-local embark--rerun-function nil + "Function to rerun the collect or export that made the current buffer.") + +(autoload 'package-delete "package") +(declare-function package--from-builtin "package") +(declare-function package-desc-extras "package") +(declare-function package-desc-name "package") +(defvar package--builtins) +(defvar package-alist) +(defvar package-archive-contents) +(defvar package--initialized) + +(defun embark--package-desc (pkg) + "Return the description structure for package PKG." + (or ; found this in `describe-package-1' + (car (alist-get pkg package-alist)) + (if-let ((built-in (assq pkg package--builtins))) + (package--from-builtin built-in) + (car (alist-get pkg package-archive-contents))))) + +(defun embark-minibuffer-candidates () + "Return all current completion candidates from the minibuffer." + (when (minibufferp) + (let* ((all (completion-all-completions + (minibuffer-contents) + minibuffer-completion-table + minibuffer-completion-predicate + (embark--minibuffer-point))) + (last (last all))) + (when last (setcdr last nil)) + (cons + (completion-metadata-get (embark--metadata) 'category) + all)))) + +(defun embark-sorted-minibuffer-candidates () + "Return a sorted list of current minibuffer completion candidates. +This using the same sort order that `icomplete' and +`minibuffer-force-complete' use. The intended usage is that you +replace `embark-minibuffer-candidates' with this function in the +list `embark-candidate-collectors'." + (when (minibufferp) + (cons + (completion-metadata-get (embark--metadata) 'category) + (nconc (cl-copy-list (completion-all-sorted-completions)) nil)))) + +(declare-function dired-get-marked-files "dired") +(declare-function dired-move-to-filename "dired") +(declare-function dired-move-to-end-of-filename "dired") + +(defun embark-dired-candidates () + "Return marked or all files shown in Dired buffer. +If any buffer is marked, return marked buffers; otherwise, return +all buffers." + (when (derived-mode-p 'dired-mode) + (cons 'file + (or + ;; dired-get-marked-files returns the file on the current + ;; line if no marked files are found; and when the fourth + ;; argument is non-nil, the "no marked files" case is + ;; distinguished from the "single marked file" case by + ;; returning (list t marked-file) in the latter + (let ((marked (dired-get-marked-files t nil nil t))) + (and (cdr marked) + (if (eq (car marked) t) (cdr marked) marked))) + (save-excursion + (goto-char (point-min)) + (let (files) + (while (not (eobp)) + (when-let (file (dired-get-filename t t)) + (push `(,file + ,(progn (dired-move-to-filename) (point)) + . ,(progn (dired-move-to-end-of-filename t) (point))) + files)) + (forward-line)) + (nreverse files))))))) + +(autoload 'ibuffer-marked-buffer-names "ibuffer") +(declare-function ibuffer-map-lines-nomodify "ibuffer") + +(defun embark-ibuffer-candidates () + "Return marked or all buffers listed in ibuffer buffer. +If any buffer is marked, return marked buffers; otherwise, return +all buffers." + (when (derived-mode-p 'ibuffer-mode) + (cons 'buffer + (or (ibuffer-marked-buffer-names) + (let (buffers) + (ibuffer-map-lines-nomodify + (lambda (buffer _mark) + (push (buffer-name buffer) buffers))) + (nreverse buffers)))))) + +(defun embark-embark-collect-candidates () + "Return candidates in Embark Collect buffer. +This makes `embark-export' work in Embark Collect buffers." + (when (derived-mode-p 'embark-collect-mode) + (cons embark--type + (save-excursion + (goto-char (point-min)) + (let (all) + (when-let ((cand (embark-target-collect-candidate))) + (push (cdr cand) all)) + (while (forward-button 1 nil nil t) + (when-let ((cand (embark-target-collect-candidate))) + (push (cdr cand) all))) + (nreverse all)))))) + +(defun embark-completion-list-candidates () + "Return all candidates in a completions buffer." + (when (derived-mode-p 'completion-list-mode) + (cons + embark--type + (save-excursion + (goto-char (point-min)) + (next-completion 1) + (let (all) + (while (not (eobp)) + (push (cdr (embark-target-completion-list-candidate)) all) + (next-completion 1)) + (nreverse all)))))) + +(defun embark-custom-candidates () + "Return all variables and faces listed in this `Custom-mode' buffer." + (when (derived-mode-p 'Custom-mode) + (cons 'symbol ; gets refined to variable or face when acted upon + (save-excursion + (goto-char (point-min)) + (let (symbols) + (while (not (eobp)) + (when-let (widget (widget-at (point))) + (when (eq (car widget) 'custom-visibility) + (push + `(,(symbol-name + (plist-get (cdr (plist-get (cdr widget) :parent)) + :value)) + ,(point) + . ,(progn + (re-search-forward ":" (line-end-position) 'noerror) + (point))) + symbols))) + (forward-line)) + (nreverse symbols)))))) + + +(defun embark-collect--target () + "Return the Embark Collect candidate at point. +This takes into account `embark-transformer-alist'." + (let ((embark-target-finders '(embark-target-collect-candidate))) + (car (embark--targets)))) + +(defun embark--action-command (action) + "Turn an ACTION into a command to perform the action. +Returns the name of the command." + (let ((name (intern (format "embark-action--%s" + (embark--command-name action))))) + (fset name (lambda (arg) + (interactive "P") + (when-let (target (embark-collect--target)) + (let ((prefix-arg arg)) + (embark--act action target))))) + (when (fboundp action) + (put name 'function-documentation (documentation action))) + name)) + +(defun embark--all-bindings (keymap &optional nested) + "Return an alist of all bindings in KEYMAP. +If NESTED is non-nil subkeymaps are not flattened." + (let (bindings maps) + (map-keymap + (lambda (key def) + (cond + ((keymapp def) + (if nested + (push (cons (vector key) def) maps) + (dolist (bind (embark--all-bindings def)) + (push (cons (vconcat (vector key) (car bind)) (cdr bind)) + maps)))) + (def (push (cons (vector key) def) bindings)))) + (keymap-canonicalize keymap)) + (nconc (nreverse bindings) (nreverse maps)))) + +(defun embark-collect--direct-action-map (type) + "Return a direct action keymap for targets of given TYPE." + (let* ((actions (embark--action-keymap type nil)) + (map (make-sparse-keymap))) + (set-keymap-parent map button-map) + (pcase-dolist (`(,key . ,cmd) (embark--all-bindings actions)) + (unless (or (equal key [13]) + (memq cmd '(digit-argument negative-argument))) + (define-key map key (if (eq cmd 'embark-keymap-help) + #'embark-bindings-at-point + (embark--action-command cmd))))) + map)) + +(define-minor-mode embark-collect-direct-action-minor-mode + "Bind type-specific actions directly (without need for `embark-act')." + :init-value nil + :lighter " Act" + (unless (derived-mode-p 'embark-collect-mode) + (user-error "Not in an Embark Collect buffer")) + (save-excursion + (goto-char (point-min)) + (let ((inhibit-read-only t) maps) + (while (progn + (when (tabulated-list-get-id) + (put-text-property + (point) (button-end (point)) 'keymap + (if embark-collect-direct-action-minor-mode + (when-let ((target (embark-collect--target)) + (type (plist-get target :type))) + (or (alist-get type maps) + (setf (alist-get type maps) + (embark-collect--direct-action-map type))))))) + (forward-button 1 nil nil t)))))) + +(define-button-type 'embark-collect-entry + 'face 'embark-collect-candidate + 'action 'embark-collect-choose) + +(declare-function outline-toggle-children "outline") +(define-button-type 'embark-collect-group + 'face 'embark-collect-group-title + 'action (lambda (_) (outline-toggle-children))) + +(defun embark--boundaries () + "Get current minibuffer completion boundaries." + (let ((contents (minibuffer-contents)) + (pt (embark--minibuffer-point))) + (completion-boundaries + (substring contents 0 pt) + minibuffer-completion-table + minibuffer-completion-predicate + (substring contents pt)))) + +(defun embark-collect-choose (entry) + "Run default action on Embark Collect ENTRY." + (pcase-let ((`(,type ,text ,start . ,end) + (save-excursion + (goto-char entry) + (embark-target-collect-candidate)))) + (embark--act (embark--default-action type) + (list :target text + :type type + :bounds (cons start end))))) + +(defvar-keymap embark-collect-mode-map + :doc "Keymap for Embark collect mode." + :parent tabulated-list-mode-map + "a" #'embark-act + "A" #'embark-act-all + "M-a" #'embark-collect-direct-action-minor-mode + "E" #'embark-export + "s" #'isearch-forward + "n" #'forward-button + "p" #'backward-button + "}" 'outline-next-heading + "{" 'outline-previous-heading + " " 'outline-next-heading + " " 'outline-previous-heading + " " #'embark-rerun-collect-or-export) + +(defconst embark-collect--outline-string (string #x210000) + "Special string used for outline headings in Embark Collect buffers. +Chosen to be extremely unlikely to appear in a candidate.") + +(define-derived-mode embark-collect-mode tabulated-list-mode "Embark Collect" + "List of candidates to be acted on. +The command `embark-act' is bound `embark-collect-mode-map', but +you might prefer to change the key binding to match your other +key binding for it. Or alternatively you might want to enable the +embark collect direct action minor mode by adding the function +`embark-collect-direct-action-minor-mode' to +`embark-collect-mode-hook'. + +Reverting an Embark Collect buffer has slightly unusual behavior +if the buffer was obtained by running `embark-collect' from +within a minibuffer completion session. In that case reverting +just restarts the completion session, that is, the command that +opened the minibuffer is run again and the minibuffer contents +restored. You can then interact normally with the command, +perhaps editing the minibuffer contents, and, if you wish, you +can rerun `embark-collect' to get an updated buffer." + :interactive nil :abbrev-table nil :syntax-table nil) + +(defun embark-collect--metadatum (type metadatum) + "Get METADATUM for current buffer's candidates. +For non-minibuffers, assume candidates are of given TYPE." + (if (minibufferp) + (or (completion-metadata-get (embark--metadata) metadatum) + (plist-get completion-extra-properties + (intern (format ":%s" metadatum)))) + ;; otherwise fake some metadata for Marginalia users's benefit + (completion-metadata-get `((category . ,type)) metadatum))) + +(defun embark-collect--affixator (type) + "Get affixation function for current buffer's candidates. +For non-minibuffers, assume candidates are of given TYPE." + (or (embark-collect--metadatum type 'affixation-function) + (let ((annotator + (or (embark-collect--metadatum type 'annotation-function) + (lambda (_) "")))) + (lambda (candidates) + (mapcar (lambda (c) + (if-let (a (funcall annotator c)) (list c "" a) c)) + candidates))))) + +(defun embark--display-string (str) + ;; Note: Keep in sync with vertico--display-string + "Return display STR without display and invisible properties." + (let ((end (length str)) (pos 0) chunks) + (while (< pos end) + (let ((nextd (next-single-property-change pos 'display str end)) + (disp (get-text-property pos 'display str))) + (if (stringp disp) + (let ((face (get-text-property pos 'face str))) + (when face + (add-face-text-property + 0 (length disp) face t (setq disp (concat disp)))) + (setq pos nextd chunks (cons disp chunks))) + (while (< pos nextd) + (let ((nexti + (next-single-property-change pos 'invisible str nextd))) + (unless (or (get-text-property pos 'invisible str) + (and (= pos 0) (= nexti end))) ;; full=>no allocation + (push (substring str pos nexti) chunks)) + (setq pos nexti)))))) + (if chunks (apply #'concat (nreverse chunks)) str))) + +(defconst embark--hline + (propertize + (concat "\n" (propertize + " " 'display '(space :align-to right) + 'face '(:inherit completions-group-separator :height 0.01) + 'cursor-intangible t 'intangible t))) + "Horizontal line used to separate multiline collect entries.") + +(defun embark-collect--format-entries (candidates grouper) + "Format CANDIDATES for `tabulated-list-mode' grouped by GROUPER. +The GROUPER is either nil or a function like the `group-function' +completion metadatum, that is, a function of two arguments, the +first of which is a candidate and the second controls what is +computed: if nil, the title of the group the candidate belongs +to, and if non-nil, a rewriting of the candidate (useful to +simplify the candidate so it doesn't repeat the group title, for +example)." + (let ((max-width 0) + (transform + (if grouper (lambda (cand) (funcall grouper cand t)) #'identity))) + (setq + tabulated-list-entries + (mapcan + (lambda (group) + (let ((multiline (seq-some (lambda (x) (string-match-p "\n" (car x))) + candidates))) + (cons + `(nil [(,(concat (propertize embark-collect--outline-string + 'invisible t) + (format embark-collect-group-format (car group))) + type embark-collect-group) + ("" skip t)]) + (mapcar + (pcase-lambda (`(,cand ,prefix ,annotation)) + (let* ((display (embark--display-string (funcall transform cand))) + (length (length annotation)) + (faces (text-property-not-all + 0 length 'face nil annotation))) + (setq max-width (max max-width (+ (string-width prefix) + (string-width display)))) + (when faces + (add-face-text-property 0 length 'default t annotation)) + `(,cand + [(,(propertize + (if multiline (concat display embark--hline) display) + 'line-prefix prefix) + type embark-collect-entry) + (,annotation + skip t + ,@(unless faces + '(face embark-collect-annotation)))]))) + (cdr group))))) + (if grouper + (seq-group-by (lambda (item) (funcall grouper (car item) nil)) + candidates) + (list (cons "" candidates))))) + (if (null grouper) + (pop tabulated-list-entries) + (setq-local outline-regexp embark-collect--outline-string) + (outline-minor-mode)) + (setq tabulated-list-format + `[("Candidate" ,max-width t) ("Annotation" 0 t)]))) + +(defun embark-collect--update-candidates (buffer) + "Update candidates for Embark Collect BUFFER." + (let* ((transformed (embark--maybe-transform-candidates)) + (type (plist-get transformed :orig-type)) ; we need the originals for + (candidates (plist-get transformed :orig-candidates)) ; default action + (bounds (plist-get transformed :bounds)) + (affixator (embark-collect--affixator type)) + (grouper (embark-collect--metadatum type 'group-function))) + (when (eq type 'file) + (let ((dir (buffer-local-value 'default-directory buffer))) + (setq candidates + (mapcar (lambda (cand) + (let ((rel (file-relative-name cand dir))) + (if (string-prefix-p "../" rel) cand rel))) + candidates)))) + (if (seq-some #'identity bounds) + (cl-loop for cand in candidates and (start . _end) in bounds + when start + do (add-text-properties + 0 1 `(embark--location ,(copy-marker start)) cand))) + (setq candidates (funcall affixator candidates)) + (with-current-buffer buffer + (setq embark--type type) + (unless embark--command + (setq embark--command #'embark--goto)) + (embark-collect--format-entries candidates grouper)) + candidates)) + +(defun embark--goto (target) + "Jump to the original location of TARGET. +This function is used as a default action in Embark Collect +buffers when the candidates were a selection from a regular +buffer." + ;; TODO: ensure the location jumped to is visible + ;; TODO: remove duplication with embark-org-goto-heading + (when-let ((marker (get-text-property 0 'embark--location target))) + (pop-to-buffer (marker-buffer marker)) + (widen) + (goto-char marker) + (pulse-momentary-highlight-one-line))) + +(defun embark--collect (buffer-name) + "Create an Embark Collect buffer named BUFFER-NAME. + +The function `generate-new-buffer-name' is used to ensure the +buffer has a unique name." + (let ((buffer (generate-new-buffer buffer-name)) + (rerun (embark--rerun-function #'embark-collect))) + (with-current-buffer buffer + ;; we'll run the mode hooks once the buffer is displayed, so + ;; the hooks can make use of the window + (delay-mode-hooks (embark-collect-mode))) + + (embark--cache-info buffer) + (unless (embark-collect--update-candidates buffer) + (user-error "No candidates to collect")) + + (with-current-buffer buffer + (setq tabulated-list-use-header-line nil ; default to no header + header-line-format nil + tabulated-list--header-string nil) + (setq embark--rerun-function rerun)) + + (let ((window (display-buffer buffer))) + (with-selected-window window + (run-mode-hooks) + (tabulated-list-revert)) + (set-window-dedicated-p window t) + buffer))) + +(defun embark--descriptive-buffer-name (type) + "Return a descriptive name for an Embark collect or export buffer. +TYPE should be either `collect' or `export'." + (format "*Embark %s: %s*" + (capitalize (symbol-name type)) + (if (minibufferp) + (format "%s - %s" embark--command + (minibuffer-contents-no-properties)) + (buffer-name)))) + +;;;###autoload +(defun embark-collect () + "Create an Embark Collect buffer. + +To control the display, add an entry to `display-buffer-alist' +with key \"Embark Collect\". + +In Embark Collect buffers `revert-buffer' is remapped to +`embark-rerun-collect-or-export', which has slightly unusual +behavior if the buffer was obtained by running `embark-collect' +from within a minibuffer completion session. In that case +rerunning just restarts the completion session, that is, the +command that opened the minibuffer is run again and the +minibuffer contents restored. You can then interact normally with +the command, perhaps editing the minibuffer contents, and, if you +wish, you can rerun `embark-collect' to get an updated buffer." + (interactive) + (let ((buffer (embark--collect (embark--descriptive-buffer-name 'collect)))) + (when (minibufferp) + (embark--run-after-command #'pop-to-buffer buffer) + (embark--quit-and-run #'message nil)))) + +;;;###autoload +(defun embark-live () + "Create a live-updating Embark Collect buffer. + +To control the display, add an entry to `display-buffer-alist' +with key \"Embark Live\"." + (interactive) + (let ((live-buffer (embark--collect + (format "*Embark Live: %s*" + (if (minibufferp) + (format "M-x %s" embark--command) + (buffer-name))))) + (run-collect (make-symbol "run-collect")) + (stop-collect (make-symbol "stop-collect")) + timer) + (setf (symbol-function stop-collect) + (lambda () + (remove-hook 'change-major-mode-hook stop-collect t) + (remove-hook 'after-change-functions run-collect t))) + (setf (symbol-function run-collect) + (lambda (_1 _2 _3) + (unless timer + (setq timer + (run-with-idle-timer + 0.05 nil + (lambda () + (if (not (buffer-live-p live-buffer)) + (funcall stop-collect) + (embark-collect--update-candidates live-buffer) + (with-current-buffer live-buffer + ;; TODO figure out why I can't restore point + (tabulated-list-print t t)) + (setq timer nil)))))))) + (add-hook 'after-change-functions run-collect nil t) + (when (minibufferp) + (add-hook 'change-major-mode-hook stop-collect nil t)))) + +(defun embark--rerun-function (kind) + "Return a rerun function for an export or collect buffer in this context. +The parameter KIND should be either `embark-export' or `embark-collect'." + (let ((buffer (or embark--target-buffer (embark--target-buffer))) + (command embark--command)) + (cl-flet ((rerunner (action) + (lambda (&rest _) + (quit-window 'kill-buffer) + (with-current-buffer + (if (buffer-live-p buffer) buffer (current-buffer)) + (let ((embark--command command)) + (funcall action)))))) + (if (minibufferp) + (rerunner + (let ((input (minibuffer-contents-no-properties))) + (lambda () + (minibuffer-with-setup-hook + (lambda () + (delete-minibuffer-contents) + (insert input)) + (setq this-command embark--command) + (command-execute embark--command))))) + (rerunner kind))))) + +(defun embark-rerun-collect-or-export () + "Rerun the `embark-collect' or `embark-export' that created this buffer." + (interactive) + (if embark--rerun-function + (funcall embark--rerun-function) + (user-error "No function to rerun collect or export found"))) + +;;;###autoload +(defun embark-export () + "Create a type-specific buffer to manage current candidates. +The variable `embark-exporters-alist' controls how to make the +buffer for each type of completion. + +In Embark Export buffers `revert-buffer' is remapped to +`embark-rerun-collect-or-export', which has slightly unusual +behavior if the buffer was obtained by running `embark-export' +from within a minibuffer completion session. In that case +reverting just restarts the completion session, that is, the +command that opened the minibuffer is run again and the +minibuffer contents restored. You can then interact normally +with the command, perhaps editing the minibuffer contents, and, +if you wish, you can rerun `embark-export' to get an updated +buffer." + (interactive) + (let* ((transformed (embark--maybe-transform-candidates)) + (candidates (or (plist-get transformed :candidates) + (user-error "No candidates for export"))) + (type (plist-get transformed :type))) + (let ((exporter (or (alist-get type embark-exporters-alist) + (alist-get t embark-exporters-alist)))) + (if (eq exporter 'embark-collect) + (embark-collect) + (let* ((after embark-after-export-hook) + (cmd embark--command) + (name (embark--descriptive-buffer-name 'export)) + (rerun (embark--rerun-function #'embark-export)) + (buffer (save-excursion + (funcall exporter candidates) + (rename-buffer name t) + (current-buffer)))) + (embark--quit-and-run + (lambda () + (pop-to-buffer buffer) + (setq embark--rerun-function rerun) + (use-local-map + (make-composed-keymap + '(keymap + (remap keymap + (revert-buffer . embark-rerun-collect-or-export))) + (current-local-map))) + (let ((embark-after-export-hook after) + (embark--command cmd)) + (run-hooks 'embark-after-export-hook))))))))) + +(defmacro embark--export-rename (buffer title &rest body) + "Run BODY and rename BUFFER to Embark export buffer with TITLE." + (declare (indent 2)) + (let ((saved (make-symbol "saved"))) + `(let ((,saved (embark-rename-buffer + ,buffer " *Embark Saved*" t))) + ,@body + (set-buffer (embark-rename-buffer + ,buffer ,(format "*Embark Export %s*" title) t)) + (when ,saved (embark-rename-buffer ,saved ,buffer))))) + +(defun embark--export-customize (items type pred) + "Create a customization buffer listing ITEMS. +TYPE is the items type. +PRED is a predicate function used to filter the items." + (custom-buffer-create + (cl-loop for item in items + for sym = (intern-soft item) + when (and sym (funcall pred sym)) collect `(,sym ,type)))) + +(autoload 'apropos-parse-pattern "apropos") +(autoload 'apropos-symbols-internal "apropos") +(defun embark-export-apropos (symbols) + "Create apropos buffer listing SYMBOLS." + (embark--export-rename "*Apropos*" "Apropos" + (apropos-parse-pattern "") ;; Initialize apropos pattern + ;; HACK: Ensure that order of exported symbols is kept. + (cl-letf (((symbol-function #'sort) (lambda (list _pred) (nreverse list)))) + (apropos-symbols-internal + (delq nil (mapcar #'intern-soft symbols)) + (bound-and-true-p apropos-do-all))))) + +(defun embark-export-customize-face (faces) + "Create a customization buffer listing FACES." + (embark--export-customize faces 'custom-face #'facep)) + +(defun embark-export-customize-variable (variables) + "Create a customization buffer listing VARIABLES." + ;; The widget library serializes/deserializes the values. We advise + ;; the serialization in order to avoid errors for nonserializable + ;; variables. + (cl-letf* ((ht (make-hash-table :test #'equal)) + (orig-read (symbol-function #'read)) + (orig-write (symbol-function 'widget-sexp-value-to-internal)) + ((symbol-function #'read) + (lambda (&optional str) + (condition-case nil + (funcall orig-read str) + (error (gethash str ht))))) + ((symbol-function 'widget-sexp-value-to-internal) + (lambda (widget val) + (let ((str (funcall orig-write widget val))) + (puthash str val ht) + str)))) + (embark--export-customize variables 'custom-variable #'boundp))) + +(defun embark-export-ibuffer (buffers) + "Create an ibuffer buffer listing BUFFERS." + (ibuffer t "*Embark Export Ibuffer*" + `((predicate . (member (buffer-name) ',buffers))))) + +(autoload 'dired-check-switches "dired") +(declare-function dired-unadvertise "dired") +(defvar dired-directory) + +(defun embark-export-dired (files) + "Create a Dired buffer listing FILES." + (setq files (mapcar #'directory-file-name + (cl-remove-if-not #'file-exists-p files))) + (when (dired-check-switches dired-listing-switches "A" "almost-all") + (setq files (cl-remove-if + (lambda (path) + (let ((file (file-name-nondirectory path))) + (or (string= file ".") (string= file "..")))) + files))) + (cl-letf* ((dir (or (file-name-directory (try-completion "" files)) "")) + ;; Prevent reusing existing Dired buffer. + ((symbol-function 'dired-find-buffer-nocreate) #'ignore) + (buf (dired-noselect + (cons (expand-file-name dir) + (mapcar (lambda (file) (string-remove-prefix dir file)) + files))))) + (with-current-buffer buf + ;; Unadvertise to prevent the new buffer from being reused. + (dired-unadvertise default-directory) + (rename-buffer (format "*Embark Export Dired %s*" default-directory))) + (pop-to-buffer buf))) + +(autoload 'package-menu-mode "package") +(autoload 'package-menu--generate "package") + +(defun embark-export-list-packages (packages) + "Create a package menu mode buffer listing PACKAGES." + (let ((buf (generate-new-buffer "*Embark Export Packages*"))) + (with-current-buffer buf + (package-menu-mode) + (package-menu--generate nil (mapcar #'intern packages))) + (pop-to-buffer buf))) + +(defvar bookmark-alist) + +(defun embark-export-bookmarks (bookmarks) + "Create a `bookmark-bmenu-mode' buffer listing BOOKMARKS." + (embark--export-rename "*Bookmark List*" "Bookmarks" + (let ((bookmark-alist + (cl-remove-if-not + (lambda (bmark) + (member (car bmark) bookmarks)) + bookmark-alist))) + (bookmark-bmenu-list)))) + +;;; Multiple target selection + +(defface embark-selected '((t (:inherit match))) + "Face for selected candidates.") + +(defcustom embark-selection-indicator + #(" Embark:%s " 1 12 (face (embark-selected bold))) + "Mode line indicator used for selected candidates." + :type '(choice string (const nil))) + +(defvar-local embark--selection nil + "Buffer local list of selected targets. +Add or remove elements to this list using the `embark-select' +action.") + +(defun embark--selection-indicator () + "Mode line indicator showing number of selected items." + (when-let ((sel + (buffer-local-value + 'embark--selection + (or (when-let ((win (active-minibuffer-window))) + (window-buffer win)) + (current-buffer))))) + (format embark-selection-indicator (length sel)))) + +(cl-defun embark--select + (&key orig-target orig-type bounds &allow-other-keys) + "Add or remove ORIG-TARGET of given ORIG-TYPE to the selection. +If BOUNDS are given, also highlight the target when selecting it." + (cl-flet ((multi-type (x) (car (get-text-property 0 'multi-category x)))) + (if-let* ((existing (seq-find + (pcase-lambda (`(,cand . ,ov)) + (and + (equal cand orig-target) + (if (and bounds ov) + (and (= (car bounds) (overlay-start ov)) + (= (cdr bounds) (overlay-end ov))) + (let ((cand-type (multi-type cand))) + (or (eq cand-type orig-type) + (eq cand-type (multi-type orig-target))))))) + embark--selection))) + (progn + (when (cdr existing) (delete-overlay (cdr existing))) + (setq embark--selection (delq existing embark--selection))) + (let ((target (copy-sequence orig-target)) overlay) + (when bounds + (setq overlay (make-overlay (car bounds) (cdr bounds))) + (overlay-put overlay 'category 'embark-selected-overlay)) + (add-text-properties 0 (length orig-target) + `(multi-category ,(cons orig-type orig-target)) + target) + (push (cons target overlay) embark--selection)))) + (when embark-selection-indicator + (add-to-list 'mode-line-misc-info '(:eval (embark--selection-indicator))) + (force-mode-line-update t))) + +;;;###autoload +(defun embark-select () + "Add or remove the target from the current buffer's selection. +You can act on all selected targets at once with `embark-act-all'. +When called from outside `embark-act' this command will select +the first target at point." + (interactive) + (if-let ((target (car (embark--targets)))) + (apply #'embark--select target) + (user-error "No target to select"))) + +(defun embark-selected-candidates () + "Return currently selected candidates in the buffer." + (when embark--selection + (cl-flet ((unwrap (x) (get-text-property 0 'multi-category x))) + (let* ((first-type (car (unwrap (caar embark--selection)))) + (same (cl-every (lambda (item) + (eq (car (unwrap (car item))) first-type)) + embark--selection)) + (extract (if same + (pcase-lambda (`(,cand . ,overlay)) + (cons (cdr (unwrap cand)) overlay)) + #'identity))) + (cons + (if same first-type 'multi-category) + (nreverse + (mapcar + (lambda (item) + (pcase-let ((`(,cand . ,ov) (funcall extract item))) + (if ov `(,cand ,(overlay-start ov) . ,(overlay-end ov)) cand))) + embark--selection))))))) + +;;; Integration with external packages, mostly completion UIs + +;; marginalia + +;; Ensure that the Marginalia cache is reset, such that +;; `embark-toggle-variable-value' updates the display (See #540). +(with-eval-after-load 'marginalia + (push 'marginalia--cache-reset (alist-get :always embark-post-action-hooks))) + +;; vertico + +(declare-function vertico--candidate "ext:vertico") +(declare-function vertico--update "ext:vertico") +(defvar vertico--input) +(defvar vertico--candidates) +(defvar vertico--base) + +(defun embark--vertico-selected () + "Target the currently selected item in Vertico. +Return the category metadatum as the type of the target." + (when vertico--input + ;; Force candidate computation, if candidates are not yet available. + (vertico--update) + (cons (completion-metadata-get (embark--metadata) 'category) + (vertico--candidate)))) + +(defun embark--vertico-candidates () + "Collect the current Vertico candidates. +Return the category metadatum as the type of the candidates." + (when vertico--input + ;; Force candidate computation, if candidates are not yet available. + (vertico--update) + (cons (completion-metadata-get (embark--metadata) 'category) + vertico--candidates))) + +(defun embark--vertico-indicator () + "Embark indicator highlighting the current Vertico candidate." + (let ((fr face-remapping-alist)) + (lambda (&optional keymap _targets _prefix) + (when vertico--input + (setq-local face-remapping-alist + (if keymap + (cons '(vertico-current . embark-target) fr) + fr)))))) + +(with-eval-after-load 'vertico + (cl-defmethod vertico--format-candidate + :around (cand prefix suffix index start &context (embark--selection cons)) + (when (cl-find (concat vertico--base (nth index vertico--candidates)) + embark--selection + :test #'equal :key #'car) + (setq cand (copy-sequence cand)) + (add-face-text-property 0 (length cand) 'embark-selected t cand)) + (cl-call-next-method cand prefix suffix index start)) + (add-hook 'embark-indicators #'embark--vertico-indicator) + (add-hook 'embark-target-finders #'embark--vertico-selected) + (add-hook 'embark-candidate-collectors #'embark--vertico-candidates) + (remove-hook 'embark-candidate-collectors #'embark-selected-candidates) + (add-hook 'embark-candidate-collectors #'embark-selected-candidates)) + +;; ivy + +(declare-function ivy--expand-file-name "ext:ivy") +(declare-function ivy-state-current "ext:ivy") +(defvar ivy-text) +(defvar ivy-last) +(defvar ivy--old-cands) ; this stores the current candidates :) +(defvar ivy--length) + +(defun embark--ivy-selected () + "Target the currently selected item in Ivy. +Return the category metadatum as the type of the target." + ;; my favorite way of detecting Ivy + (when (memq 'ivy--queue-exhibit post-command-hook) + (cons + (completion-metadata-get (embark--metadata) 'category) + (ivy--expand-file-name + (if (and (> ivy--length 0) + (stringp (ivy-state-current ivy-last))) + (ivy-state-current ivy-last) + ivy-text))))) + +(defun embark--ivy-candidates () + "Return all current Ivy candidates." + ;; my favorite way of detecting Ivy + (when (memq 'ivy--queue-exhibit post-command-hook) + (cons + ;; swiper-isearch uses swiper-isearch-function as a completion + ;; table, but it doesn't understand metadata queries + (ignore-errors + (completion-metadata-get (embark--metadata) 'category)) + ivy--old-cands))) + +(with-eval-after-load 'ivy + (add-hook 'embark-target-finders #'embark--ivy-selected) + (add-hook 'embark-candidate-collectors #'embark--ivy-candidates) + (remove-hook 'embark-candidate-collectors #'embark-selected-candidates) + (add-hook 'embark-candidate-collectors #'embark-selected-candidates)) + +;;; Custom actions + +(defvar embark-separator-history nil + "Input history for the separators used by some embark commands. +The commands that prompt for a string separator are +`embark-insert' and `embark-copy-as-kill'.") + +(defun embark-keymap-help () + "Prompt for an action to perform or command to become and run it." + (interactive) + (user-error "Not meant to be called directly")) + +(defun embark-toggle-quit () + "Toggle whether the following action quits the minibuffer." + (interactive) + (when (minibufferp) + (setq embark--toggle-quit (not embark--toggle-quit)) + (if (consp embark-quit-after-action) + (message "Will %sobey embark-quit-after-action." + (if embark--toggle-quit "dis" "")) + (message + "Will %squit minibuffer after action" + (if (eq embark--toggle-quit embark-quit-after-action) "not " ""))))) + +(defun embark--separator (strings) + "Return a separator to join the STRINGS together. +With a prefix argument, prompt the user (unless STRINGS has 0 or +1 elements, in which case a separator is not needed)." + (if (and current-prefix-arg (cdr strings)) + (read-string "Separator: " nil 'embark-separator-history) + "\n")) + +(defun embark-copy-as-kill (strings) + "Join STRINGS and save on the `kill-ring'. +With a prefix argument, prompt for the separator to join the +STRINGS, which defaults to a newline." + (kill-new (string-join strings (embark--separator strings)))) + +(defun embark-insert (strings) + "Join STRINGS and insert the result at point. +With a prefix argument, prompt for the separator to join the +STRINGS, which defaults to a newline. + +Some whitespace is also inserted if necessary to avoid having the +inserted string blend into the existing buffer text. More +precisely: + +1. If the inserted string does not contain newlines, a space may +be added before or after it as needed to avoid inserting a word +constituent character next to an existing word constituent. + +2. For a multiline inserted string, newlines may be added before +or after as needed to ensure the inserted string is on lines of +its own." + (let* ((separator (embark--separator strings)) + (multiline + (or (and (cdr strings) (string-match-p "\n" separator)) + (and (null (cdr strings)) + (equal (buffer-substring (line-beginning-position) + (line-end-position)) + (car strings))) + (seq-some (lambda (s) (string-match-p "\n" s)) strings)))) + (cl-labels ((maybe-space () + (and (looking-at "\\w") (looking-back "\\w" 1) + (insert " "))) + (maybe-newline () + (or (looking-back "^[ \t]*" 40) (looking-at "\n") + (newline-and-indent))) + (maybe-whitespace () + (if multiline (maybe-newline) (maybe-space))) + (ins-string () + (let ((start (point))) + (insert + (mapconcat #'substring-no-properties strings separator)) + (save-excursion (goto-char start) (maybe-whitespace)) + (when (looking-back "\n" 1) (delete-char -1)) + (save-excursion (maybe-whitespace))))) + (if buffer-read-only + (with-selected-window (other-window-for-scrolling) + (ins-string)) + (ins-string))))) + +;; For Emacs 28 dired-jump will be moved to dired.el, but it seems +;; that since it already has an autoload in Emacs 28, this next +;; autoload is ignored. +(autoload 'dired-jump "dired-x" nil t) + +(defun embark-dired-jump (file &optional other-window) + "Open Dired buffer in directory containing FILE and move to its line. +When called with a prefix argument OTHER-WINDOW, open Dired in other window." + (interactive "fJump to Dired file: \nP") + (dired-jump other-window file)) + +(defun embark--read-from-history (prompt candidates &optional category) + "Read with completion from list of history CANDIDATES of CATEGORY. +Sorting and history are disabled. PROMPT is the prompt message." + (completing-read prompt + (embark--with-category category candidates) + nil t nil t)) + +(defun embark-kill-ring-remove (text) + "Remove TEXT from `kill-ring'." + (interactive (list (embark--read-from-history + "Remove from kill-ring: " kill-ring 'kill-ring))) + (embark-history-remove text) + (setq kill-ring (delete text kill-ring))) + +(defvar recentf-list) +(defun embark-recentf-remove (file) + "Remove FILE from the list of recent files." + (interactive (list (embark--read-from-history + "Remove recent file: " recentf-list 'file))) + (embark-history-remove (expand-file-name file)) + (embark-history-remove (abbreviate-file-name file)) + (when (and (boundp 'recentf-list) (fboundp 'recentf-expand-file-name)) + (setq recentf-list (delete (recentf-expand-file-name file) recentf-list)))) + +(defun embark-history-remove (str) + "Remove STR from `minibuffer-history-variable'. +Many completion UIs sort by history position. This command can be used +to remove entries from the history, such that they are not sorted closer +to the top." + (interactive (list (embark--read-from-history + "Remove history item: " + (if (eq minibuffer-history-variable t) + (user-error "No minibuffer history") + (symbol-value minibuffer-history-variable))))) + (unless (eq minibuffer-history-variable t) + (set minibuffer-history-variable + (delete str (symbol-value minibuffer-history-variable))))) + +(defvar xref-backend-functions) + +(defun embark-find-definition (symbol) + "Find definition of Emacs Lisp SYMBOL." + (interactive "sSymbol: ") + (let ((xref-backend-functions (lambda () 'elisp))) + (xref-find-definitions symbol))) + +(defun embark-info-lookup-symbol (symbol) + "Display the definition of SYMBOL, from the Elisp manual." + (interactive "SSymbol: ") + (info-lookup-symbol symbol 'emacs-lisp-mode)) + +(defun embark-rename-buffer (buffer newname &optional unique) + "Rename BUFFER to NEWNAME, optionally making it UNIQUE. +Interactively, you can set UNIQUE with a prefix argument. +Returns the new name actually used." + (interactive "bBuffer: \nBRename %s to: \nP") + (when-let ((buf (get-buffer buffer))) + (with-current-buffer buf + (rename-buffer newname unique)))) + +(defun embark--package-url (pkg) + "Return homepage for package PKG." + (when-let (desc (embark--package-desc pkg)) + (alist-get :url (package-desc-extras desc)))) + +(defun embark--prompt-for-package () + "Prompt user for a package name." + ;; this code is taken from the interactive spec of describe-package + (unless package--initialized + (package-initialize t)) + (intern + (completing-read "Package: " + (append (mapcar #'car package-alist) + (mapcar #'car package-archive-contents) + (mapcar #'car package--builtins))))) + +(defun embark-browse-package-url (pkg) + "Open homepage for package PKG with `browse-url'." + (interactive (list (embark--prompt-for-package))) + (if-let ((url (embark--package-url pkg))) + (browse-url url) + (user-error "No homepage found for `%s'" pkg))) + +(defun embark-save-package-url (pkg) + "Save URL of homepage for package PKG on the `kill-ring'." + (interactive (list (embark--prompt-for-package))) + (if-let ((url (embark--package-url pkg))) + (kill-new url) + (user-error "No homepage found for `%s'" pkg))) + +(defun embark-save-variable-value (var) + "Save value of VAR in the `kill-ring'." + (interactive "SVariable: ") + (kill-new (string-trim (pp-to-string (symbol-value var))))) + +(defun embark-insert-variable-value (var) + "Insert value of VAR." + (interactive "SVariable: ") + (embark-insert (list (string-trim (pp-to-string (symbol-value var)))))) + +(defun embark-toggle-variable (var &optional local) + "Toggle value of boolean variable VAR. +If prefix LOCAL is non-nil make variable local." + (interactive "SVariable: \nP") + (let ((val (symbol-value var))) + (unless (memq val '(nil t)) + (user-error "Not a boolean variable")) + (when local + (make-local-variable var)) + (funcall (or (get var 'custom-set) 'set) var (not val)))) + +(defun embark-insert-relative-path (file) + "Insert relative path to FILE. +The insert path is relative to `default-directory'." + (interactive "FFile: ") + (embark-insert (list (file-relative-name (substitute-in-file-name file))))) + +(defun embark-save-relative-path (file) + "Save the relative path to FILE in the kill ring. +The insert path is relative to `default-directory'." + (interactive "FFile: ") + (kill-new (file-relative-name (substitute-in-file-name file)))) + +(defun embark-shell-command-on-buffer (buffer command &optional replace) + "Run shell COMMAND on contents of BUFFER. +Called with \\[universal-argument], replace contents of buffer +with command output. For replacement behavior see +`shell-command-dont-erase-buffer' setting." + (interactive + (list + (read-buffer "Buffer: " nil t) + (read-shell-command "Shell command: ") + current-prefix-arg)) + (with-current-buffer buffer + (shell-command-on-region (point-min) (point-max) + command + (and replace (current-buffer))))) + +(defun embark-open-externally (file) + "Open FILE or url using system's default application." + (interactive "sOpen externally: ") + (unless (string-match-p "\\`[a-z]+://" file) + (setq file (expand-file-name file))) + (message "Opening `%s' externally..." file) + (if (and (eq system-type 'windows-nt) + (fboundp 'w32-shell-execute)) + (w32-shell-execute "open" file) + (call-process (pcase system-type + ('darwin "open") + ('cygwin "cygstart") + (_ "xdg-open")) + nil 0 nil file))) + +(declare-function bookmark-prop-get "bookmark") +(declare-function bookmark-completing-read "bookmark") + +(defun embark-bookmark-open-externally (bookmark) + "Open BOOKMARK in external application." + (interactive (list (bookmark-completing-read "Open externally: "))) + (embark-open-externally + (or (bookmark-prop-get bookmark 'location) + (bookmark-prop-get bookmark 'filename) + (user-error "Bookmark `%s' does not have a location" bookmark)))) + +(defun embark-bury-buffer (buf) + "Bury buffer BUF." + (interactive "bBuffer: ") + (if-let (win (get-buffer-window buf)) + (with-selected-window win + (bury-buffer)) + (bury-buffer))) + +(defun embark-kill-buffer-and-window (buf) + "Kill buffer BUF and delete its window." + (interactive "bBuffer: ") + (when-let (buf (get-buffer buf)) + (if-let (win (get-buffer-window buf)) + (with-selected-window win + (kill-buffer-and-window)) + (kill-buffer buf)))) + +(defun embark-save-unicode-character (char) + "Save Unicode character CHAR to kill ring." + (interactive + (list (read-char-by-name "Insert character (Unicode name or hex): "))) + (kill-new (format "%c" char))) + +(defun embark-isearch-forward () + "Prompt for string in the minibuffer and start isearch forwards. +Unlike isearch, this command reads the string from the +minibuffer, which means it can be used as an Embark action." + (interactive) + (isearch-mode t) + (isearch-edit-string)) + +(defun embark-isearch-backward () + "Prompt for string in the minibuffer and start isearch backwards. +Unlike isearch, this command reads the string from the +minibuffer, which means it can be used as an Embark action." + (interactive) + (isearch-mode nil) + (isearch-edit-string)) + +(defun embark-toggle-highlight () + "Toggle symbol highlighting using `highlight-symbol-at-point'." + (interactive) + (let ((regexp (find-tag-default-as-symbol-regexp)) + (highlighted (cl-find-if #'boundp + '(hi-lock-interactive-lighters + hi-lock-interactive-patterns)))) + (if (and highlighted (assoc regexp (symbol-value highlighted))) + (unhighlight-regexp regexp) + (highlight-symbol-at-point)))) + +(defun embark-next-symbol () + "Jump to next occurrence of symbol at point. +The search respects symbol boundaries." + (interactive) + (if-let ((symbol (thing-at-point 'symbol))) + (let ((regexp (format "\\_<%s\\_>" (regexp-quote symbol)))) + (when (looking-at regexp) + (forward-symbol 1)) + (unless (re-search-forward regexp nil t) + (user-error "Symbol `%s' not found" symbol))) + (user-error "No symbol at point"))) + +(defun embark-previous-symbol () + "Jump to previous occurrence of symbol at point. +The search respects symbol boundaries." + (interactive) + (if-let ((symbol (thing-at-point 'symbol))) + (let ((regexp (format "\\_<%s\\_>" (regexp-quote symbol)))) + (when (looking-back regexp (- (point) (length symbol))) + (forward-symbol -1)) + (unless (re-search-backward regexp nil t) + (user-error "Symbol `%s' not found" symbol))) + (user-error "No symbol at point"))) + +(defun embark-compose-mail (address) + "Compose email to ADDRESS." + ;; The only reason we cannot use compose-mail directly is its + ;; interactive specification, which just supplies nil for the + ;; address (and several other arguments). + (interactive "sTo: ") + (compose-mail address)) + +(autoload 'pp-display-expression "pp") + +(defun embark-pp-eval-defun (edebug) + "Run `eval-defun' and pretty print the result. +With a prefix argument EDEBUG, instrument the code for debugging." + (interactive "P") + (cl-letf (((symbol-function #'eval-expression-print-format) + (lambda (result) + (pp-display-expression result "*Pp Eval Output*")))) + (eval-defun edebug))) + +(defun embark-eval-replace (noquote) + "Evaluate region and replace with evaluated result. +If NOQUOTE is non-nil (interactively, if called with a prefix +argument), no quoting is used for strings." + (interactive "P") + (let ((beg (region-beginning)) + (end (region-end))) + (save-excursion + (goto-char end) + (insert (format (if noquote "%s" "%S") + (eval (read (buffer-substring beg end)) lexical-binding))) + (delete-region beg end)))) + +(when (< emacs-major-version 29) + (defun embark-elp-restore-package (prefix) + "Remove instrumentation from functions with names starting with PREFIX." + (interactive "SPrefix: ") + (when (fboundp 'elp-restore-list) + (elp-restore-list + (mapcar #'intern + (all-completions (symbol-name prefix) + obarray 'elp-profilable-p)))))) + +(defmacro embark--define-hash (algorithm) + "Define command which computes hash from a string. +ALGORITHM is the hash algorithm symbol understood by `secure-hash'." + `(defun ,(intern (format "embark-hash-%s" algorithm)) (str) + ,(format "Compute %s hash of STR and store it in the kill ring." algorithm) + (interactive "sString: ") + (let ((hash (secure-hash ',algorithm str))) + (kill-new hash) + (message "%s: %s" ',algorithm hash)))) + +(embark--define-hash md5) +(embark--define-hash sha1) +(embark--define-hash sha224) +(embark--define-hash sha256) +(embark--define-hash sha384) +(embark--define-hash sha512) + +(defun embark-encode-url (start end) + "Properly URI-encode the region between START and END in current buffer." + (interactive "r") + (let ((encoded (url-encode-url (buffer-substring-no-properties start end)))) + (delete-region start end) + (insert encoded))) + +(defun embark-decode-url (start end) + "Decode the URI-encoded region between START and END in current buffer." + (interactive "r") + (let ((decoded (url-unhex-string (buffer-substring-no-properties start end)))) + (delete-region start end) + (insert decoded))) + +(defvar epa-replace-original-text) +(defun embark-epa-decrypt-region (start end) + "Decrypt region between START and END." + (interactive "r") + (let ((epa-replace-original-text t)) + (epa-decrypt-region start end))) + +(defvar eww-download-directory) +(autoload 'eww-download-callback "eww") + +(defun embark-download-url (url) + "Download URL to `eww-download-directory'." + (interactive "sDownload URL: ") + (let ((dir eww-download-directory)) + (when (functionp dir) (setq dir (funcall dir))) + (access-file dir "Download failed") + (url-retrieve + url #'eww-download-callback + (if (>= emacs-major-version 28) (list url dir) (list url))))) + +;;; Setup and pre-action hooks + +(defun embark--restart (&rest _) + "Restart current command with current input. +Use this to refresh the list of candidates for commands that do +not handle that themselves." + (when (minibufferp) + (embark--become-command embark--command (minibuffer-contents)))) + +(defun embark--shell-prep (&rest _) + "Prepare target for use as argument for a shell command. +This quotes the spaces, inserts an extra space at the beginning +and leaves the point to the left of it." + (let ((contents (minibuffer-contents))) + (delete-minibuffer-contents) + (insert " " (shell-quote-wildcard-pattern contents)) + (goto-char (minibuffer-prompt-end)))) + +(defun embark--force-complete (&rest _) + "Select first minibuffer completion candidate matching target." + (minibuffer-force-complete)) + +(cl-defun embark--eval-prep (&key type &allow-other-keys) + "If target's TYPE is variable, skip edit; if function, wrap in ()." + (when (memq type '(command function)) + (embark--allow-edit) + (goto-char (minibuffer-prompt-end)) + (insert "(") + (goto-char (point-max)) + (insert ")") + (backward-char))) + +(cl-defun embark--beginning-of-target (&key bounds &allow-other-keys) + "Go to beginning of the target BOUNDS." + (when (number-or-marker-p (car bounds)) + (goto-char (car bounds)))) + +(cl-defun embark--end-of-target (&key bounds &allow-other-keys) + "Go to end of the target BOUNDS." + (when (number-or-marker-p (cdr bounds)) + (goto-char (cdr bounds)))) + +(cl-defun embark--mark-target (&rest rest &key run bounds &allow-other-keys) + "Mark the target if its BOUNDS are known. +After marking the target, call RUN with the REST of its arguments." + (cond + ((and bounds run) + (save-mark-and-excursion + (set-mark (cdr bounds)) + (goto-char (car bounds)) + (apply run :bounds bounds rest))) + (bounds ;; used as pre- or post-action hook + (set-mark (cdr bounds)) + (goto-char (car bounds))) + (run (apply run rest)))) + +(cl-defun embark--unmark-target (&rest _) + "Deactivate the region target." + (deactivate-mark t)) + +(cl-defun embark--narrow-to-target + (&rest rest &key run bounds &allow-other-keys) + "Narrow buffer to target if its BOUNDS are known. +Intended for use as an Embark around-action hook. This function +runs RUN with the buffer narrowed to given BOUNDS passing along +the REST of the arguments." + (if bounds + (save-excursion + (save-restriction + (narrow-to-region (car bounds) (cdr bounds)) + (goto-char (car bounds)) + (apply run :bounds bounds rest))) + (apply run rest))) + +(defun embark--allow-edit (&rest _) + "Allow editing the target." + (remove-hook 'post-command-hook #'exit-minibuffer t) + (remove-hook 'post-command-hook 'ivy-immediate-done t)) + +(defun embark--ignore-target (&rest _) + "Ignore the target." + (let ((contents + (get-text-property (minibuffer-prompt-end) 'embark--initial-input))) + (delete-minibuffer-contents) + (when contents (insert contents))) + (embark--allow-edit)) + +(autoload 'xref-push-marker-stack "xref") +(defun embark--xref-push-marker (&rest _) + "Push a marker onto the xref marker stack." + (xref-push-marker-stack)) + +(cl-defun embark--confirm (&key action target &allow-other-keys) + "Ask for confirmation before running the ACTION on the TARGET." + (unless (y-or-n-p (format "Run %s on %s? " action target)) + (user-error "Canceled"))) + +(defconst embark--associated-file-fn-alist + `((file . identity) + (buffer . ,(lambda (target) + (let ((buffer (get-buffer target))) + (or (buffer-file-name buffer) + (buffer-local-value 'default-directory buffer))))) + (bookmark . bookmark-location) + (library . locate-library)) + "Alist of functions that extract a file path from targets of a given type.") + +(defun embark--associated-directory (target type) + "Return directory associated to TARGET of given TYPE. +The supported values of TYPE are file, buffer, bookmark and +library, which have an obvious notion of associated directory." + (when-let ((file-fn (alist-get type embark--associated-file-fn-alist)) + (file (funcall file-fn target))) + (if (file-directory-p file) + (file-name-as-directory file) + (file-name-directory file)))) + +(cl-defun embark--cd (&rest rest &key run target type &allow-other-keys) + "Run action with `default-directory' set to the directory of TARGET. +The supported values of TYPE are file, buffer, bookmark and +library, which have an obvious notion of associated directory. +The REST of the arguments are also passed to RUN." + (let ((default-directory + (or (embark--associated-directory target type) default-directory))) + (apply run :target target :type type rest))) + +(cl-defun embark--save-excursion (&rest rest &key run &allow-other-keys) + "Run action without moving point. +This simply calls RUN with the REST of its arguments inside +`save-excursion'." + (save-excursion (apply run rest))) + +(defun embark--universal-argument (&rest _) + "Run action with a universal prefix argument." + (setq prefix-arg '(4))) + +;;; keymaps + +(defvar-keymap embark-meta-map + :doc "Keymap for non-action Embark functions." + "-" #'negative-argument + "0" #'digit-argument + "1" #'digit-argument + "2" #'digit-argument + "3" #'digit-argument + "4" #'digit-argument + "5" #'digit-argument + "6" #'digit-argument + "7" #'digit-argument + "8" #'digit-argument + "9" #'digit-argument) + +(defvar-keymap embark-general-map + :doc "Keymap for Embark general actions." + :parent embark-meta-map + "i" #'embark-insert + "w" #'embark-copy-as-kill + "q" #'embark-toggle-quit + "E" #'embark-export + "S" #'embark-collect + "L" #'embark-live + "B" #'embark-become + "A" #'embark-act-all + "C-s" #'embark-isearch-forward + "C-r" #'embark-isearch-backward + "C-SPC" #'mark + "DEL" #'delete-region + "SPC" #'embark-select) + +(defvar-keymap embark-encode-map + :doc "Keymap for Embark region encoding actions." + "r" #'rot13-region + "." #'morse-region + "-" #'unmorse-region + "s" #'studlify-region + "m" #'embark-hash-md5 + "1" #'embark-hash-sha1 + "2" #'embark-hash-sha256 + "3" #'embark-hash-sha384 + "4" #'embark-hash-sha224 + "5" #'embark-hash-sha512 + "f" #'format-encode-region + "F" #'format-decode-region + "b" #'base64-encode-region + "B" #'base64-decode-region + "u" #'embark-encode-url + "U" #'embark-decode-url + "c" #'epa-encrypt-region + "C" #'embark-epa-decrypt-region) + +(fset 'embark-encode-map embark-encode-map) + +(defvar-keymap embark-sort-map + :doc "Keymap for Embark actions that sort the region" + "l" #'sort-lines + "P" #'sort-pages + "f" #'sort-fields + "c" #'sort-columns + "p" #'sort-paragraphs + "r" #'sort-regexp-fields + "n" #'sort-numeric-fields) + +(fset 'embark-sort-map embark-sort-map) + +;; these will have autoloads in Emacs 28 +(autoload 'calc-grab-sum-down "calc" nil t) +(autoload 'calc-grab-sum-across "calc" nil t) + +;; this has had an autoload cookie since at least Emacs 26 +;; but that autoload doesn't seem to work for me +(autoload 'org-table-convert-region "org-table" nil t) + +(defvar-keymap embark-region-map + :doc "Keymap for Embark actions on the active region." + :parent embark-general-map + "u" #'upcase-region + "l" #'downcase-region + "c" #'capitalize-region + "|" #'shell-command-on-region + "e" #'eval-region + "<" #'embark-eval-replace + "a" #'align + "A" #'align-regexp + "" #'indent-rigidly + "" #'indent-rigidly + "TAB" #'indent-region + "f" #'fill-region + "p" #'fill-region-as-paragraph + "$" #'ispell-region + "=" #'count-words-region + "F" #'whitespace-cleanup-region + "t" #'transpose-regions + "o" #'org-table-convert-region + ";" #'comment-or-uncomment-region + "W" #'write-region + "+" #'append-to-file + "m" #'apply-macro-to-region-lines + "n" #'narrow-to-region + "*" #'calc-grab-region + ":" #'calc-grab-sum-down + "_" #'calc-grab-sum-across + "r" #'reverse-region + "d" #'delete-duplicate-lines + "b" #'browse-url-of-region + "h" #'shr-render-region + "'" #'expand-region-abbrevs + "v" #'vc-region-history + "R" #'repunctuate-sentences + "s" 'embark-sort-map + ">" 'embark-encode-map) + +(defvar-keymap embark-vc-file-map + :doc "Keymap for Embark VC file actions." + "d" #'vc-delete-file + "r" #'vc-rename-file + "i" #'vc-ignore) + +(fset 'embark-vc-file-map embark-vc-file-map) + +(defvar-keymap embark-file-map + :doc "Keymap for Embark file actions." + :parent embark-general-map + "RET" #'find-file + "f" #'find-file + "F" #'find-file-literally + "o" #'find-file-other-window + "d" #'delete-file + "D" #'delete-directory + "r" #'rename-file + "c" #'copy-file + "s" #'make-symbolic-link + "j" #'embark-dired-jump + "!" #'shell-command + "&" #'async-shell-command + "$" #'eshell + "<" #'insert-file + "m" #'chmod + "=" #'ediff-files + "+" #'make-directory + "\\" #'embark-recentf-remove + "I" #'embark-insert-relative-path + "W" #'embark-save-relative-path + "x" #'embark-open-externally + "e" #'eww-open-file + "l" #'load-file + "b" #'byte-compile-file + "R" #'byte-recompile-directory + "v" 'embark-vc-file-map) + +(defvar-keymap embark-kill-ring-map + :doc "Keymap for `kill-ring' commands." + :parent embark-general-map + "\\" #'embark-kill-ring-remove) + +(defvar-keymap embark-url-map + :doc "Keymap for Embark url actions." + :parent embark-general-map + "RET" #'browse-url + "b" #'browse-url + "d" #'embark-download-url + "x" #'embark-open-externally + "e" #'eww) + +(defvar-keymap embark-email-map + :doc "Keymap for Embark email actions." + :parent embark-general-map + "RET" #'embark-compose-mail + "c" #'embark-compose-mail) + +(defvar-keymap embark-library-map + :doc "Keymap for operations on Emacs Lisp libraries." + :parent embark-general-map + "RET" #'find-library + "l" #'load-library + "f" #'find-library + "h" #'finder-commentary + "a" #'apropos-library + "L" #'locate-library + "m" #'info-display-manual + "$" #'eshell) + +(defvar-keymap embark-buffer-map + :doc "Keymap for Embark buffer actions." + :parent embark-general-map + "RET" #'switch-to-buffer + "k" #'kill-buffer + "b" #'switch-to-buffer + "o" #'switch-to-buffer-other-window + "z" #'embark-bury-buffer + "K" #'embark-kill-buffer-and-window + "r" #'embark-rename-buffer + "=" #'ediff-buffers + "|" #'embark-shell-command-on-buffer + "<" #'insert-buffer + "$" #'eshell) + +(defvar-keymap embark-tab-map + :doc "Keymap for actions for tab-bar tabs." + :parent embark-general-map + "RET" #'tab-bar-select-tab-by-name + "s" #'tab-bar-select-tab-by-name + "r" #'tab-bar-rename-tab-by-name + "k" #'tab-bar-close-tab-by-name) + +(defvar-keymap embark-identifier-map + :doc "Keymap for Embark identifier actions." + :parent embark-general-map + "RET" #'xref-find-definitions + "h" #'display-local-help + "H" #'embark-toggle-highlight + "d" #'xref-find-definitions + "r" #'xref-find-references + "a" #'xref-find-apropos + "s" #'info-lookup-symbol + "n" #'embark-next-symbol + "p" #'embark-previous-symbol + "'" #'expand-abbrev + "$" #'ispell-word + "o" #'occur) + +(defvar-keymap embark-expression-map + :doc "Keymap for Embark expression actions." + :parent embark-general-map + "RET" #'pp-eval-expression + "e" #'pp-eval-expression + "<" #'embark-eval-replace + "m" #'pp-macroexpand-expression + "TAB" #'indent-region + "r" #'raise-sexp + ";" #'comment-dwim + "t" #'transpose-sexps + "k" #'kill-region + "u" #'backward-up-list + "n" #'forward-list + "p" #'backward-list) + +(defvar-keymap embark-defun-map + :doc "Keymap for Embark defun actions." + :parent embark-expression-map + "RET" #'embark-pp-eval-defun + "e" #'embark-pp-eval-defun + "c" #'compile-defun + "D" #'edebug-defun + "o" #'checkdoc-defun + "N" #'narrow-to-defun) + +;; Use quoted symbols to avoid byte-compiler warnings. +(defvar-keymap embark-heading-map + :doc "Keymap for Embark heading actions." + :parent embark-general-map + "RET" 'outline-show-subtree + "TAB" 'outline-cycle ;; New in Emacs 28! + "C-SPC" 'outline-mark-subtree + "n" 'outline-next-visible-heading + "p" 'outline-previous-visible-heading + "f" 'outline-forward-same-level + "b" 'outline-backward-same-level + "^" 'outline-move-subtree-up + "v" 'outline-move-subtree-down + "u" 'outline-up-heading + "+" 'outline-show-subtree + "-" 'outline-hide-subtree + ">" 'outline-demote + "<" 'outline-promote) + +(defvar-keymap embark-symbol-map + :doc "Keymap for Embark symbol actions." + :parent embark-identifier-map + "RET" #'embark-find-definition + "h" #'describe-symbol + "s" #'embark-info-lookup-symbol + "d" #'embark-find-definition + "e" #'pp-eval-expression + "a" #'apropos + "\\" #'embark-history-remove) + +(defvar-keymap embark-face-map + :doc "Keymap for Embark face actions." + :parent embark-symbol-map + "h" #'describe-face + "c" #'customize-face + "+" #'make-face-bold + "-" #'make-face-unbold + "/" #'make-face-italic + "|" #'make-face-unitalic + "!" #'invert-face + "f" #'set-face-foreground + "b" #'set-face-background) + +(defvar-keymap embark-variable-map + :doc "Keymap for Embark variable actions." + :parent embark-symbol-map + "=" #'set-variable + "c" #'customize-set-variable + "u" #'customize-variable + "v" #'embark-save-variable-value + "<" #'embark-insert-variable-value + "t" #'embark-toggle-variable) + +(defvar-keymap embark-function-map + :doc "Keymap for Embark function actions." + :parent embark-symbol-map + "m" #'elp-instrument-function ;; m=measure + "M" 'elp-restore-function ;; quoted, not autoloaded + "k" #'debug-on-entry ;; breaKpoint (running out of letters, really) + "K" #'cancel-debug-on-entry + "t" #'trace-function + "T" 'untrace-function) ;; quoted, not autoloaded + +(defvar-keymap embark-command-map + :doc "Keymap for Embark command actions." + :parent embark-function-map + "x" #'execute-extended-command + "I" #'Info-goto-emacs-command-node + "b" #'where-is + "g" #'global-set-key + "l" #'local-set-key) + +(defvar-keymap embark-package-map + :doc "Keymap for Embark package actions." + :parent embark-general-map + "RET" #'describe-package + "h" #'describe-package + "i" #'package-install + "I" #'embark-insert + "d" #'package-delete + "r" #'package-reinstall + "u" #'embark-browse-package-url + "W" #'embark-save-package-url + "a" #'package-autoremove + "g" #'package-refresh-contents + "m" #'elp-instrument-package ;; m=measure + "M" (if (fboundp 'embark-elp-restore-package) + 'embark-elp-restore-package + 'elp-restore-package)) + +(defvar-keymap embark-bookmark-map + :doc "Keymap for Embark bookmark actions." + :parent embark-general-map + "RET" #'bookmark-jump + "s" #'bookmark-set + "d" #'bookmark-delete + "r" #'bookmark-rename + "R" #'bookmark-relocate + "l" #'bookmark-locate + "<" #'bookmark-insert + "j" #'bookmark-jump + "o" #'bookmark-jump-other-window + "f" #'bookmark-jump-other-frame + "a" 'bookmark-show-annotation + "e" 'bookmark-edit-annotation + "x" #'embark-bookmark-open-externally + "$" #'eshell) + +(defvar-keymap embark-unicode-name-map + :doc "Keymap for Embark Unicode name actions." + :parent embark-general-map + "RET" #'insert-char + "I" #'insert-char + "W" #'embark-save-unicode-character) + +(defvar-keymap embark-prose-map + :doc "Keymap for Embark actions for dealing with prose." + :parent embark-general-map + "$" #'ispell-region + "f" #'fill-region + "u" #'upcase-region + "l" #'downcase-region + "c" #'capitalize-region + "F" #'whitespace-cleanup-region + "=" #'count-words-region) + +(defvar-keymap embark-sentence-map + :doc "Keymap for Embark actions for dealing with sentences." + :parent embark-prose-map + "t" #'transpose-sentences + "n" #'forward-sentence + "p" #'backward-sentence) + +(defvar-keymap embark-paragraph-map + :doc "Keymap for Embark actions for dealing with paragraphs." + :parent embark-prose-map + "t" #'transpose-paragraphs + "n" #'forward-paragraph + "p" #'backward-paragraph + "R" #'repunctuate-sentences) + +(defvar-keymap embark-flymake-map + :doc "Keymap for Embark actions on Flymake diagnostics." + :parent embark-general-map + "RET" 'flymake-show-buffer-diagnostics + "n" 'flymake-goto-next-error + "p" 'flymake-goto-prev-error) + +(defvar-keymap embark-become-help-map + :doc "Keymap for Embark help actions." + :parent embark-meta-map + "V" #'apropos-variable + "U" #'apropos-user-option + "C" #'apropos-command + "v" #'describe-variable + "f" #'describe-function + "s" #'describe-symbol + "F" #'describe-face + "p" #'describe-package + "i" #'describe-input-method) + +(autoload 'recentf-open-files "recentf" nil t) + +(defvar-keymap embark-become-file+buffer-map + :doc "Embark become keymap for files and buffers." + :parent embark-meta-map + "f" #'find-file + "4 f" #'find-file-other-window + "." #'find-file-at-point + "p" #'project-find-file + "r" #'recentf-open-files + "b" #'switch-to-buffer + "4 b" #'switch-to-buffer-other-window + "l" #'locate + "L" #'find-library + "v" #'vc-dir) + +(defvar-keymap embark-become-shell-command-map + :doc "Embark become keymap for shell commands." + :parent embark-meta-map + "!" #'shell-command + "&" #'async-shell-command + "c" #'comint-run + "t" #'term) + +(defvar-keymap embark-become-match-map + :doc "Embark become keymap for search." + :parent embark-meta-map + "o" #'occur + "k" #'keep-lines + "f" #'flush-lines + "c" #'count-matches) + +(provide 'embark) + +;; Check that embark-consult is installed. If Embark is used in +;; combination with Consult, you should install the integration package, +;; such that features like embark-export from consult-grep work as +;; expected. + +(with-eval-after-load 'consult + (unless (require 'embark-consult nil 'noerror) + (warn "The package embark-consult should be installed if you use both Embark and Consult"))) + +(with-eval-after-load 'org + (require 'embark-org)) + +;;; embark.el ends here blob - /dev/null blob + d065002fe448518c34b6dd1d7d20f4ac1fb9255a (mode 644) --- /dev/null +++ elpa/embark-1.1/embark.info @@ -0,0 +1,1527 @@ +This is docytLD1w.info, produced by makeinfo version 6.8 from +embark.texi. + +INFO-DIR-SECTION Emacs misc features +START-INFO-DIR-ENTRY +* Embark: (embark). Emacs Mini-Buffer Actions Rooted in Keymaps. +END-INFO-DIR-ENTRY + + +File: docytLD1w.info, Node: Top, Next: Overview, Up: (dir) + +Embark: Emacs Mini-Buffer Actions Rooted in Keymaps +*************************************************** + +* Menu: + +* Overview:: +* Quick start:: +* Advanced configuration:: +* How does Embark call the actions?:: +* Embark, Marginalia and Consult: Embark Marginalia and Consult. +* Related Packages:: +* Resources:: +* Contributions:: +* Acknowledgments:: + +— The Detailed Node Listing — + +Overview + +* Acting on targets:: +* The default action on a target:: +* Working with sets of possible targets:: +* Switching to a different command without losing what you've typed:: + +Working with sets of possible targets + +* Selecting some targets to make an ad hoc candidate set:: +* embark-live a live-updating variant of embark-collect:: + +Advanced configuration + +* Showing information about available targets and actions:: +* Selecting commands via completions instead of key bindings:: +* Quitting the minibuffer after an action:: +* Running some setup after injecting the target:: +* Running hooks before, after or around an action: Running hooks before after or around an action. +* Creating your own keymaps:: +* Defining actions for new categories of targets:: + +Selecting commands via completions instead of key bindings + +* Selecting commands via completion outside of Embark:: + +Defining actions for new categories of targets + +* New minibuffer target example - tab-bar tabs:: +* New target example in regular buffers - short Wikipedia links:: + +How does Embark call the actions? + +* Non-interactive functions as actions:: + +Embark, Marginalia and Consult + +* Marginalia:: +* Consult:: + + + +File: docytLD1w.info, Node: Overview, Next: Quick start, Prev: Top, Up: Top + +1 Overview +********** + +Embark makes it easy to choose a command to run based on what is near +point, both during a minibuffer completion session (in a way familiar to +Helm or Counsel users) and in normal buffers. Bind the command +‘embark-act’ to a key and it acts like prefix-key for a keymap of +_actions_ (commands) relevant to the _target_ around point. With point +on an URL in a buffer you can open the URL in a browser or eww or +download the file it points to. If while switching buffers you spot an +old one, you can kill it right there and continue to select another. +Embark comes preconfigured with over a hundred actions for common types +of targets such as files, buffers, identifiers, s-expressions, +sentences; and it is easy to add more actions and more target types. +Embark can also collect all the candidates in a minibuffer to an +occur-like buffer or export them to a buffer in a major-mode specific to +the type of candidates, such as dired for a set of files, ibuffer for a +set of buffers, or customize for a set of variables. + +* Menu: + +* Acting on targets:: +* The default action on a target:: +* Working with sets of possible targets:: +* Switching to a different command without losing what you've typed:: + + +File: docytLD1w.info, Node: Acting on targets, Next: The default action on a target, Up: Overview + +1.1 Acting on targets +===================== + +You can think of ‘embark-act’ as a keyboard-based version of a +right-click contextual menu. The ‘embark-act’ command (which you should +bind to a convenient key), acts as a prefix for a keymap offering you +relevant _actions_ to use on a _target_ determined by the context: + + • In the minibuffer, the target is the current top completion + candidate. + • In the ‘*Completions*’ buffer the target is the completion at + point. + • In a regular buffer, the target is the region if active, or else + the file, symbol, URL, s-expression or defun at point. + + Multiple targets can be present at the same location and you can +cycle between them by repeating the ‘embark-act’ key binding. The type +of actions offered depend on the type of the target. Here is a sample +of a few of the actions offered in the default configuration: + + • For files you get offered actions like deleting, copying, renaming, + visiting in another window, running a shell command on the file, + etc. + • For buffers the actions include switching to or killing the buffer. + • For package names the actions include installing, removing or + visiting the homepage. + • For Emacs Lisp symbols the actions include finding the definition, + looking up documentation, evaluating (which for a variable + immediately shows the value, but for a function lets you pass it + some arguments first). There are some actions specific to + variables, such as setting the value directly or though the + customize system, and some actions specific to commands, such as + binding it to a key. + + By default when you use ‘embark-act’ if you don’t immediately select +an action, after a short delay Embark will pop up a buffer showing a +list of actions and their corresponding key bindings. If you are using +‘embark-act’ outside the minibuffer, Embark will also highlight the +current target. These behaviors are configurable via the variable +‘embark-indicators’. Instead of selecting an action via its key +binding, you can select it by name with completion by typing ‘C-h’ after +‘embark-act’. + + Everything is easily configurable: determining the current target, +classifying it, and deciding which actions are offered for each type in +the classification. The above introduction just mentions part of the +default configuration. + + Configuring which actions are offered for a type is particularly easy +and requires no programming: the variable ‘embark-keymap-alist’ +associates target types with variables containing keymaps, and those +keymaps containing bindings for the actions. (To examine the available +categories and their associated keymaps, you can use ‘C-h v +embark-keymap-alist’ or customize that variable.) For example, in the +default configuration the type ‘file’ is associated with the symbol +‘embark-file-map’. That symbol names a keymap with single-letter key +bindings for common Emacs file commands, for instance ‘c’ is bound to +‘copy-file’. This means that if you are in the minibuffer after running +a command that prompts for a file, such as ‘find-file’ or ‘rename-file’, +you can copy a file by running ‘embark-act’ and then pressing ‘c’. + + These action keymaps are very convenient but not strictly necessary +when using ‘embark-act’: you can use any command that reads from the +minibuffer as an action and the target of the action will be inserted at +the first minibuffer prompt. After running ‘embark-act’ all of your key +bindings and even ‘execute-extended-command’ can be used to run a +command. For example, if you want to replace all occurrences of the +symbol at point, just use ‘M-%’ as the action, there is no need to bind +‘query-replace’ in one of Embark’s keymaps. Also, those action keymaps +are normal Emacs keymaps and you should feel free to bind in them +whatever commands you find useful as actions and want to be available +through convenient bindings. + + The actions in ‘embark-general-map’ are available no matter what type +of completion you are in the middle of. By default this includes +bindings to save the current candidate in the kill ring and to insert +the current candidate in the previously selected buffer (the buffer that +was current when you executed a command that opened up the minibuffer). + + Emacs’s minibuffer completion system includes metadata indicating the +_category_ of what is being completed. For example, ‘find-file’’s +metadata indicates a category of ‘file’ and ‘switch-to-buffer’’s +metadata indicates a category of ‘buffer’. Embark has the related +notion of the _type_ of a target for actions, and by default when +category metadata is present it is taken to be the type of minibuffer +completion candidates when used as targets. Emacs commands often do not +set useful category metadata so the Marginalia +(https://github.com/minad/marginalia) package, which supplies this +missing metadata, is highly recommended for use with Embark. + + Embark’s default configuration has actions for the following target +types: files, buffers, symbols, packages, URLs, bookmarks, and as a +somewhat special case, actions for when the region is active. You can +read about the default actions and their key bindings +(https://github.com/oantolin/embark/wiki/Default-Actions) on the GitHub +project wiki. + + +File: docytLD1w.info, Node: The default action on a target, Next: Working with sets of possible targets, Prev: Acting on targets, Up: Overview + +1.2 The default action on a target +================================== + +Embark has a notion of default action for a target: + + • If the target is a minibuffer completion candidate, then the + default action is whatever command opened the minibuffer in the + first place. For example if you run ‘kill-buffer’, then the + default action will be to kill buffers. + • If the target comes from a regular buffer (i.e., not a minibuffer), + then the default action is whatever is bound to ‘RET’ in the keymap + of actions for that type of target. For example, in Embark’s + default configuration for a URL found at point the default action + is ‘browse-url’, because ‘RET’ is bound to ‘browse-url’ in the + ‘embark-url-map’ keymap. + + To run the default action you can press ‘RET’ after running +‘embark-act’. Note that if there are several different targets at a +given location, each has its own default action, so first cycle to the +target you want and then press ‘RET’ to run the corresponding default +action. + + There is also ‘embark-dwim’ which runs the default action for the +first target found. It’s pretty handy in non-minibuffer buffers: with +Embark’s default configuration it will: + + • Open the file at point. + • Open the URL at point in a web browser (using the ‘browse-url’ + command). + • Compose a new email to the email address at point. + • In an Emacs Lisp buffer, if point is on an opening parenthesis or + right after a closing one, it will evaluate the corresponding + expression. + • Go to the definition of an Emacs Lisp function, variable or macro + at point. + • Find the file corresponding to an Emacs Lisp library at point. + + +File: docytLD1w.info, Node: Working with sets of possible targets, Next: Switching to a different command without losing what you've typed, Prev: The default action on a target, Up: Overview + +1.3 Working with sets of possible targets +========================================= + +Besides acting individually on targets, Embark lets you work +collectively on a set of target _candidates_. For example, while you +are in the minibuffer the candidates are simply the possible completions +of your input. Embark provides three main commands to work on candidate +sets: + + • The ‘embark-act-all’ command runs the same action on each of the + current candidates. It is just like using ‘embark-act’ on each + candidate in turn. (Because you can easily act on many more + candidates than you meant to, by default Embark asks you to confirm + uses of ‘embark-act-all’; you can turn this off by setting the user + option ‘embark-confirm-act-all’ to ‘nil’.) + + • The ‘embark-collect’ command produces a buffer listing all the + current candidates, for you to peruse and run actions on at your + leisure. The candidates are displayed as a list showing additional + annotations. If any of the candidates contain newlines, then + horizontal lines are used to separate candidates. + + The Embark Collect buffer is somewhat “dired-like”: you can select + and deselect candidates through ‘embark-select’ (available as an + action in ‘embark-act’, bound to ‘SPC’; but you could also give it + a global key binding). In an Embark Collect buffer ‘embark-act’ is + bound to ‘a’ and ‘embark-act-all’ is bound to ‘A’; ‘embark-act-all’ + will act on all currently marked candidates if there any, and will + act on all candidates if none are marked. In particular, this + means that ‘a SPC’ will toggle whether the candidate at point is + selected, and ‘A SPC’ will select all candidates if none are + selected, or deselect all selected candidates if there are some. + + • The ‘embark-export’ command tries to open a buffer in an + appropriate major mode for the set of candidates. If the + candidates are files export produces a Dired buffer; if they are + buffers, you get an Ibuffer buffer; and if they are packages you + get a buffer in package menu mode. + + If you use the grepping commands from the Consult + (https://github.com/minad/consult/) package, ‘consult-grep’, + ‘consult-git-grep’ or ‘consult-ripgrep’, then you should install + the ‘embark-consult’ package, which adds support for exporting a + list of grep results to an honest grep-mode buffer, on which you + can even use wgrep (https://github.com/mhayashi1120/Emacs-wgrep) if + you wish. + + When in doubt choosing between exporting and collecting, a good rule +of thumb is to always prefer ‘embark-export’ since when an exporter to a +special major mode is available for a given type of target, it will be +more featureful than an Embark collect buffer, and if no such exporter +is configured the ‘embark-export’ command falls back to the generic +‘embark-collect’. + + These commands are always available as “actions” (although they do +not act on just the current target but on all candidates) for +‘embark-act’ and are bound to ‘A’, ‘S’ (for “snapshot”), and ‘E’, +respectively, in ‘embark-general-map’. This means that you do not have +to bind your own key bindings for these (although you can, of course!), +just a key binding for ‘embark-act’. + + In Embark Collect or Embark Export buffers that were obtained by +running ‘embark-collect’ or ‘embark-export’ from within a minibuffer +completion session, ‘g’ is bound to a command that restarts the +completion session, that is, the command that opened the minibuffer is +run again and the minibuffer contents restored. You can then interact +normally with the command, perhaps editing the minibuffer contents, and, +if you wish, you can rerun ‘embark-collect’ or ‘embark-export’ to get an +updated buffer. + +* Menu: + +* Selecting some targets to make an ad hoc candidate set:: +* embark-live a live-updating variant of embark-collect:: + + +File: docytLD1w.info, Node: Selecting some targets to make an ad hoc candidate set, Next: embark-live a live-updating variant of embark-collect, Up: Working with sets of possible targets + +1.3.1 Selecting some targets to make an ad hoc candidate set +------------------------------------------------------------ + +The commands for working with sets of candidates just described, namely +‘embark-act-all’, ‘embark-export’ and ‘embark-collect’ by default work +with all candidates defined in the current context. For example, in the +minibuffer they operate on all currently completion candidates, or in a +dired buffer they work on all marked files (or all files if none are +marked). Embark also has a notion of _selection_, where you can +accumulate an ad hoc list of targets for these commands to work on. + + The selection is controlled by using the ‘embark-select’ action, +bound to ‘SPC’ in ‘embark-general-map’ so that it is always available +(you can also give ‘embark-select’ a global key binding if you wish; +when called directly, not as an action for ‘embark-act’, it will select +the first target at point). Calling this action on a target toggles its +membership in the current buffer’s Embark selection; that is, it adds it +to selection if not selected and removes it from the selection if it was +selected. Whenever the selection for a buffer is non-empty, the +commands ‘embark-act-all’, ‘embark-export’ and ‘embark-collect’ will act +on the selection. + + To deselect all selected targets, you can use the ‘embark-select’ +action through ‘embark-act-all’, since this will run ‘embark-select’ on +each member of the current selection. Similarly if no targets are +selected and you are in a minibuffer completion session, running +‘embark-select’ from ‘embark-act-all’ will select all the current +completion candidates. + + By default, whenever some targets are selected in the current buffer, +a count of selected targets appears in the mode line. This can be +turned off or customized through the ‘embark-selection-indicator’ user +option. + + The selection functionality is supported in every buffer: + + • In the minibuffer this gives a convenient way to act on several + completion candidates that don’t follow any simple pattern: just go + through the completions selecting the ones you want, then use + ‘embark-act-all’. For example, you could attach several files at + once to an email. + • For Embark Collect buffers this functionality enables a dired-like + workflow, in which you mark various candidates and apply an action + to all at once. (It supersedes a previous ad hoc dired-like + interface that was implemented only in Embark Collect buffers, with + a slightly different interface.) + • In a eww buffer you could use this to select various links you wish + to follow up on, and then collect them into a buffer. Similarly, + while reading Emacs’s info manual you could select some symbols you + want to read more about and export them to an ‘apropos-mode’ + buffer. + • You can use selections in regular text or programming buffers to do + complex editing operations. For example, if you have three + paragraphs scattered over a file and you want to bring them + together, you can select each one, insert them all somewhere and + finally delete all of them (from their original locations). + + +File: docytLD1w.info, Node: embark-live a live-updating variant of embark-collect, Prev: Selecting some targets to make an ad hoc candidate set, Up: Working with sets of possible targets + +1.3.2 ‘embark-live’ a live-updating variant of ‘embark-collect’ +--------------------------------------------------------------- + +Finally, there is also an ‘embark-live’ variant of the ‘embark-collect’ +command which automatically updates the collection after each change in +the source buffer. Users of a completion UI that automatically updates +and displays the candidate list (such as Vertico, Icomplete, Fido-mode, +or MCT) will probably not want to use ‘embark-live’ from the minibuffer +as they will then have two live updating displays of the completion +candidates! + + A more likely use of ‘embark-live’ is to be called from a regular +buffer to display a sort of live updating “table of contents” for the +buffer. This depends on having appropriate candidate collectors +configured in ‘embark-candidate-collectors’. There are not many in +Embark’s default configuration, but you can try this experiment: open a +dired buffer in a directory that has very many files, mark a few, and +run ‘embark-live’. You’ll get an Embark Collect buffer containing only +the marked files, which updates as you mark or unmark files in dired. +To make ‘embark-live’ genuinely useful other candidate collectors are +required. The ‘embark-consult’ package (documented near the end of this +manual) contains a few: one for imenu items and one for outline headings +as used by ‘outline-minor-mode’. Those collectors really do give +‘embark-live’ a table-of-contents feel. + + +File: docytLD1w.info, Node: Switching to a different command without losing what you've typed, Prev: Working with sets of possible targets, Up: Overview + +1.4 Switching to a different command without losing what you’ve typed +===================================================================== + +Embark also has the ‘embark-become’ command which is useful for when you +run a command, start typing at the minibuffer and realize you meant a +different command. The most common case for me is that I run +‘switch-to-buffer’, start typing a buffer name and realize I haven’t +opened the file I had in mind yet! I’ll use this situation as a running +example to illustrate ‘embark-become’. When this happens I can, of +course, press ‘C-g’ and then run ‘find-file’ and open the file, but this +requires retyping the portion of the file name you already typed. This +process can be streamlined with ‘embark-become’: while still in the +‘switch-to-buffer’ you can run ‘embark-become’ and effectively make the +‘switch-to-buffer’ command become ‘find-file’ for this run. + + You can bind ‘embark-become’ to a key in ‘minibuffer-local-map’, but +it is also available as an action under the letter ‘B’ (uppercase), so +you don’t need a binding if you already have one for ‘embark-act’. So, +assuming I have ‘embark-act’ bound to, say, ‘C-.’, once I realize I +haven’t open the file I can type ‘C-. B C-x C-f’ to have +‘switch-to-buffer’ become ‘find-file’ without losing what I have already +typed in the minibuffer. + + But for even more convenience, ‘embark-become’ offers shorter key +bindings for commands you are likely to want the current command to +become. When you use ‘embark-become’ it looks for the current command +in all keymaps named in the list ‘embark-become-keymaps’ and then +activates all keymaps that contain it. For example, the default value +of ‘embark-become-keymaps’ contains a keymap +‘embark-become-file+buffer-map’ with bindings for several commands +related to files and buffers, in particular, it binds ‘switch-to-buffer’ +to ‘b’ and ‘find-file’ to ‘f’. So when I accidentally try to switch to +a buffer for a file I haven’t opened yet, ‘embark-become’ finds that the +command I ran, ‘switch-to-buffer’, is in the keymap +‘embark-become-file+buffer-map’, so it activates that keymap (and any +others that also contain a binding for ‘switch-to-buffer’). The end +result is that I can type ‘C-. B f’ to switch to ‘find-file’. + + +File: docytLD1w.info, Node: Quick start, Next: Advanced configuration, Prev: Overview, Up: Top + +2 Quick start +************* + +The easiest way to install Embark is from GNU ELPA, just run ‘M-x +package-install RET embark RET’. (It is also available on MELPA.) It +is highly recommended to also install Marginalia +(https://github.com/minad/marginalia) (also available on GNU ELPA), so +that Embark can offer you preconfigured actions in more contexts. For +‘use-package’ users, the following is a very reasonable starting +configuration: + + (use-package marginalia + :ensure t + :config + (marginalia-mode)) + + (use-package embark + :ensure t + + :bind + (("C-." . embark-act) ;; pick some comfortable binding + ("C-;" . embark-dwim) ;; good alternative: M-. + ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' + + :init + + ;; Optionally replace the key help with a completing-read interface + (setq prefix-help-command #'embark-prefix-help-command) + + ;; Show the Embark target at point via Eldoc. You may adjust the + ;; Eldoc strategy, if you want to see the documentation from + ;; multiple providers. Beware that using this can be a little + ;; jarring since the message shown in the minibuffer can be more + ;; than one line, causing the modeline to move up and down: + + ;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target) + ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) + + :config + + ;; Hide the mode line of the Embark live/completions buffers + (add-to-list 'display-buffer-alist + '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" + nil + (window-parameters (mode-line-format . none))))) + + ;; Consult users will also want the embark-consult package. + (use-package embark-consult + :ensure t ; only need to install it, embark loads it after consult if found + :hook + (embark-collect-mode . consult-preview-at-point-mode)) + + About the suggested key bindings for ‘embark-act’ and ‘embark-dwim’: + • Those key bindings are unlikely to work in the terminal, but + terminal users are probably well aware of this and will know to + select different bindings. + • The suggested ‘C-.’ binding is used by default in (at least some + installations of) GNOME to input emojis, and Emacs doesn’t even get + a chance to respond to the binding. You can select a different key + binding for ‘embark-act’ or use ‘ibus-setup’ to change the shortcut + for emoji insertion (Emacs 29 will likely use ‘C-x 8 e e’, in case + you want to set the same one system-wide). + • The suggested alternative of ‘M-.’ for ‘embark-dwim’ is bound by + default to ‘xref-find-definitions’. That is a very useful command + but overwriting it with ‘embark-dwim’ is sensible since in Embark’s + default configuration, ‘embark-dwim’ will also find the definition + of the identifier at point. (Note that ‘xref-find-definitions’ + with a prefix argument prompts you for an identifier, ‘embark-dwim’ + does not cover this case). + + Other Embark commands such as ‘embark-act-all’, ‘embark-become’, +‘embark-collect’, and ‘embark-export’ can be run through ‘embark-act’ as +actions bound to ‘A’, ‘B’, ‘S’ (for “snapshot”), and ‘E’ respectively, +and thus don’t really need a dedicated key binding, but feel free to +bind them directly if you so wish. If you do choose to bind them +directly, you’ll probably want to bind them in ‘minibuffer-local-map’, +since they are most useful in the minibuffer (in fact, ‘embark-become’ +only works in the minibuffer). + + The command ‘embark-dwim’ executes the default action at point. +Another good keybinding for ‘embark-dwim’ is ‘M-.’ since ‘embark-dwim’ +acts like ‘xref-find-definitions’ on the symbol at point. ‘C-.’ can be +seen as a right-click context menu at point and ‘M-.’ acts like +left-click. The keybindings are mnemonic, both act at the point (‘.’). + + Embark needs to know what your minibuffer completion system considers +to be the list of candidates and which one is the current candidate. +Embark works out of the box if you use Emacs’s default tab completion, +the built-in ‘icomplete-mode’ or ‘fido-mode’, or the third-party +packages Vertico (https://github.com/minad/vertico) or Ivy +(https://github.com/abo-abo/swiper). + + If you are a Helm (https://emacs-helm.github.io/helm/) or Ivy +(https://github.com/abo-abo/swiper) user you are unlikely to want Embark +since those packages include comprehensive functionality for acting on +minibuffer completion candidates. (Embark does come with Ivy +integration despite this.) + + +File: docytLD1w.info, Node: Advanced configuration, Next: How does Embark call the actions?, Prev: Quick start, Up: Top + +3 Advanced configuration +************************ + +* Menu: + +* Showing information about available targets and actions:: +* Selecting commands via completions instead of key bindings:: +* Quitting the minibuffer after an action:: +* Running some setup after injecting the target:: +* Running hooks before, after or around an action: Running hooks before after or around an action. +* Creating your own keymaps:: +* Defining actions for new categories of targets:: + + +File: docytLD1w.info, Node: Showing information about available targets and actions, Next: Selecting commands via completions instead of key bindings, Up: Advanced configuration + +3.1 Showing information about available targets and actions +=========================================================== + +By default, if you run ‘embark-act’ and do not immediately select an +action, after a short delay Embark will pop up a buffer called ‘*Embark +Actions*’ containing a list of available actions with their key +bindings. You can scroll that buffer with the mouse of with the usual +commands ‘scroll-other-window’ and ‘scroll-other-window-down’ (bound by +default to ‘C-M-v’ and ‘C-M-S-v’). + + That functionality is provided by the ‘embark-mixed-indicator’, but +Embark has other indicators that can provide information about the +target and its type, what other targets you can cycle to, and which +actions have key bindings in the action map for the current type of +target. Any number of indicators can be active at once and the user +option ‘embark-indicators’ should be set to a list of the desired +indicators. + + Embark comes with the following indicators: + + • ‘embark-minimal-indicator’: shows a messages in the echo area or + minibuffer prompt showing the current target and the types of all + targets starting with the current one. + + • ‘embark-highlight-indicator’: highlights the target at point; on by + default. + + • ‘embark-verbose-indicator’: displays a table of actions and their + key bindings in a buffer; this is not on by default, in favor of + the mixed indicator described next. + + • ‘embark-mixed-indicator’: starts out by behaving as the minimal + indicator but after a short delay acts as the verbose indicator; + this is on by default. + + • ‘embark-isearch-highlight-indicator’: this only does something when + the current target is the symbol at point, in which case it lazily + highlights all occurrences of that symbol in the current buffer, + like isearch; also on by default. + + Users of the popular which-key +(https://github.com/justbur/emacs-which-key) package may prefer to use +the ‘embark-which-key-indicator’ from the Embark wiki +(https://github.com/oantolin/embark/wiki/Additional-Configuration#use-which-key-like-a-key-menu-prompt). +Just copy its definition from the wiki into your configuration and +customize the ‘embark-indicators’ user option to exclude the mixed and +verbose indicators and to include ‘embark-which-key-indicator’. + + If you use Vertico (https://github.com/minad/vertico), there is an +even easier way to get a ‘which-key’-like display that also lets you use +completion to narrow down the list of alternatives, described at the end +of the next section. + + +File: docytLD1w.info, Node: Selecting commands via completions instead of key bindings, Next: Quitting the minibuffer after an action, Prev: Showing information about available targets and actions, Up: Advanced configuration + +3.2 Selecting commands via completions instead of key bindings +============================================================== + +As an alternative to reading the list of actions in the verbose or mixed +indicators (see the previous section for a description of these), you +can press the ‘embark-help-key’, which is ‘C-h’ by default (but you may +prefer ‘?’ to free up ‘C-h’ for use as a prefix) after running +‘embark-act’. Pressing the help key will prompt you for the name of an +action with completion (but feel free to enter a command that is not +among the offered candidates!), and will also remind you of the key +bindings. You can press ‘embark-keymap-prompter-key’, which is ‘@’ by +default, at the prompt and then one of the key bindings to enter the +name of the corresponding action. + + You may think that with the ‘*Embark Actions*’ buffer popping up to +remind you of the key bindings you’d never want to use completion to +select an action by name, but personally I find that typing a small +portion of the action name to narrow down the list of candidates feels +significantly faster than visually scanning the entire list of actions. + + If you find you prefer selecting actions that way, you can configure +embark to always prompt you for actions by setting the variable +‘embark-prompter’ to ‘embark-completing-read-prompter’. + + On the other hand, you may wish to continue using key bindings for +the actions you perform most often, and to use completion only to +explore what further actions are available or when you’ve forgotten a +key binding. In that case, you may prefer to use the minimal indicator, +which does not pop-up an ‘*Embark Actions*’ buffer at all, and to use +the ‘embark-help-key’ whenever you need help. This unobtrusive setup is +achieved with the following configuration: + + (setq embark-indicators + '(embark-minimal-indicator ; default is embark-mixed-indicator + embark-highlight-indicator + embark-isearch-highlight-indicator)) + + Vertico (https://github.com/minad/vertico) users may wish to +configure a grid display for the actions and key-bindings, reminiscent +of the popular package which-key +(https://github.com/justbur/emacs-which-key), but, of course, enhanced +by the use of completion to narrow the list of commands. In order to +get the grid display, put the following in your Vertico configuration: + + (add-to-list 'vertico-multiform-categories '(embark-keybinding grid)) + (vertico-multiform-mode) + + This will make the available keys be shown in a compact grid like in +‘which-key’. The ‘vertico-multiform-mode’ also enables keys such as +‘M-V’, ‘M-G’, ‘M-B’, and ‘M-U’ for manually switching between layouts in +Vertico buffers. + +* Menu: + +* Selecting commands via completion outside of Embark:: + + +File: docytLD1w.info, Node: Selecting commands via completion outside of Embark, Up: Selecting commands via completions instead of key bindings + +3.2.1 Selecting commands via completion outside of Embark +--------------------------------------------------------- + +If you like this completion interface for exploring key bindings for +Embark actions, you may want to use it elsewhere in Emacs. You can use +Embark’s completion-based command prompter to list: + + • key bindings under a prefix, + • local key bindings, or + • all key bindings. + + To use it for key bindings under a prefix (you can use this to +replace the ‘which-key’ package, for example), use this configuration: + + (setq prefix-help-command #'embark-prefix-help-command) + + Now, when you have started on a prefix sequence such as ‘C-x’ or +‘C-c’, pressing ‘C-h’ will bring up the Embark version of the built-in +‘prefix-help-command’, which will list the keys under that prefix and +their bindings, and lets you select the one you wanted with completion, +or by key binding if you press ‘embark-keymap-prompter-key’. + + To list local or global key bindings, use the command +‘embark-bindings’. You can bind that to ‘C-h b’, which is the default +key binding for the built-in ‘describe-bindings’ command, which this +command can replace. By default, ‘embark-bindings’ lists local key +bindings, typically those bound in the major mode keymap; to get global +bindings as well, call it with a ‘C-u’ prefix argument. + + +File: docytLD1w.info, Node: Quitting the minibuffer after an action, Next: Running some setup after injecting the target, Prev: Selecting commands via completions instead of key bindings, Up: Advanced configuration + +3.3 Quitting the minibuffer after an action +=========================================== + +By default, if you call ‘embark-act’ from the minibuffer it quits the +minibuffer after performing the action. You can change this by setting +the user option ‘embark-quit-after-action’ to ‘nil’. Having +‘embark-act’ _not_ quit the minibuffer can be useful to turn commands +into little “thing managers”. For example, you can use ‘find-file’ as a +little file manager or ‘describe-package’ as a little package manager: +you can run those commands, perform a series of actions, and then quit +the command. + + If you want to control the quitting behavior in a fine-grained manner +depending on the action, you can set ‘embark-quit-after-action’ to an +alist, associating commands to either ‘t’ for quitting or ‘nil’ for not +quitting. When using an alist, you can use the special key ‘t’ to +specify the default behavior. For example, to specify that by default +actions should not quit the minibuffer but that using ‘kill-buffer’ as +an action should quit, you can use the following configuration: + + (setq embark-quit-after-action '((kill-buffer . t) (t . nil))) + + The variable ‘embark-quit-after-action’ only specifies a default, +that is, it only controls whether or not ‘embark-act’ quits the +minibuffer when you call it without a prefix argument, and you can +select the opposite behavior to what the variable says by calling +‘embark-act’ with ‘C-u’. Also note that both the variable +‘embark-quit-after-action’ and ‘C-u’ have no effect when you call +‘embark-act’ outside the minibuffer. + + If you find yourself using the quitting and non-quitting variants of +‘embark-act’ about equally often, independently of the action, you may +prefer to simply have separate commands for them instead of a single +command that you call with ‘C-u’ half the time. You could, for example, +keep the default exiting behavior of ‘embark-act’ and define a +non-quitting version as follows: + + (defun embark-act-noquit () + "Run action but don't quit the minibuffer afterwards." + (interactive) + (let ((embark-quit-after-action nil)) + (embark-act))) + + +File: docytLD1w.info, Node: Running some setup after injecting the target, Next: Running hooks before after or around an action, Prev: Quitting the minibuffer after an action, Up: Advanced configuration + +3.4 Running some setup after injecting the target +================================================= + +You can customize what happens after the target is inserted at the +minibuffer prompt of an action. There are +‘embark-target-injection-hooks’, that are run by default after injecting +the target into the minibuffer. The variable +‘embark-target-injection-hooks’ is an alist associating commands to +their setup hooks. There are two special keys: if no setup hook is +specified for a given action, the hook associated to ‘t’ is run; and the +hook associated to ‘:always’ is run regardless of the action. (This +variable used to have the less explicit name of +‘embark-setup-action-hooks’, so please update your configuration.) + + For example, consider using ‘shell-command’ as an action during file +completion. It would be useful to insert a space before the target file +name and to leave the point at the beginning, so you can immediately +type the shell command to run on that file. That’s why in Embark’s +default configuration there is an entry in +‘embark-target-injection-hooks’ associating ‘shell-command’ to a hook +that includes ‘embark--shell-prep’, a simple helper function that quotes +all the spaces in the file name, inserts an extra space at the beginning +of the line and leaves point to the left of it. + + Now, the preparation that ‘embark--shell-prep’ does would be useless +if Embark did what it normally does after it inserts the target of the +action at the minibuffer prompt, which is to “press ‘RET’” for you, +accepting the target as is; if Embark did that for ‘shell-command’ you +wouldn’t get a chance to type in the command to execute! That is why in +Embark’s default configuration the entry for ‘shell-command’ in +‘embark-target-injection-hooks’ also contains the function +‘embark--allow-edit’. + + Embark used to have a dedicated variable ‘embark-allow-edit-actions’ +to which you could add commands for which Embark should forgo pressing +‘RET’ for you after inserting the target. Since its effect can also be +achieved via the general ‘embark-target-injection-hooks’ mechanism, that +variable has been removed to simplify Embark. Be sure to update your +configuration; if you had something like: + + (add-to-list 'embark-allow-edit-actions 'my-command) + + you should replace it with: + + (push 'embark--allow-edit + (alist-get 'my-command embark-target-injection-hooks)) + + Also note that while you could abuse ‘embark--allow-edit’ so that you +have to confirm “dangerous” actions such as ‘delete-file’, it is better +to implement confirmation by adding the ‘embark--confirm’ function to +the appropriate entry of a different hook alist, namely, +‘embark-pre-action-hooks’. + + Besides ‘embark--allow-edit’, Embark comes with another function that +is of general utility in action setup hooks: ‘embark--ignore-target’. +Use it for commands that do prompt you in the minibuffer but for which +inserting the target would be inappropriate. This is not a common +situation but does occasionally arise. For example it is used by +default for ‘shell-command-on-region’: that command is used as an action +for region targets, and it prompts you for a shell command; you +typically do _not_ want the target, that is the contents of the region, +to be entered at that prompt! + + +File: docytLD1w.info, Node: Running hooks before after or around an action, Next: Creating your own keymaps, Prev: Running some setup after injecting the target, Up: Advanced configuration + +3.5 Running hooks before, after or around an action +=================================================== + +Embark has three variables, ‘embark-pre-action-hooks’, +‘embark-post-action-hooks’ and ‘embark-around-action-hooks’, which are +alists associating commands to hooks that should run before or after or +as around advice for the command when used as an action. As with +‘embark-target-injection-hooks’, there are two special keys for the +alists: ‘t’ designates the default hook to run when no specific hook is +specified for a command; and the hook associated to ‘:always’ runs +regardless. + + The default values of those variables are fairly extensive, adding +creature comforts to make running actions a smooth experience. Embark +comes with several functions intended to be added to these hooks, and +used in the default values of ‘embark-pre-action-hooks’, +‘embark-post-action-hooks’ and ‘embark-around-action-hooks’. + + For pre-action hooks: + +‘embark--confirm’ + Prompt the user for confirmation before executing the action. This + is used be default for commands deemed “dangerous”, or, more + accurately, hard to undo, such as ‘delete-file’ and ‘kill-buffer’. + +‘embark--unmark-target’ + Unmark the active region. Use this for commands you want to act on + the region contents but without the region being active. The + default configuration uses this function as a pre-action hook for + ‘occur’ and ‘query-replace’, for example, so that you can use them + as actions with region targets to search the whole buffer for the + text contained in the region. Without this pre-action hook using + ‘occur’ as an action for a region target would be pointless: it + would search for the the region contents _in the region_, + (typically, due to the details of regexps) finding only one match! + +‘embark--beginning-of-target’ + Move to the beginning of the target (for targets that report + bounds). This is used by default for backward motion commands such + as ‘backward-sexp’, so that they don’t accidentally leave you on + the current target. + +‘embark--end-of-target’ + Move to the end of the target. This is used similarly to the + previous function, but also for commands that act on the last + s-expression like ‘eval-last-sexp’. This allow you to act on an + s-expression from anywhere inside it and still use ‘eval-last-sexp’ + as an action. + +‘embark--xref-push-markers’ + Push the current location on the xref marker stack. Use this for + commands that take you somewhere and for which you’d like to be + able to come back to where you were using ‘xref-pop-marker-stack’. + This is used by default for ‘find-library’. + + For post-action hooks: + +‘embark--restart’ + Restart the command currently prompting in the minibuffer, so that + the list of completion candidates is updated. This is useful as a + post action hook for commands that delete or rename a completion + candidate; for example the default value of + ‘embark-post-action-hooks’ uses it for ‘delete-file’, + ‘kill-buffer’, ‘rename-file’, ‘rename-buffer’, etc. + + For around-action hooks: + +‘embark--mark-target’ + Save existing mark and point location, mark the target and run the + action. Most targets at point outside the minibuffer report which + region of the buffer they correspond to (this is the information + used by ‘embark-highlight-indicator’ to know what portion of the + buffer to highlight); this function marks that region. It is + useful as an around action hook for commands that expect a region + to be marked, for example, it is used by default for + ‘indent-region’ so that it works on s-expression targets, or for + ‘fill-region’ so that it works on paragraph targets. + +‘embark--cd’ + Run the action with ‘default-directory’ set to the directory + associated to the current target. The target should be of type + ‘file’, ‘buffer’, ‘bookmark’ or ‘library’, and the associated + directory is what you’d expect in each case. + +‘embark--narrow-to-target’ + Run the action with buffer narrowed to current target. Use this as + an around hook to localize the effect of actions that don’t already + work on just the region. In the default configuration it is used + for ‘repunctuate-sentences’. + +‘embark--save-excursion’ + Run the action restoring point at the end. The current default + configuration doesn’t use this but it is available for users. + + +File: docytLD1w.info, Node: Creating your own keymaps, Next: Defining actions for new categories of targets, Prev: Running hooks before after or around an action, Up: Advanced configuration + +3.6 Creating your own keymaps +============================= + +All internal keymaps are defined with the standard helper macro +‘defvar-keymap’. For example a simple version of the file action keymap +could be defined as follows: + + (defvar-keymap embark-file-map + :doc "Example keymap with a few file actions" + :parent embark-general-map + "d" #'delete-file + "r" #'rename-file + "c" #'copy-file) + + These action keymaps are perfectly normal Emacs keymaps. You may +want to inherit from the ‘embark-general-map’ if you want to access the +default Embark actions. Note that ‘embark-collect’ and ‘embark-export’ +are also made available via ‘embark-general-map’. + + +File: docytLD1w.info, Node: Defining actions for new categories of targets, Prev: Creating your own keymaps, Up: Advanced configuration + +3.7 Defining actions for new categories of targets +================================================== + +It is easy to configure Embark to provide actions for new types of +targets, either in the minibuffer or outside it. I present below two +very detailed examples of how to do this. At several points I’ll +explain more than one way to proceed, typically with the easiest option +first. I include the alternative options since there will be similar +situations where the easiest option is not available. + +* Menu: + +* New minibuffer target example - tab-bar tabs:: +* New target example in regular buffers - short Wikipedia links:: + + +File: docytLD1w.info, Node: New minibuffer target example - tab-bar tabs, Next: New target example in regular buffers - short Wikipedia links, Up: Defining actions for new categories of targets + +3.7.1 New minibuffer target example - tab-bar tabs +-------------------------------------------------- + +As an example, take the new tab bars +(https://www.gnu.org/software/emacs/manual/html_node/emacs/Tab-Bars.html) +from Emacs 27. I’ll explain how to configure Embark to offer +tab-specific actions when you use the tab-bar-mode commands that mention +tabs by name. The configuration explained here is now built-in to +Embark (and Marginalia), but it’s still a good self-contained example. +In order to setup up tab actions you would need to: (1) make sure Embark +knows those commands deal with tabs, (2) define a keymap for tab actions +and configure Embark so it knows that’s the keymap you want. + + 1. Telling Embark about commands that prompt for tabs by name + + For step (1), it would be great if the ‘tab-bar-mode’ commands + reported the completion category ‘tab’ when asking you for a tab + with completion. (All built-in Emacs commands that prompt for file + names, for example, do have metadata indicating that they want a + ‘file’.) They do not, unfortunately, and I will describe a couple + of ways to deal with this. + + Maybe the easiest thing is to configure Marginalia + (https://github.com/minad/marginalia) to enhance those commands. + All of the ‘tab-bar-*-tab-by-name’ commands have the words “tab by + name” in the minibuffer prompt, so you can use: + + (add-to-list 'marginalia-prompt-categories '("tab by name" . tab)) + + That’s it! But in case you are ever in a situation where you don’t + already have commands that prompt for the targets you want, I’ll + describe how writing your own command with appropriate ‘category’ + metadata looks: + + (defun my-select-tab-by-name (tab) + (interactive + (list + (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) + (tab-bar-tabs)) + (user-error "No tabs found")))) + (completing-read + "Tabs: " + (lambda (string predicate action) + (if (eq action 'metadata) + '(metadata (category . tab)) + (complete-with-action + action tab-list string predicate))))))) + (tab-bar-select-tab-by-name tab)) + + As you can see, the built-in support for setting the category + meta-datum is not very easy to use or pretty to look at. To help + with this I recommend the ‘consult--read’ function from the + excellent Consult (https://github.com/minad/consult/) package. + With that function we can rewrite the command as follows: + + (defun my-select-tab-by-name (tab) + (interactive + (list + (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) + (tab-bar-tabs)) + (user-error "No tabs found")))) + (consult--read tab-list + :prompt "Tabs: " + :category 'tab)))) + (tab-bar-select-tab-by-name tab)) + + Much nicer! No matter how you define the ‘my-select-tab-by-name’ + command, the first approach with Marginalia and prompt detection + has the following advantages: you get the ‘tab’ category for all + the ‘tab-bar-*-bar-by-name’ commands at once, also, you enhance + built-in commands, instead of defining new ones. + + 2. Defining and configuring a keymap for tab actions + + Let’s say we want to offer select, rename and close actions for + tabs (in addition to Embark general actions, such as saving the tab + name to the kill-ring, which you get for free). Then this will do: + + (defvar-keymap embark-tab-actions + :doc "Keymap for actions for tab-bar tabs (when mentioned by name)." + :parent embark-general-map + "s" #'tab-bar-select-tab-by-name + "r" #'tab-bar-rename-tab-by-name + "k" #'tab-bar-close-tab-by-name) + + (add-to-list 'embark-keymap-alist '(tab . embark-tab-actions)) + + What if after using this for a while you feel closing the tab + without confirmation is dangerous? You have a couple of options: + + 1. You can keep using the ‘tab-bar-close-tab-by-name’ command, + but have Embark ask you for confirmation: + (push #'embark--confirm + (alist-get 'tab-bar-close-tab-by-name + embark-pre-action-hooks)) + + 2. You can write your own command that prompts for confirmation + and use that instead of ‘tab-bar-close-tab-by-name’ in the + above keymap: + (defun my-confirm-close-tab-by-name (tab) + (interactive "sTab to close: ") + (when (y-or-n-p (format "Close tab '%s'? " tab)) + (tab-bar-close-tab-by-name tab))) + + Notice that this is a command you can also use directly from + ‘M-x’ independently of Embark. Using it from ‘M-x’ leaves + something to be desired, though, since you don’t get + completion for the tab names. You can fix this if you wish as + described in the previous section. + + +File: docytLD1w.info, Node: New target example in regular buffers - short Wikipedia links, Prev: New minibuffer target example - tab-bar tabs, Up: Defining actions for new categories of targets + +3.7.2 New target example in regular buffers - short Wikipedia links +------------------------------------------------------------------- + +Say you want to teach Embark to treat text of the form +‘wikipedia:Garry_Kasparov’ in any regular buffer as a link to Wikipedia, +with actions to open the Wikipedia page in eww or an external browser or +to save the URL of the page in the kill-ring. We can take advantage of +the actions that Embark has preconfigured for URLs, so all we need to do +is teach Embark that ‘wikipedia:Garry_Kasparov’ stands for the URL +‘https://en.wikipedia.org/wiki/Garry_Kasparov’. + + You can be as fancy as you want with the recognized syntax. Here, to +keep the example simple, I’ll assume the link matches the regexp +‘wikipedia:[[:alnum:]_]+’. We will write a function that looks for a +match surrounding point, and returns a dotted list of the form ‘'(url +URL-OF-THE-PAGE START . END)’ where ‘START’ and ‘END’ are the buffer +positions bounding the target, and are used by Embark to highlight it if +you have ‘embark-highlight-indicator’ included in the list +‘embark-indicators’. (There are a couple of other options for the +return value of a target finder: the bounding positions are optional and +a single target finder is allowed to return multiple targets; see the +documentation for ‘embark-target-finders’ for details.) + + (defun my-short-wikipedia-link () + "Target a link at point of the form wikipedia:Page_Name." + (save-excursion + (let* ((start (progn (skip-chars-backward "[:alnum:]_:") (point))) + (end (progn (skip-chars-forward "[:alnum:]_:") (point))) + (str (buffer-substring-no-properties start end))) + (save-match-data + (when (string-match "wikipedia:\\([[:alnum:]_]+\\)" str) + `(url + ,(format "https://en.wikipedia.org/wiki/%s" + (match-string 1 str)) + ,start . ,end)))))) + + (add-to-list 'embark-target-finders 'my-short-wikipedia-link) + + +File: docytLD1w.info, Node: How does Embark call the actions?, Next: Embark Marginalia and Consult, Prev: Advanced configuration, Up: Top + +4 How does Embark call the actions? +*********************************** + +Embark actions are normal Emacs commands, that is, functions with an +interactive specification. In order to execute an action, Embark calls +the command with ‘call-interactively’, so the command reads user input +exactly as if run directly by the user. For example the command may +open a minibuffer and read a string (‘read-from-minibuffer’) or open a +completion interface (‘completing-read’). If this happens, Embark takes +the target string and inserts it automatically into the minibuffer, +simulating user input this way. After inserting the string, Embark +exits the minibuffer, submitting the input. (The immediate minibuffer +exit can be disabled for specific actions in order to allow editing the +input; this is done by adding the ‘embark--allow-edit’ function to the +appropriate entry of ‘embark-target-injection-hooks’). Embark inserts +the target string at the first minibuffer opened by the action command, +and if the command happens to prompt the user for input more than once, +the user still interacts with the second and further prompts in the +normal fashion. Note that if a command does not prompt the user for +input in the minibuffer, Embark still allows you to use it as an action, +but of course, never inserts the target anywhere. (There are plenty of +examples in the default configuration of commands that do not prompt the +user bound to keys in the action maps, most of the region actions, for +instance.) + + This is how Embark manages to reuse normal commands as actions. The +mechanism allows you to use as Embark actions commands that were not +written with Embark in mind (and indeed almost all actions that are +bound by default in Embark’s action keymaps are standard Emacs +commands). It also allows you to write new custom actions in such a way +that they are useful even without Embark. + + Staring from version 28.1, Emacs has a variable +‘y-or-n-p-use-read-key’, which when set to ‘t’ causes ‘y-or-n-p’ to use +‘read-key’ instead of ‘read-from-minibuffer’. Setting +‘y-or-n-p-use-read-key’ to ‘t’ is recommended for Embark users because +it keeps Embark from attempting to insert the target at a ‘y-or-n-p’ +prompt, which would almost never be sensible. Also consider this as a +warning to structure your own action commands so that if they use +‘y-or-n-p’, they do so only after the prompting for the target. + + Here is a simple example illustrating the various ways of reading +input from the user mentioned above. Bind the following commands to the +‘embark-symbol-map’ to be used as actions, then put the point on some +symbol and run them with ‘embark-act’: + + (defun example-action-command1 () + (interactive) + (message "The input was `%s'." (read-from-minibuffer "Input: "))) + + (defun example-action-command2 (arg input1 input2) + (interactive "P\nsInput 1: \nsInput 2: ") + (message "The first input %swas `%s', and the second was `%s'." + (if arg "truly " "") + input1 + input2)) + + (defun example-action-command3 () + (interactive) + (message "Your selection was `%s'." + (completing-read "Select: " '("E" "M" "B" "A" "R" "K")))) + + (defun example-action-command4 () + (interactive) + (message "I don't prompt you for input and thus ignore the target!")) + + (keymap-set embark-symbol-map "X 1" #'example-action-command1) + (keymap-set embark-symbol-map "X 2" #'example-action-command2) + (keymap-set embark-symbol-map "X 3" #'example-action-command3) + (keymap-set embark-symbol-map "X 4" #'example-action-command4) + + Also note that if you are using the key bindings to call actions, you +can pass prefix arguments to actions in the normal way. For example, +you can use ‘C-u X2’ with the above demonstration actions to make the +message printed by ‘example-action-command2’ more emphatic. This +ability to pass prefix arguments to actions is useful for some actions +in the default configuration, such as ‘embark-shell-command-on-buffer’. + +* Menu: + +* Non-interactive functions as actions:: + + +File: docytLD1w.info, Node: Non-interactive functions as actions, Up: How does Embark call the actions? + +4.1 Non-interactive functions as actions +======================================== + +Alternatively, Embark does support one other type of action: a +non-interactive function of a single argument. The target is passed as +argument to the function. For example: + + (defun example-action-function (target) + (message "The target was `%s'." target)) + + (keymap-set embark-symbol-map "X 4" #'example-action-function) + + Note that normally binding non-interactive functions in a keymap is +useless, since when attempting to run them using the key binding you get +an error message similar to “Wrong type argument: commandp, +example-action-function”. In general it is more flexible to write any +new Embark actions as commands, that is, as interactive functions, +because that way you can also run them directly, without Embark. But +there are a couple of reasons to use non-interactive functions as +actions: + + 1. You may already have the function lying around, and it is + convenient to simply reuse it. + + 2. For command actions the targets can only be simple string, with no + text properties. For certain advanced uses you may want the action + to receive a string _with_ some text properties, or even a + non-string target. + + +File: docytLD1w.info, Node: Embark Marginalia and Consult, Next: Related Packages, Prev: How does Embark call the actions?, Up: Top + +5 Embark, Marginalia and Consult +******************************** + +Embark cooperates well with the Marginalia +(https://github.com/minad/marginalia) and Consult +(https://github.com/minad/consult) packages. Neither of those packages +is a dependency of Embark, but both are highly recommended companions to +Embark, for opposite reasons: Marginalia greatly enhances Embark’s +usefulness, while Embark can help enhance Consult. + + In the remainder of this section I’ll explain what exactly Marginalia +does for Embark, and what Embark can do for Consult. + +* Menu: + +* Marginalia:: +* Consult:: + + +File: docytLD1w.info, Node: Marginalia, Next: Consult, Up: Embark Marginalia and Consult + +5.1 Marginalia +============== + +Embark comes with actions for symbols (commands, functions, variables +with actions such as finding the definition, looking up the +documentation, evaluating, etc.) in the ‘embark-symbol-map’ keymap, and +for packages (actions like install, delete, browse url, etc.) in the +‘embark-package-keymap’. + + Unfortunately Embark does not automatically offers you these keymaps +when relevant, because many built-in Emacs commands don’t report +accurate category metadata. For example, a command like +‘describe-package’, which reads a package name from the minibuffer, does +not have metadata indicating this fact. + + In an earlier Embark version, there were functions to supply this +missing metadata, but they have been moved to Marginalia, which augments +many Emacs command to report accurate category metadata. Simply +activating ‘marginalia-mode’ allows Embark to offer you the package and +symbol actions when appropriate again. Candidate annotations in the +Embark collect buffer are also provided by the Marginalia package: + + • If you install Marginalia and activate ‘marginalia-mode’, Embark + Collect buffers will use the Marginalia annotations automatically. + + • If you don’t install Marginalia, you will see only the annotations + that come with Emacs (such as key bindings in ‘M-x’, or the unicode + characters in ‘C-x 8 RET’). + + +File: docytLD1w.info, Node: Consult, Prev: Marginalia, Up: Embark Marginalia and Consult + +5.2 Consult +=========== + +The excellent Consult package provides many commands that use minibuffer +completion, via the ‘completing-read’ function; plenty of its commands +can be considered enhanced versions of built-in Emacs commands, and some +are completely new functionality. One common enhancement provided in +all commands for which it makes sense is preview functionality, for +example ‘consult-buffer’ will show you a quick preview of a buffer +before you actually switch to it. + + If you use both Consult and Embark you should install the +‘embark-consult’ package which provides integration between the two. It +provides exporters for several Consult commands and also tweaks the +behavior of many Consult commands when used as actions with ‘embark-act’ +in subtle ways that you may not even notice, but make for a smoother +experience. You need only install it to get these benefits: Embark will +automatically load it after Consult if found. + + The ‘embark-consult’ package provides the following exporters: + + • You can use ‘embark-export’ from ‘consult-line’, ‘consult-outline’, + or ‘consult-mark’ to obtain an ‘occur-mode’ buffer. As with the + built-in ‘occur’ command you use that buffer to jump to a match and + after that, you can then use ‘next-error’ and ‘previous-error’ to + navigate to other matches. You can also press ‘e’ to activate + ‘occur-edit-mode’ and edit the matches in place! + + • You can export from any of the Consult asynchronous search + commands, ‘consult-grep’, ‘consult-git-grep’, or ‘consult-ripgrep’ + to get a ‘grep-mode’ buffer. Here too you can use ‘next-error’ and + ‘previous-error’ to navigate among matches, and, if you install the + wgrep + (http://github.com/mhayashi1120/Emacs-wgrep/raw/master/wgrep.el) + package, you can use it to edit the matches in place. + + In both cases, pressing ‘g’ will rerun the Consult command you had +exported from and re-enter the input you had typed (which is similar to +reverting but a little more flexible). You can then proceed to +re-export if that’s what you want, but you can also edit the input +changing the search terms or simply cancel if you see you are done with +that search. + + The ‘embark-consult’ also contains some candidates collectors that +allow you to run ‘embark-live’ to get a live-updating table of contents +for your buffer: + + • ‘embark-consult-outline-candidates’ produces the outline headings + of the current buffer, using ‘consult-outline’. + • ‘embark-consult-imenu-candidates’ produces the imenu items of the + current buffer, using ‘consult-imenu’. + • ‘embark-consult-imenu-or-outline-candidates’ is a simple + combination of the two previous functions: it produces imenu items + in buffers deriving from ‘prog-mode’ and otherwise outline + headings. + + The way to configure ‘embark-live’ (or ‘embark-collect’ and +‘embark-export’ for that matter) to use one of these function is to add +it at the end of the ‘embark-candidate-collectors’ list. The +‘embark-consult’ package by default adds the last one, which seems to be +the most sensible default. + + Besides those exporters and candidate collectors, the +‘embark-consult’ package provides many subtle tweaks and small +integrations between Embark and Consult. Some examples are: + + • When used as actions, the asynchronous search commands will search + only the files associated to the targets: if the targets _are_ + files, it searches those files; for buffers it will search either + the associated file if there is one, else all files in the buffer’s + ‘default-directory’; for bookmarks it will search the file they + point to, same for Emacs Lisp libraries. This is particularly + powerful when using ‘embark-act-all’ to act on multiple files at + once, for example you can use ‘consult-find’ to search among file + _names_ and then ‘embark-act-all’ and ‘consult-grep’ to search + within the matching files. + + • For all other target types, those that do not have a sensible + notion of associated file, a Consult search command + (asynchronous or not) will search for the text of the target + but leave the minibuffer open so you can interact with the + Consult command. + + • ‘consult-imenu’ will search for the target and take you directly to + the location if it matches a unique imenu entry, otherwise it will + leave the minibuffer open so you can navigate among the matches. + + +File: docytLD1w.info, Node: Related Packages, Next: Resources, Prev: Embark Marginalia and Consult, Up: Top + +6 Related Packages +****************** + +There are several packages that offer functionality similar to Embark’s. + +Acting on minibuffer completion candidates + The popular Ivy and Helm packages have support for acting on the + completion candidates of commands written using their APIs, and + there is an extensive ecosystem of packages meant for Helm and for + Ivy (the Ivy ones usually have “counsel” in the name) providing + commands and appropriate actions. +Acting on things at point + The built-in ‘context-menu-mode’ provides a mouse-driven + context-sensitive configurable menu. The ‘do-at-point’ package by + Philip Kaludercic (available on GNU ELPA), on the other hand is + keyboard-driven. +Collecting completion candidates into a buffer + The Ivy package has the command ‘ivy-occur’ which is similar to + ‘embark-collect’. As with Ivy actions, ‘ivy-occur’ only works for + commands written using the Ivy API. + + +File: docytLD1w.info, Node: Resources, Next: Contributions, Prev: Related Packages, Up: Top + +7 Resources +*********** + +If you want to learn more about how others have used Embark here are +some links to read: + + • Fifteen ways to use Embark + (https://karthinks.com/software/fifteen-ways-to-use-embark/), a + blog post by Karthik Chikmagalur. + • Protesilaos Stavrou’s dotemacs (https://protesilaos.com/dotemacs/), + look for the section called “Extended minibuffer actions and more + (embark.el and prot-embark.el)” + + And some videos to watch: + + • Embark and my extras + (https://protesilaos.com/codelog/2021-01-09-emacs-embark-extras/) + by Protesilaos Stavrou. + • Embark – Key features and tweaks (https://youtu.be/qpoQiiinCtY) by + Raoul Comninos on the Emacs-Elements YouTube channel. + • Livestreamed: Adding an Embark context action to send a stream + message (https://youtu.be/WsxXr1ncukY) by Sacha Chua. + • System Crafters Live! - The Many Uses of Embark + (https://youtu.be/qk2Is_sC8Lk) by David Wilson. + • Marginalia, Consult and Embark by Mike Zamansky. + + +File: docytLD1w.info, Node: Contributions, Next: Acknowledgments, Prev: Resources, Up: Top + +8 Contributions +*************** + +Contributions to Embark are very welcome. There is a wish list +(https://github.com/oantolin/embark/issues/95) for actions, target +finders, candidate collectors and exporters. For other ideas you have +for Embark, feel free to open an issue on the issue tracker +(https://github.com/oantolin/embark/issues). Any neat configuration +tricks you find might be a good fit for the wiki +(https://github.com/oantolin/embark/wiki). + + Code contributions are very welcome too, but since Embark is now on +GNU ELPA, copyright assignment to the FSF is required before you can +contribute code. + + +File: docytLD1w.info, Node: Acknowledgments, Prev: Contributions, Up: Top + +9 Acknowledgments +***************** + +While I, Omar Antolín Camarena, have written most of the Embark code and +remain very stubborn about some of the design decisions, Embark has +received substantial help from a number of other people which this +document has neglected to mention for far too long. In particular, +Daniel Mendler has been absolutely invaluable, implementing several +important features, and providing a lot of useful advice. + + Code contributions: + + • Daniel Mendler (https://github.com/minad) + • Clemens Radermacher (https://github.com/clemera/) + • José Antonio Ortega Ruiz (https://codeberg.org/jao/) + • Itai Y. Efrat (https://github.com/iyefrat) + • a13 (https://github.com/a13) + • jakanakaevangeli (https://github.com/jakanakaevangeli) + • mihakam (https://github.com/mihakam) + • Brian Leung (https://github.com/leungbk) + • Karthik Chikmagalur (https://github.com/karthink) + • Roshan Shariff (https://github.com/roshanshariff) + • condy0919 (https://github.com/condy0919) + • Damien Cassou (https://github.com/DamienCassou) + • JimDBh (https://github.com/JimDBh) + + Advice and useful discussions: + + • Daniel Mendler (https://github.com/minad) + • Protesilaos Stavrou (https://gitlab.com/protesilaos/) + • Clemens Radermacher (https://github.com/clemera/) + • Howard Melman (https://github.com/hmelman/) + • Augusto Stoffel (https://github.com/astoff) + • Bruce d’Arcus (https://github.com/bdarcus) + • JD Smith (https://github.com/jdtsmith) + • Karthik Chikmagalur (https://github.com/karthink) + • jakanakaevangeli (https://github.com/jakanakaevangeli) + • Itai Y. Efrat (https://github.com/iyefrat) + • Mohsin Kaleem (https://github.com/mohkale) + + + +Tag Table: +Node: Top223 +Node: Overview1848 +Node: Acting on targets3157 +Node: The default action on a target8702 +Node: Working with sets of possible targets10612 +Node: Selecting some targets to make an ad hoc candidate set14891 +Node: embark-live a live-updating variant of embark-collect18347 +Node: Switching to a different command without losing what you've typed20045 +Node: Quick start22622 +Node: Advanced configuration27552 +Node: Showing information about available targets and actions28137 +Node: Selecting commands via completions instead of key bindings30959 +Node: Selecting commands via completion outside of Embark34047 +Node: Quitting the minibuffer after an action35582 +Node: Running some setup after injecting the target38038 +Node: Running hooks before after or around an action41656 +Node: Creating your own keymaps46535 +Node: Defining actions for new categories of targets47442 +Node: New minibuffer target example - tab-bar tabs48214 +Ref: Telling Embark about commands that prompt for tabs by name49120 +Ref: Defining and configuring a keymap for tab actions51983 +Node: New target example in regular buffers - short Wikipedia links53774 +Node: How does Embark call the actions?56037 +Node: Non-interactive functions as actions60380 +Node: Embark Marginalia and Consult61737 +Node: Marginalia62468 +Node: Consult63975 +Node: Related Packages68737 +Node: Resources69834 +Node: Contributions70968 +Node: Acknowledgments71681 + +End Tag Table + + +Local Variables: +coding: utf-8 +End: blob - /dev/null blob + e510368e691d46dab88442e0669de8afc9768eeb (mode 644) --- /dev/null +++ elpa/embark-1.1/embark.texi @@ -0,0 +1,1563 @@ +\input texinfo @c -*- texinfo -*- +@c %**start of header +@setfilename embark.info +@settitle Embark: Emacs Mini-Buffer Actions Rooted in Keymaps +@documentencoding UTF-8 +@documentlanguage en +@c %**end of header + +@dircategory Emacs misc features +@direntry +* Embark: (embark). Emacs Mini-Buffer Actions Rooted in Keymaps. +@end direntry + +@finalout +@titlepage +@title Embark: Emacs Mini-Buffer Actions Rooted in Keymaps +@author Omar Antolín Camarena +@end titlepage + +@contents + +@ifnottex +@node Top +@top Embark: Emacs Mini-Buffer Actions Rooted in Keymaps +@end ifnottex + +@menu +* Overview:: +* Quick start:: +* Advanced configuration:: +* How does Embark call the actions?:: +* Embark, Marginalia and Consult: Embark Marginalia and Consult. +* Related Packages:: +* Resources:: +* Contributions:: +* Acknowledgments:: + +@detailmenu +--- The Detailed Node Listing --- + +Overview + +* Acting on targets:: +* The default action on a target:: +* Working with sets of possible targets:: +* Switching to a different command without losing what you've typed:: + +Working with sets of possible targets + +* Selecting some targets to make an ad hoc candidate set:: +* @samp{embark-live} a live-updating variant of @samp{embark-collect}:: + +Advanced configuration + +* Showing information about available targets and actions:: +* Selecting commands via completions instead of key bindings:: +* Quitting the minibuffer after an action:: +* Running some setup after injecting the target:: +* Running hooks before, after or around an action: Running hooks before after or around an action. +* Creating your own keymaps:: +* Defining actions for new categories of targets:: + +Selecting commands via completions instead of key bindings + +* Selecting commands via completion outside of Embark:: + +Defining actions for new categories of targets + +* New minibuffer target example - tab-bar tabs:: +* New target example in regular buffers - short Wikipedia links:: + +How does Embark call the actions? + +* Non-interactive functions as actions:: + +Embark, Marginalia and Consult + +* Marginalia:: +* Consult:: + +@end detailmenu +@end menu + +@node Overview +@chapter Overview + +Embark makes it easy to choose a command to run based on what is near +point, both during a minibuffer completion session (in a way familiar +to Helm or Counsel users) and in normal buffers. Bind the command +@samp{embark-act} to a key and it acts like prefix-key for a keymap of +@emph{actions} (commands) relevant to the @emph{target} around point. With point on +an URL in a buffer you can open the URL in a browser or eww or +download the file it points to. If while switching buffers you spot an +old one, you can kill it right there and continue to select another. +Embark comes preconfigured with over a hundred actions for common +types of targets such as files, buffers, identifiers, s-expressions, +sentences; and it is easy to add more actions and more target types. +Embark can also collect all the candidates in a minibuffer to an +occur-like buffer or export them to a buffer in a major-mode specific +to the type of candidates, such as dired for a set of files, ibuffer +for a set of buffers, or customize for a set of variables. + +@menu +* Acting on targets:: +* The default action on a target:: +* Working with sets of possible targets:: +* Switching to a different command without losing what you've typed:: +@end menu + +@node Acting on targets +@section Acting on targets + +You can think of @samp{embark-act} as a keyboard-based version of a +right-click contextual menu. The @samp{embark-act} command (which you should +bind to a convenient key), acts as a prefix for a keymap offering you +relevant @emph{actions} to use on a @emph{target} determined by the context: + +@itemize +@item +In the minibuffer, the target is the current top completion +candidate. +@item +In the @samp{*Completions*} buffer the target is the completion at point. +@item +In a regular buffer, the target is the region if active, or else the +file, symbol, URL, s-expression or defun at point. +@end itemize + +Multiple targets can be present at the same location and you can cycle +between them by repeating the @samp{embark-act} key binding. The type of +actions offered depend on the type of the target. Here is a sample of +a few of the actions offered in the default configuration: + +@itemize +@item +For files you get offered actions like deleting, copying, +renaming, visiting in another window, running a shell command on the +file, etc. +@item +For buffers the actions include switching to or killing the buffer. +@item +For package names the actions include installing, removing or +visiting the homepage. +@item +For Emacs Lisp symbols the actions include finding the definition, +looking up documentation, evaluating (which for a variable +immediately shows the value, but for a function lets you pass it +some arguments first). There are some actions specific to variables, +such as setting the value directly or though the customize system, +and some actions specific to commands, such as binding it to a key. +@end itemize + +By default when you use @samp{embark-act} if you don't immediately select an +action, after a short delay Embark will pop up a buffer showing a list +of actions and their corresponding key bindings. If you are using +@samp{embark-act} outside the minibuffer, Embark will also highlight the +current target. These behaviors are configurable via the variable +@samp{embark-indicators}. Instead of selecting an action via its key binding, +you can select it by name with completion by typing @samp{C-h} after +@samp{embark-act}. + +Everything is easily configurable: determining the current target, +classifying it, and deciding which actions are offered for each type +in the classification. The above introduction just mentions part of +the default configuration. + +Configuring which actions are offered for a type is particularly easy +and requires no programming: the variable @samp{embark-keymap-alist} +associates target types with variables containing keymaps, and those +keymaps containing bindings for the actions. (To examine the available +categories and their associated keymaps, you can use @samp{C-h v +embark-keymap-alist} or customize that variable.) For example, in the +default configuration the type @samp{file} is associated with the symbol +@samp{embark-file-map}. That symbol names a keymap with single-letter key +bindings for common Emacs file commands, for instance @samp{c} is bound to +@samp{copy-file}. This means that if you are in the minibuffer after running +a command that prompts for a file, such as @samp{find-file} or @samp{rename-file}, +you can copy a file by running @samp{embark-act} and then pressing @samp{c}. + +These action keymaps are very convenient but not strictly necessary +when using @samp{embark-act}: you can use any command that reads from the +minibuffer as an action and the target of the action will be inserted +at the first minibuffer prompt. After running @samp{embark-act} all of your +key bindings and even @samp{execute-extended-command} can be used to run a +command. For example, if you want to replace all occurrences of the +symbol at point, just use @samp{M-%} as the action, there is no need to bind +@samp{query-replace} in one of Embark's keymaps. Also, those action keymaps +are normal Emacs keymaps and you should feel free to bind in them +whatever commands you find useful as actions and want to be available +through convenient bindings. + +The actions in @samp{embark-general-map} are available no matter what type +of completion you are in the middle of. By default this includes +bindings to save the current candidate in the kill ring and to insert +the current candidate in the previously selected buffer (the buffer +that was current when you executed a command that opened up the +minibuffer). + +Emacs's minibuffer completion system includes metadata indicating the +@emph{category} of what is being completed. For example, @samp{find-file}'s +metadata indicates a category of @samp{file} and @samp{switch-to-buffer}'s metadata +indicates a category of @samp{buffer}. Embark has the related notion of the +@emph{type} of a target for actions, and by default when category metadata +is present it is taken to be the type of minibuffer completion +candidates when used as targets. Emacs commands often do not set +useful category metadata so the @uref{https://github.com/minad/marginalia, Marginalia} package, which supplies +this missing metadata, is highly recommended for use with Embark. + +Embark's default configuration has actions for the following target +types: files, buffers, symbols, packages, URLs, bookmarks, and as a +somewhat special case, actions for when the region is active. You can +read about the @uref{https://github.com/oantolin/embark/wiki/Default-Actions, default actions and their key bindings} on the GitHub +project wiki. + +@node The default action on a target +@section The default action on a target + +Embark has a notion of default action for a target: + +@itemize +@item +If the target is a minibuffer completion candidate, then the default +action is whatever command opened the minibuffer in the first place. +For example if you run @samp{kill-buffer}, then the default action will be +to kill buffers. +@item +If the target comes from a regular buffer (i.e., not a minibuffer), +then the default action is whatever is bound to @samp{RET} in the keymap of +actions for that type of target. For example, in Embark's default +configuration for a URL found at point the default action is +@samp{browse-url}, because @samp{RET} is bound to @samp{browse-url} in the @samp{embark-url-map} +keymap. +@end itemize + +To run the default action you can press @samp{RET} after running @samp{embark-act}. +Note that if there are several different targets at a given location, +each has its own default action, so first cycle to the target you want +and then press @samp{RET} to run the corresponding default action. + +There is also @samp{embark-dwim} which runs the default action for the first +target found. It's pretty handy in non-minibuffer buffers: with +Embark's default configuration it will: + +@itemize +@item +Open the file at point. +@item +Open the URL at point in a web browser (using the @samp{browse-url} +command). +@item +Compose a new email to the email address at point. +@item +In an Emacs Lisp buffer, if point is on an opening parenthesis or +right after a closing one, it will evaluate the corresponding +expression. +@item +Go to the definition of an Emacs Lisp function, variable or macro at +point. +@item +Find the file corresponding to an Emacs Lisp library at point. +@end itemize + +@node Working with sets of possible targets +@section Working with sets of possible targets + +Besides acting individually on targets, Embark lets you work +collectively on a set of target @emph{candidates}. For example, while you are +in the minibuffer the candidates are simply the possible completions +of your input. Embark provides three main commands to work on candidate +sets: + +@itemize +@item +The @samp{embark-act-all} command runs the same action on each of the +current candidates. It is just like using @samp{embark-act} on each +candidate in turn. (Because you can easily act on many more +candidates than you meant to, by default Embark asks you to confirm +uses of @samp{embark-act-all}; you can turn this off by setting the user +option @samp{embark-confirm-act-all} to @samp{nil}.) + +@item +The @samp{embark-collect} command produces a buffer listing all the current +candidates, for you to peruse and run actions on at your leisure. +The candidates are displayed as a list showing additional +annotations. If any of the candidates contain newlines, then +horizontal lines are used to separate candidates. + +The Embark Collect buffer is somewhat ``dired-like'': you can select +and deselect candidates through @samp{embark-select} (available as an +action in @samp{embark-act}, bound to @samp{SPC}; but you could also give it a +global key binding). In an Embark Collect buffer @samp{embark-act} is bound +to @samp{a} and @samp{embark-act-all} is bound to @samp{A}; @samp{embark-act-all} will act on +all currently marked candidates if there any, and will act on all +candidates if none are marked. In particular, this means that @samp{a SPC} +will toggle whether the candidate at point is selected, and @samp{A SPC} +will select all candidates if none are selected, or deselect all +selected candidates if there are some. + +@item +The @samp{embark-export} command tries to open a buffer in an appropriate +major mode for the set of candidates. If the candidates are files +export produces a Dired buffer; if they are buffers, you get an +Ibuffer buffer; and if they are packages you get a buffer in +package menu mode. + +If you use the grepping commands from the @uref{https://github.com/minad/consult/, Consult} package, +@samp{consult-grep}, @samp{consult-git-grep} or @samp{consult-ripgrep}, then you should +install the @samp{embark-consult} package, which adds support for exporting a +list of grep results to an honest grep-mode buffer, on which you can +even use @uref{https://github.com/mhayashi1120/Emacs-wgrep, wgrep} if you wish. +@end itemize + +When in doubt choosing between exporting and collecting, a good rule +of thumb is to always prefer @samp{embark-export} since when an exporter to a +special major mode is available for a given type of target, it will be +more featureful than an Embark collect buffer, and if no such exporter +is configured the @samp{embark-export} command falls back to the generic +@samp{embark-collect}. + +These commands are always available as ``actions'' (although they do not +act on just the current target but on all candidates) for @samp{embark-act} +and are bound to @samp{A}, @samp{S} (for ``snapshot''), and @samp{E}, respectively, in +@samp{embark-general-map}. This means that you do not have to bind your own +key bindings for these (although you can, of course!), just a key +binding for @samp{embark-act}. + +In Embark Collect or Embark Export buffers that were obtained by +running @samp{embark-collect} or @samp{embark-export} from within a minibuffer +completion session, @samp{g} is bound to a command that restarts the +completion session, that is, the command that opened the minibuffer is +run again and the minibuffer contents restored. You can then interact +normally with the command, perhaps editing the minibuffer contents, +and, if you wish, you can rerun @samp{embark-collect} or @samp{embark-export} to get +an updated buffer. + +@menu +* Selecting some targets to make an ad hoc candidate set:: +* @samp{embark-live} a live-updating variant of @samp{embark-collect}:: +@end menu + +@node Selecting some targets to make an ad hoc candidate set +@subsection Selecting some targets to make an ad hoc candidate set + +The commands for working with sets of candidates just described, +namely @samp{embark-act-all}, @samp{embark-export} and @samp{embark-collect} by default +work with all candidates defined in the current context. For example, +in the minibuffer they operate on all currently completion candidates, +or in a dired buffer they work on all marked files (or all files if +none are marked). Embark also has a notion of @emph{selection}, where you can +accumulate an ad hoc list of targets for these commands to work on. + +The selection is controlled by using the @samp{embark-select} action, bound +to @samp{SPC} in @samp{embark-general-map} so that it is always available (you can +also give @samp{embark-select} a global key binding if you wish; when called +directly, not as an action for @samp{embark-act}, it will select the first +target at point). Calling this action on a target toggles its +membership in the current buffer's Embark selection; that is, it adds +it to selection if not selected and removes it from the selection if +it was selected. Whenever the selection for a buffer is non-empty, the +commands @samp{embark-act-all}, @samp{embark-export} and @samp{embark-collect} will act on +the selection. + +To deselect all selected targets, you can use the @samp{embark-select} action +through @samp{embark-act-all}, since this will run @samp{embark-select} on each +member of the current selection. Similarly if no targets are selected +and you are in a minibuffer completion session, running @samp{embark-select} +from @samp{embark-act-all} will select all the current completion candidates. + +By default, whenever some targets are selected in the current buffer, +a count of selected targets appears in the mode line. This can be +turned off or customized through the @samp{embark-selection-indicator} user +option. + +The selection functionality is supported in every buffer: + +@itemize +@item +In the minibuffer this gives a convenient way to act on several +completion candidates that don't follow any simple pattern: just go +through the completions selecting the ones you want, then use +@samp{embark-act-all}. For example, you could attach several files at once +to an email. +@item +For Embark Collect buffers this functionality enables a dired-like +workflow, in which you mark various candidates and apply an action +to all at once. (It supersedes a previous ad hoc dired-like +interface that was implemented only in Embark Collect buffers, with +a slightly different interface.) +@item +In a eww buffer you could use this to select various links you wish +to follow up on, and then collect them into a buffer. Similarly, +while reading Emacs's info manual you could select some symbols you +want to read more about and export them to an @samp{apropos-mode} buffer. +@item +You can use selections in regular text or programming buffers to do +complex editing operations. For example, if you have three +paragraphs scattered over a file and you want to bring them +together, you can select each one, insert them all somewhere and +finally delete all of them (from their original locations). +@end itemize + +@node @samp{embark-live} a live-updating variant of @samp{embark-collect} +@subsection @samp{embark-live} a live-updating variant of @samp{embark-collect} + +Finally, there is also an @samp{embark-live} variant of the @samp{embark-collect} +command which automatically updates the collection after each change +in the source buffer. Users of a completion UI that automatically +updates and displays the candidate list (such as Vertico, Icomplete, +Fido-mode, or MCT) will probably not want to use +@samp{embark-live} from the minibuffer as they will then have two live +updating displays of the completion candidates! + +A more likely use of @samp{embark-live} is to be called from a regular buffer +to display a sort of live updating ``table of contents'' for the buffer. +This depends on having appropriate candidate collectors configured in +@samp{embark-candidate-collectors}. There are not many in Embark's default +configuration, but you can try this experiment: open a dired buffer in +a directory that has very many files, mark a few, and run @samp{embark-live}. +You'll get an Embark Collect buffer containing only the marked files, +which updates as you mark or unmark files in dired. To make +@samp{embark-live} genuinely useful other candidate collectors are required. +The @samp{embark-consult} package (documented near the end of this manual) +contains a few: one for imenu items and one for outline headings as +used by @samp{outline-minor-mode}. Those collectors really do give +@samp{embark-live} a table-of-contents feel. + +@node Switching to a different command without losing what you've typed +@section Switching to a different command without losing what you've typed + +Embark also has the @samp{embark-become} command which is useful for when +you run a command, start typing at the minibuffer and realize you +meant a different command. The most common case for me is that I run +@samp{switch-to-buffer}, start typing a buffer name and realize I haven't +opened the file I had in mind yet! I'll use this situation as a +running example to illustrate @samp{embark-become}. When this happens I can, +of course, press @samp{C-g} and then run @samp{find-file} and open the file, but +this requires retyping the portion of the file name you already +typed. This process can be streamlined with @samp{embark-become}: while still +in the @samp{switch-to-buffer} you can run @samp{embark-become} and effectively +make the @samp{switch-to-buffer} command become @samp{find-file} for this run. + +You can bind @samp{embark-become} to a key in @samp{minibuffer-local-map}, but it is +also available as an action under the letter @samp{B} (uppercase), so you +don't need a binding if you already have one for @samp{embark-act}. So, +assuming I have @samp{embark-act} bound to, say, @samp{C-.}, once I realize I +haven't open the file I can type @samp{C-. B C-x C-f} to have +@samp{switch-to-buffer} become @samp{find-file} without losing what I have already +typed in the minibuffer. + +But for even more convenience, @samp{embark-become} offers shorter key +bindings for commands you are likely to want the current command to +become. When you use @samp{embark-become} it looks for the current command in +all keymaps named in the list @samp{embark-become-keymaps} and then activates +all keymaps that contain it. For example, the default value of +@samp{embark-become-keymaps} contains a keymap @samp{embark-become-file+buffer-map} +with bindings for several commands related to files and buffers, in +particular, it binds @samp{switch-to-buffer} to @samp{b} and @samp{find-file} to @samp{f}. So when +I accidentally try to switch to a buffer for a file I haven't opened +yet, @samp{embark-become} finds that the command I ran, @samp{switch-to-buffer}, is +in the keymap @samp{embark-become-file+buffer-map}, so it activates that +keymap (and any others that also contain a binding for +@samp{switch-to-buffer}). The end result is that I can type @samp{C-. B f} to +switch to @samp{find-file}. + +@node Quick start +@chapter Quick start + +The easiest way to install Embark is from GNU ELPA, just run @samp{M-x +package-install RET embark RET}. (It is also available on MELPA@.) It is +highly recommended to also install @uref{https://github.com/minad/marginalia, Marginalia} (also available on GNU +ELPA), so that Embark can offer you preconfigured actions in more +contexts. For @samp{use-package} users, the following is a very reasonable +starting configuration: + +@lisp +(use-package marginalia + :ensure t + :config + (marginalia-mode)) + +(use-package embark + :ensure t + + :bind + (("C-." . embark-act) ;; pick some comfortable binding + ("C-;" . embark-dwim) ;; good alternative: M-. + ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' + + :init + + ;; Optionally replace the key help with a completing-read interface + (setq prefix-help-command #'embark-prefix-help-command) + + ;; Show the Embark target at point via Eldoc. You may adjust the + ;; Eldoc strategy, if you want to see the documentation from + ;; multiple providers. Beware that using this can be a little + ;; jarring since the message shown in the minibuffer can be more + ;; than one line, causing the modeline to move up and down: + + ;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target) + ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) + + :config + + ;; Hide the mode line of the Embark live/completions buffers + (add-to-list 'display-buffer-alist + '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" + nil + (window-parameters (mode-line-format . none))))) + +;; Consult users will also want the embark-consult package. +(use-package embark-consult + :ensure t ; only need to install it, embark loads it after consult if found + :hook + (embark-collect-mode . consult-preview-at-point-mode)) +@end lisp + +About the suggested key bindings for @samp{embark-act} and @samp{embark-dwim}: +@itemize +@item +Those key bindings are unlikely to work in the terminal, but +terminal users are probably well aware of this and will know to +select different bindings. +@item +The suggested @samp{C-.} binding is used by default in (at least some +installations of) GNOME to input emojis, and Emacs doesn't even get +a chance to respond to the binding. You can select a different key +binding for @samp{embark-act} or use @samp{ibus-setup} to change the shortcut for +emoji insertion (Emacs 29 will likely use @samp{C-x 8 e e}, in case you +want to set the same one system-wide). +@item +The suggested alternative of @samp{M-.} for @samp{embark-dwim} is bound by default +to @samp{xref-find-definitions}. That is a very useful command but +overwriting it with @samp{embark-dwim} is sensible since in Embark's +default configuration, @samp{embark-dwim} will also find the definition of +the identifier at point. (Note that @samp{xref-find-definitions} with a +prefix argument prompts you for an identifier, @samp{embark-dwim} does not +cover this case). +@end itemize + +Other Embark commands such as @samp{embark-act-all}, @samp{embark-become}, +@samp{embark-collect}, and @samp{embark-export} can be run through @samp{embark-act} as +actions bound to @samp{A}, @samp{B}, @samp{S} (for ``snapshot''), and @samp{E} respectively, and +thus don't really need a dedicated key binding, but feel free to bind +them directly if you so wish. If you do choose to bind them directly, +you'll probably want to bind them in @samp{minibuffer-local-map}, since they +are most useful in the minibuffer (in fact, @samp{embark-become} only works +in the minibuffer). + +The command @samp{embark-dwim} executes the default action at point. Another good +keybinding for @samp{embark-dwim} is @samp{M-.} since @samp{embark-dwim} acts like +@samp{xref-find-definitions} on the symbol at point. @samp{C-.} can be seen as a +right-click context menu at point and @samp{M-.} acts like left-click. The +keybindings are mnemonic, both act at the point (@samp{.}). + +Embark needs to know what your minibuffer completion system considers +to be the list of candidates and which one is the current candidate. +Embark works out of the box if you use Emacs's default tab completion, +the built-in @samp{icomplete-mode} or @samp{fido-mode}, or the third-party packages +@uref{https://github.com/minad/vertico, Vertico} or @uref{https://github.com/abo-abo/swiper, Ivy}. + +If you are a @uref{https://emacs-helm.github.io/helm/, Helm} or @uref{https://github.com/abo-abo/swiper, Ivy} user you are unlikely to want Embark since +those packages include comprehensive functionality for acting on +minibuffer completion candidates. (Embark does come with Ivy +integration despite this.) + +@node Advanced configuration +@chapter Advanced configuration + +@menu +* Showing information about available targets and actions:: +* Selecting commands via completions instead of key bindings:: +* Quitting the minibuffer after an action:: +* Running some setup after injecting the target:: +* Running hooks before, after or around an action: Running hooks before after or around an action. +* Creating your own keymaps:: +* Defining actions for new categories of targets:: +@end menu + +@node Showing information about available targets and actions +@section Showing information about available targets and actions + +By default, if you run @samp{embark-act} and do not immediately select an +action, after a short delay Embark will pop up a buffer called @samp{*Embark +Actions*} containing a list of available actions with their key +bindings. You can scroll that buffer with the mouse of with the usual +commands @samp{scroll-other-window} and @samp{scroll-other-window-down} (bound by +default to @samp{C-M-v} and @samp{C-M-S-v}). + +That functionality is provided by the @samp{embark-mixed-indicator}, but +Embark has other indicators that can provide information about the +target and its type, what other targets you can cycle to, and which +actions have key bindings in the action map for the current type of +target. Any number of indicators can be active at once and the user +option @samp{embark-indicators} should be set to a list of the desired +indicators. + +Embark comes with the following indicators: + +@itemize +@item +@samp{embark-minimal-indicator}: shows a messages in the echo area or +minibuffer prompt showing the current target and the types of all +targets starting with the current one. + +@item +@samp{embark-highlight-indicator}: highlights the target at point; on by +default. + +@item +@samp{embark-verbose-indicator}: displays a table of actions and their key +bindings in a buffer; this is not on by default, in favor of the +mixed indicator described next. + +@item +@samp{embark-mixed-indicator}: starts out by behaving as the minimal +indicator but after a short delay acts as the verbose indicator; +this is on by default. + +@item +@samp{embark-isearch-highlight-indicator}: this only does something when +the current target is the symbol at point, in which case it +lazily highlights all occurrences of that symbol in the current +buffer, like isearch; also on by default. +@end itemize + +Users of the popular @uref{https://github.com/justbur/emacs-which-key, which-key} package may prefer to use the +@samp{embark-which-key-indicator} from the @uref{https://github.com/oantolin/embark/wiki/Additional-Configuration#use-which-key-like-a-key-menu-prompt, Embark wiki}. Just copy its +definition from the wiki into your configuration and customize the +@samp{embark-indicators} user option to exclude the mixed and verbose +indicators and to include @samp{embark-which-key-indicator}. + +If you use @uref{https://github.com/minad/vertico, Vertico}, there is an even easier way to get a +@samp{which-key}-like display that also lets you use completion to narrow +down the list of alternatives, described at the end of the next +section. + +@node Selecting commands via completions instead of key bindings +@section Selecting commands via completions instead of key bindings + +As an alternative to reading the list of actions in the verbose or +mixed indicators (see the previous section for a description of +these), you can press the @samp{embark-help-key}, which is @samp{C-h} by default +(but you may prefer @samp{?} to free up @samp{C-h} for use as a prefix) after +running @samp{embark-act}. Pressing the help key will prompt you for the name +of an action with completion (but feel free to enter a command that is +not among the offered candidates!), and will also remind you of the +key bindings. You can press @samp{embark-keymap-prompter-key}, which is @samp{@@} by +default, at the prompt and then one of the key bindings to enter the +name of the corresponding action. + +You may think that with the @samp{*Embark Actions*} buffer popping up to +remind you of the key bindings you'd never want to use completion to +select an action by name, but personally I find that typing a small +portion of the action name to narrow down the list of candidates feels +significantly faster than visually scanning the entire list of actions. + +If you find you prefer selecting actions that way, you can configure +embark to always prompt you for actions by setting the variable +@samp{embark-prompter} to @samp{embark-completing-read-prompter}. + +On the other hand, you may wish to continue using key bindings for the +actions you perform most often, and to use completion only to explore +what further actions are available or when you've forgotten a key +binding. In that case, you may prefer to use the minimal indicator, +which does not pop-up an @samp{*Embark Actions*} buffer at all, and to use +the @samp{embark-help-key} whenever you need help. This unobtrusive setup is +achieved with the following configuration: + +@lisp +(setq embark-indicators + '(embark-minimal-indicator ; default is embark-mixed-indicator + embark-highlight-indicator + embark-isearch-highlight-indicator)) +@end lisp + +@uref{https://github.com/minad/vertico, Vertico} users may wish to configure a grid display for the actions and +key-bindings, reminiscent of the popular package @uref{https://github.com/justbur/emacs-which-key, which-key}, but, of +course, enhanced by the use of completion to narrow the list of +commands. In order to get the grid display, put the following in your +Vertico configuration: + +@lisp +(add-to-list 'vertico-multiform-categories '(embark-keybinding grid)) +(vertico-multiform-mode) +@end lisp + +This will make the available keys be shown in a compact grid like in +@samp{which-key}. The @samp{vertico-multiform-mode} also enables keys such as @samp{M-V}, +@samp{M-G}, @samp{M-B}, and @samp{M-U} for manually switching between layouts in Vertico +buffers. + +@menu +* Selecting commands via completion outside of Embark:: +@end menu + +@node Selecting commands via completion outside of Embark +@subsection Selecting commands via completion outside of Embark + +If you like this completion interface for exploring key bindings for +Embark actions, you may want to use it elsewhere in Emacs. You can use +Embark's completion-based command prompter to list: + +@itemize +@item +key bindings under a prefix, +@item +local key bindings, or +@item +all key bindings. +@end itemize + +To use it for key bindings under a prefix (you can use this to replace +the @samp{which-key} package, for example), use this configuration: + +@lisp +(setq prefix-help-command #'embark-prefix-help-command) +@end lisp + +Now, when you have started on a prefix sequence such as @samp{C-x} or @samp{C-c}, +pressing @samp{C-h} will bring up the Embark version of the built-in +@samp{prefix-help-command}, which will list the keys under that prefix and +their bindings, and lets you select the one you wanted with completion, +or by key binding if you press @samp{embark-keymap-prompter-key}. + +To list local or global key bindings, use the command @samp{embark-bindings}. +You can bind that to @samp{C-h b}, which is the default key binding for the +built-in @samp{describe-bindings} command, which this command can replace. By +default, @samp{embark-bindings} lists local key bindings, typically those +bound in the major mode keymap; to get global bindings as well, call +it with a @samp{C-u} prefix argument. + +@node Quitting the minibuffer after an action +@section Quitting the minibuffer after an action + +By default, if you call @samp{embark-act} from the minibuffer it quits the +minibuffer after performing the action. You can change this by setting +the user option @samp{embark-quit-after-action} to @samp{nil}. Having @samp{embark-act} @emph{not} +quit the minibuffer can be useful to turn commands into little ``thing +managers''. For example, you can use @samp{find-file} as a little file manager +or @samp{describe-package} as a little package manager: you can run those +commands, perform a series of actions, and then quit the command. + +If you want to control the quitting behavior in a fine-grained manner +depending on the action, you can set @samp{embark-quit-after-action} to an +alist, associating commands to either @samp{t} for quitting or @samp{nil} for not +quitting. When using an alist, you can use the special key @samp{t} to +specify the default behavior. For example, to specify that by default +actions should not quit the minibuffer but that using @samp{kill-buffer} as +an action should quit, you can use the following configuration: + +@lisp +(setq embark-quit-after-action '((kill-buffer . t) (t . nil))) +@end lisp + +The variable @samp{embark-quit-after-action} only specifies a default, that +is, it only controls whether or not @samp{embark-act} quits the minibuffer +when you call it without a prefix argument, and you can select the +opposite behavior to what the variable says by calling @samp{embark-act} with +@samp{C-u}. Also note that both the variable @samp{embark-quit-after-action} and @samp{C-u} +have no effect when you call @samp{embark-act} outside the minibuffer. + +If you find yourself using the quitting and non-quitting variants of +@samp{embark-act} about equally often, independently of the action, you may +prefer to simply have separate commands for them instead of a single +command that you call with @samp{C-u} half the time. You could, for example, +keep the default exiting behavior of @samp{embark-act} and define a +non-quitting version as follows: + +@lisp +(defun embark-act-noquit () + "Run action but don't quit the minibuffer afterwards." + (interactive) + (let ((embark-quit-after-action nil)) + (embark-act))) +@end lisp + +@node Running some setup after injecting the target +@section Running some setup after injecting the target + +You can customize what happens after the target is inserted at the +minibuffer prompt of an action. There are +@samp{embark-target-injection-hooks}, that are run by default after injecting +the target into the minibuffer. The variable +@samp{embark-target-injection-hooks} is an alist associating commands to +their setup hooks. There are two special keys: if no setup hook is +specified for a given action, the hook associated to @samp{t} is run; and the +hook associated to @samp{:always} is run regardless of the action. (This +variable used to have the less explicit name of +@samp{embark-setup-action-hooks}, so please update your configuration.) + +For example, consider using @samp{shell-command} as an action during file +completion. It would be useful to insert a space before the target +file name and to leave the point at the beginning, so you can +immediately type the shell command to run on that file. That's why in +Embark's default configuration there is an entry in +@samp{embark-target-injection-hooks} associating @samp{shell-command} to a hook that +includes @samp{embark--shell-prep}, a simple helper function that quotes all +the spaces in the file name, inserts an extra space at the beginning +of the line and leaves point to the left of it. + +Now, the preparation that @samp{embark--shell-prep} does would be useless if +Embark did what it normally does after it inserts the target of the +action at the minibuffer prompt, which is to ``press @samp{RET}'' for you, +accepting the target as is; if Embark did that for @samp{shell-command} you +wouldn't get a chance to type in the command to execute! That is why +in Embark's default configuration the entry for @samp{shell-command} in +@samp{embark-target-injection-hooks} also contains the function +@samp{embark--allow-edit}. + +Embark used to have a dedicated variable @samp{embark-allow-edit-actions} to +which you could add commands for which Embark should forgo pressing +@samp{RET} for you after inserting the target. Since its effect can also be +achieved via the general @samp{embark-target-injection-hooks} mechanism, that +variable has been removed to simplify Embark. Be sure to update your +configuration; if you had something like: + +@lisp +(add-to-list 'embark-allow-edit-actions 'my-command) +@end lisp + +you should replace it with: + +@lisp +(push 'embark--allow-edit + (alist-get 'my-command embark-target-injection-hooks)) +@end lisp + + +Also note that while you could abuse @samp{embark--allow-edit} so that you +have to confirm ``dangerous'' actions such as @samp{delete-file}, it is better +to implement confirmation by adding the @samp{embark--confirm} function to +the appropriate entry of a different hook alist, namely, +@samp{embark-pre-action-hooks}. + +Besides @samp{embark--allow-edit}, Embark comes with another function that is +of general utility in action setup hooks: @samp{embark--ignore-target}. Use +it for commands that do prompt you in the minibuffer but for which +inserting the target would be inappropriate. This is not a common +situation but does occasionally arise. For example it is used by +default for @samp{shell-command-on-region}: that command is used as an action +for region targets, and it prompts you for a shell command; you +typically do @emph{not} want the target, that is the contents of the region, +to be entered at that prompt! + +@node Running hooks before after or around an action +@section Running hooks before, after or around an action + +Embark has three variables, @samp{embark-pre-action-hooks}, +@samp{embark-post-action-hooks} and @samp{embark-around-action-hooks}, which are +alists associating commands to hooks that should run before or after +or as around advice for the command when used as an action. As with +@samp{embark-target-injection-hooks}, there are two special keys for the +alists: @samp{t} designates the default hook to run when no specific hook is +specified for a command; and the hook associated to @samp{:always} runs +regardless. + +The default values of those variables are fairly extensive, adding +creature comforts to make running actions a smooth experience. Embark +comes with several functions intended to be added to these hooks, and +used in the default values of @samp{embark-pre-action-hooks}, +@samp{embark-post-action-hooks} and @samp{embark-around-action-hooks}. + +For pre-action hooks: + +@table @asis +@item @samp{embark--confirm} +Prompt the user for confirmation before executing +the action. This is used be default for commands deemed ``dangerous'', +or, more accurately, hard to undo, such as @samp{delete-file} and +@samp{kill-buffer}. + +@item @samp{embark--unmark-target} +Unmark the active region. Use this for +commands you want to act on the region contents but without the +region being active. The default configuration uses this function as +a pre-action hook for @samp{occur} and @samp{query-replace}, for example, so that +you can use them as actions with region targets to search the whole +buffer for the text contained in the region. Without this pre-action +hook using @samp{occur} as an action for a region target would be +pointless: it would search for the the region contents @emph{in the +region}, (typically, due to the details of regexps) finding only one +match! + +@item @samp{embark--beginning-of-target} +Move to the beginning of the target +(for targets that report bounds). This is used by default for +backward motion commands such as @samp{backward-sexp}, so that they don't +accidentally leave you on the current target. + +@item @samp{embark--end-of-target} +Move to the end of the target. This is used +similarly to the previous function, but also for commands that act +on the last s-expression like @samp{eval-last-sexp}. This allow you to act +on an s-expression from anywhere inside it and still use +@samp{eval-last-sexp} as an action. + +@item @samp{embark--xref-push-markers} +Push the current location on the xref +marker stack. Use this for commands that take you somewhere and for +which you'd like to be able to come back to where you were using +@samp{xref-pop-marker-stack}. This is used by default for @samp{find-library}. +@end table + +For post-action hooks: + +@table @asis +@item @samp{embark--restart} +Restart the command currently prompting in the +minibuffer, so that the list of completion candidates is updated. +This is useful as a post action hook for commands that delete or +rename a completion candidate; for example the default value of +@samp{embark-post-action-hooks} uses it for @samp{delete-file}, @samp{kill-buffer}, +@samp{rename-file}, @samp{rename-buffer}, etc. +@end table + +For around-action hooks: + +@table @asis +@item @samp{embark--mark-target} +Save existing mark and point location, mark +the target and run the action. Most targets at point outside the +minibuffer report which region of the buffer they correspond to +(this is the information used by @samp{embark-highlight-indicator} to +know what portion of the buffer to highlight); this function marks +that region. It is useful as an around action hook for commands that +expect a region to be marked, for example, it is used by default for +@samp{indent-region} so that it works on s-expression targets, or for +@samp{fill-region} so that it works on paragraph targets. + +@item @samp{embark--cd} +Run the action with @samp{default-directory} set to the +directory associated to the current target. The target should be of +type @samp{file}, @samp{buffer}, @samp{bookmark} or @samp{library}, and the associated directory +is what you'd expect in each case. + +@item @samp{embark--narrow-to-target} +Run the action with buffer narrowed to +current target. Use this as an around hook to localize the effect of +actions that don't already work on just the region. In the default +configuration it is used for @samp{repunctuate-sentences}. + +@item @samp{embark--save-excursion} +Run the action restoring point at the end. +The current default configuration doesn't use this but it is +available for users. +@end table + +@node Creating your own keymaps +@section Creating your own keymaps + +All internal keymaps are defined with the standard helper macro +@samp{defvar-keymap}. For example a simple version of the file action keymap +could be defined as follows: + +@lisp +(defvar-keymap embark-file-map + :doc "Example keymap with a few file actions" + :parent embark-general-map + "d" #'delete-file + "r" #'rename-file + "c" #'copy-file) +@end lisp + +These action keymaps are perfectly normal Emacs +keymaps. You may want to inherit from the @samp{embark-general-map} if you +want to access the default Embark actions. Note that @samp{embark-collect} +and @samp{embark-export} are also made available via @samp{embark-general-map}. + +@node Defining actions for new categories of targets +@section Defining actions for new categories of targets + +It is easy to configure Embark to provide actions for new types of +targets, either in the minibuffer or outside it. I present below two +very detailed examples of how to do this. At several points I'll +explain more than one way to proceed, typically with the easiest +option first. I include the alternative options since there will be +similar situations where the easiest option is not available. + +@menu +* New minibuffer target example - tab-bar tabs:: +* New target example in regular buffers - short Wikipedia links:: +@end menu + +@node New minibuffer target example - tab-bar tabs +@subsection New minibuffer target example - tab-bar tabs + +As an example, take the new @uref{https://www.gnu.org/software/emacs/manual/html_node/emacs/Tab-Bars.html, tab bars} from Emacs 27. I'll explain how +to configure Embark to offer tab-specific actions when you use the +tab-bar-mode commands that mention tabs by name. The configuration +explained here is now built-in to Embark (and Marginalia), but it's +still a good self-contained example. In order to setup up tab actions +you would need to: (1) make sure Embark knows those commands deal with +tabs, (2) define a keymap for tab actions and configure Embark so it +knows that's the keymap you want. + +@enumerate +@item +@anchor{Telling Embark about commands that prompt for tabs by name}Telling Embark about commands that prompt for tabs by name + + +For step (1), it would be great if the @samp{tab-bar-mode} commands reported +the completion category @samp{tab} when asking you for a tab with +completion. (All built-in Emacs commands that prompt for file names, +for example, do have metadata indicating that they want a @samp{file}.) They +do not, unfortunately, and I will describe a couple of ways to deal +with this. + +Maybe the easiest thing is to configure @uref{https://github.com/minad/marginalia, Marginalia} to enhance those +commands. All of the @samp{tab-bar-*-tab-by-name} commands have the words +``tab by name'' in the minibuffer prompt, so you can use: + +@lisp +(add-to-list 'marginalia-prompt-categories '("tab by name" . tab)) +@end lisp + +That's it! But in case you are ever in a situation where you don't +already have commands that prompt for the targets you want, I'll +describe how writing your own command with appropriate @samp{category} +metadata looks: + +@lisp +(defun my-select-tab-by-name (tab) + (interactive + (list + (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) + (tab-bar-tabs)) + (user-error "No tabs found")))) + (completing-read + "Tabs: " + (lambda (string predicate action) + (if (eq action 'metadata) + '(metadata (category . tab)) + (complete-with-action + action tab-list string predicate))))))) + (tab-bar-select-tab-by-name tab)) +@end lisp + +As you can see, the built-in support for setting the category +meta-datum is not very easy to use or pretty to look at. To help with +this I recommend the @samp{consult--read} function from the excellent +@uref{https://github.com/minad/consult/, Consult} package. With that function we can rewrite the command as +follows: + +@lisp +(defun my-select-tab-by-name (tab) + (interactive + (list + (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) + (tab-bar-tabs)) + (user-error "No tabs found")))) + (consult--read tab-list + :prompt "Tabs: " + :category 'tab)))) + (tab-bar-select-tab-by-name tab)) +@end lisp + +Much nicer! No matter how you define the @samp{my-select-tab-by-name} +command, the first approach with Marginalia and prompt detection has +the following advantages: you get the @samp{tab} category for all the +@samp{tab-bar-*-bar-by-name} commands at once, also, you enhance built-in +commands, instead of defining new ones. + +@item +@anchor{Defining and configuring a keymap for tab actions}Defining and configuring a keymap for tab actions + + +Let's say we want to offer select, rename and close actions for tabs +(in addition to Embark general actions, such as saving the tab name to +the kill-ring, which you get for free). Then this will do: + +@lisp +(defvar-keymap embark-tab-actions + :doc "Keymap for actions for tab-bar tabs (when mentioned by name)." + :parent embark-general-map + "s" #'tab-bar-select-tab-by-name + "r" #'tab-bar-rename-tab-by-name + "k" #'tab-bar-close-tab-by-name) + +(add-to-list 'embark-keymap-alist '(tab . embark-tab-actions)) +@end lisp + +What if after using this for a while you feel closing the tab +without confirmation is dangerous? You have a couple of options: + +@enumerate +@item +You can keep using the @samp{tab-bar-close-tab-by-name} command, but have +Embark ask you for confirmation: +@lisp +(push #'embark--confirm + (alist-get 'tab-bar-close-tab-by-name + embark-pre-action-hooks)) +@end lisp + +@item +You can write your own command that prompts for confirmation and +use that instead of @samp{tab-bar-close-tab-by-name} in the above keymap: +@lisp +(defun my-confirm-close-tab-by-name (tab) + (interactive "sTab to close: ") + (when (y-or-n-p (format "Close tab '%s'? " tab)) + (tab-bar-close-tab-by-name tab))) +@end lisp + +Notice that this is a command you can also use directly from @samp{M-x} +independently of Embark. Using it from @samp{M-x} leaves something to be +desired, though, since you don't get completion for the tab names. +You can fix this if you wish as described in the previous section. +@end enumerate +@end enumerate + +@node New target example in regular buffers - short Wikipedia links +@subsection New target example in regular buffers - short Wikipedia links + +Say you want to teach Embark to treat text of the form +@samp{wikipedia:Garry_Kasparov} in any regular buffer as a link to Wikipedia, +with actions to open the Wikipedia page in eww or an external browser +or to save the URL of the page in the kill-ring. We can take advantage +of the actions that Embark has preconfigured for URLs, so all we need +to do is teach Embark that @samp{wikipedia:Garry_Kasparov} stands for the URL +@samp{https://en.wikipedia.org/wiki/Garry_Kasparov}. + +You can be as fancy as you want with the recognized syntax. Here, to +keep the example simple, I'll assume the link matches the regexp +@samp{wikipedia:[[:alnum:]_]+}. We will write a function that looks for a +match surrounding point, and returns a dotted list of the form @samp{'(url +URL-OF-THE-PAGE START . END)} where @samp{START} and @samp{END} are the buffer +positions bounding the target, and are used by Embark to highlight it +if you have @samp{embark-highlight-indicator} included in the list +@samp{embark-indicators}. (There are a couple of other options for the return +value of a target finder: the bounding positions are optional and a +single target finder is allowed to return multiple targets; see the +documentation for @samp{embark-target-finders} for details.) + +@lisp +(defun my-short-wikipedia-link () + "Target a link at point of the form wikipedia:Page_Name." + (save-excursion + (let* ((start (progn (skip-chars-backward "[:alnum:]_:") (point))) + (end (progn (skip-chars-forward "[:alnum:]_:") (point))) + (str (buffer-substring-no-properties start end))) + (save-match-data + (when (string-match "wikipedia:\\([[:alnum:]_]+\\)" str) + `(url + ,(format "https://en.wikipedia.org/wiki/%s" + (match-string 1 str)) + ,start . ,end)))))) + +(add-to-list 'embark-target-finders 'my-short-wikipedia-link) +@end lisp + +@node How does Embark call the actions? +@chapter How does Embark call the actions? + +Embark actions are normal Emacs commands, that is, functions with an +interactive specification. In order to execute an action, Embark +calls the command with @samp{call-interactively}, so the command reads user +input exactly as if run directly by the user. For example the +command may open a minibuffer and read a string +(@samp{read-from-minibuffer}) or open a completion interface +(@samp{completing-read}). If this happens, Embark takes the target string +and inserts it automatically into the minibuffer, simulating user +input this way. After inserting the string, Embark exits the +minibuffer, submitting the input. (The immediate minibuffer exit can +be disabled for specific actions in order to allow editing the +input; this is done by adding the @samp{embark--allow-edit} function to the +appropriate entry of @samp{embark-target-injection-hooks}). Embark inserts +the target string at the first minibuffer opened by the action +command, and if the command happens to prompt the user for input +more than once, the user still interacts with the second and further +prompts in the normal fashion. Note that if a command does not +prompt the user for input in the minibuffer, Embark still allows you +to use it as an action, but of course, never inserts the target +anywhere. (There are plenty of examples in the default configuration +of commands that do not prompt the user bound to keys in the action +maps, most of the region actions, for instance.) + +This is how Embark manages to reuse normal commands as actions. The +mechanism allows you to use as Embark actions commands that were not +written with Embark in mind (and indeed almost all actions that are +bound by default in Embark's action keymaps are standard Emacs +commands). It also allows you to write new custom actions in such a +way that they are useful even without Embark. + +Staring from version 28.1, Emacs has a variable +@samp{y-or-n-p-use-read-key}, which when set to @samp{t} causes @samp{y-or-n-p} to use +@samp{read-key} instead of @samp{read-from-minibuffer}. Setting +@samp{y-or-n-p-use-read-key} to @samp{t} is recommended for Embark users because +it keeps Embark from attempting to insert the target at a @samp{y-or-n-p} +prompt, which would almost never be sensible. Also consider this as +a warning to structure your own action commands so that if they use +@samp{y-or-n-p}, they do so only after the prompting for the target. + +Here is a simple example illustrating the various ways of reading +input from the user mentioned above. Bind the following commands to +the @samp{embark-symbol-map} to be used as actions, then put the point on +some symbol and run them with @samp{embark-act}: + +@lisp +(defun example-action-command1 () + (interactive) + (message "The input was `%s'." (read-from-minibuffer "Input: "))) + +(defun example-action-command2 (arg input1 input2) + (interactive "P\nsInput 1: \nsInput 2: ") + (message "The first input %swas `%s', and the second was `%s'." + (if arg "truly " "") + input1 + input2)) + +(defun example-action-command3 () + (interactive) + (message "Your selection was `%s'." + (completing-read "Select: " '("E" "M" "B" "A" "R" "K")))) + +(defun example-action-command4 () + (interactive) + (message "I don't prompt you for input and thus ignore the target!")) + +(keymap-set embark-symbol-map "X 1" #'example-action-command1) +(keymap-set embark-symbol-map "X 2" #'example-action-command2) +(keymap-set embark-symbol-map "X 3" #'example-action-command3) +(keymap-set embark-symbol-map "X 4" #'example-action-command4) +@end lisp + +Also note that if you are using the key bindings to call actions, +you can pass prefix arguments to actions in the normal way. For +example, you can use @samp{C-u X2} with the above demonstration actions to +make the message printed by @samp{example-action-command2} more emphatic. +This ability to pass prefix arguments to actions is useful for some +actions in the default configuration, such as +@samp{embark-shell-command-on-buffer}. + +@menu +* Non-interactive functions as actions:: +@end menu + +@node Non-interactive functions as actions +@section Non-interactive functions as actions + +Alternatively, Embark does support one other type of action: a +non-interactive function of a single argument. The target is passed +as argument to the function. For example: + +@lisp +(defun example-action-function (target) + (message "The target was `%s'." target)) + +(keymap-set embark-symbol-map "X 4" #'example-action-function) +@end lisp + +Note that normally binding non-interactive functions in a keymap is +useless, since when attempting to run them using the key binding you +get an error message similar to ``Wrong type argument: commandp, +example-action-function''. In general it is more flexible to write +any new Embark actions as commands, that is, as interactive +functions, because that way you can also run them directly, without +Embark. But there are a couple of reasons to use non-interactive +functions as actions: + +@enumerate +@item +You may already have the function lying around, and it is +convenient to simply reuse it. + +@item +For command actions the targets can only be simple string, with +no text properties. For certain advanced uses you may want the +action to receive a string @emph{with} some text properties, or even a +non-string target. +@end enumerate + +@node Embark Marginalia and Consult +@chapter Embark, Marginalia and Consult + +Embark cooperates well with the @uref{https://github.com/minad/marginalia, Marginalia} and @uref{https://github.com/minad/consult, Consult} packages. +Neither of those packages is a dependency of Embark, but both are +highly recommended companions to Embark, for opposite reasons: +Marginalia greatly enhances Embark's usefulness, while Embark can help +enhance Consult. + +In the remainder of this section I'll explain what exactly Marginalia +does for Embark, and what Embark can do for Consult. + +@menu +* Marginalia:: +* Consult:: +@end menu + +@node Marginalia +@section Marginalia + +Embark comes with actions for symbols (commands, functions, variables +with actions such as finding the definition, looking up the +documentation, evaluating, etc.) in the @samp{embark-symbol-map} keymap, and +for packages (actions like install, delete, browse url, etc.) in the +@samp{embark-package-keymap}. + +Unfortunately Embark does not automatically offers you these keymaps +when relevant, because many built-in Emacs commands don't report +accurate category metadata. For example, a command like +@samp{describe-package}, which reads a package name from the minibuffer, +does not have metadata indicating this fact. + +In an earlier Embark version, there were functions to supply this +missing metadata, but they have been moved to Marginalia, which +augments many Emacs command to report accurate category metadata. +Simply activating @samp{marginalia-mode} allows Embark to offer you the +package and symbol actions when appropriate again. Candidate +annotations in the Embark collect buffer are also provided by the +Marginalia package: + +@itemize +@item +If you install Marginalia and activate @samp{marginalia-mode}, Embark +Collect buffers will use the Marginalia annotations automatically. + +@item +If you don't install Marginalia, you will see only the annotations +that come with Emacs (such as key bindings in @samp{M-x}, or the unicode +characters in @samp{C-x 8 RET}). +@end itemize + +@node Consult +@section Consult + +The excellent Consult package provides many commands that use +minibuffer completion, via the @samp{completing-read} function; plenty of its +commands can be considered enhanced versions of built-in Emacs +commands, and some are completely new functionality. One common +enhancement provided in all commands for which it makes sense is +preview functionality, for example @samp{consult-buffer} will show you a +quick preview of a buffer before you actually switch to it. + +If you use both Consult and Embark you should install the +@samp{embark-consult} package which provides integration between the two. It +provides exporters for several Consult commands and also tweaks the +behavior of many Consult commands when used as actions with @samp{embark-act} +in subtle ways that you may not even notice, but make for a smoother +experience. You need only install it to get these benefits: Embark +will automatically load it after Consult if found. + +The @samp{embark-consult} package provides the following exporters: + +@itemize +@item +You can use @samp{embark-export} from @samp{consult-line}, @samp{consult-outline}, or +@samp{consult-mark} to obtain an @samp{occur-mode} buffer. As with the built-in +@samp{occur} command you use that buffer to jump to a match and after that, +you can then use @samp{next-error} and @samp{previous-error} to navigate to other +matches. You can also press @samp{e} to activate @samp{occur-edit-mode} and edit +the matches in place! + +@item +You can export from any of the Consult asynchronous search commands, +@samp{consult-grep}, @samp{consult-git-grep}, or @samp{consult-ripgrep} to get a +@samp{grep-mode} buffer. Here too you can use @samp{next-error} and @samp{previous-error} +to navigate among matches, and, if you install the @uref{http://github.com/mhayashi1120/Emacs-wgrep/raw/master/wgrep.el , wgrep} package, +you can use it to edit the matches in place. +@end itemize + +In both cases, pressing @samp{g} will rerun the Consult command you had +exported from and re-enter the input you had typed (which is similar +to reverting but a little more flexible). You can then proceed to +re-export if that's what you want, but you can also edit the input +changing the search terms or simply cancel if you see you are done +with that search. + +The @samp{embark-consult} also contains some candidates collectors that allow +you to run @samp{embark-live} to get a live-updating table of contents for +your buffer: + +@itemize +@item +@samp{embark-consult-outline-candidates} produces the outline headings of +the current buffer, using @samp{consult-outline}. +@item +@samp{embark-consult-imenu-candidates} produces the imenu items of +the current buffer, using @samp{consult-imenu}. +@item +@samp{embark-consult-imenu-or-outline-candidates} is a simple combination +of the two previous functions: it produces imenu items in buffers +deriving from @samp{prog-mode} and otherwise outline headings. +@end itemize + +The way to configure @samp{embark-live} (or @samp{embark-collect} and @samp{embark-export} +for that matter) to use one of these function is to add it at the end +of the @samp{embark-candidate-collectors} list. The @samp{embark-consult} package by +default adds the last one, which seems to be the most sensible +default. + +Besides those exporters and candidate collectors, the @samp{embark-consult} +package provides many subtle tweaks and small integrations between +Embark and Consult. Some examples are: + +@itemize +@item +When used as actions, the asynchronous search commands will search +only the files associated to the targets: if the targets @emph{are} files, +it searches those files; for buffers it will search either the +associated file if there is one, else all files in the buffer's +@samp{default-directory}; for bookmarks it will search the file they point +to, same for Emacs Lisp libraries. This is particularly powerful +when using @samp{embark-act-all} to act on multiple files at once, for +example you can use @samp{consult-find} to search among file @emph{names} and then +@samp{embark-act-all} and @samp{consult-grep} to search within the matching files. + +@itemize +@item +For all other target types, those that do not have a sensible +notion of associated file, a Consult search command (asynchronous +or not) will search for the text of the target but leave the +minibuffer open so you can interact with the Consult command. +@end itemize + +@item +@samp{consult-imenu} will search for the target and take you directly to +the location if it matches a unique imenu entry, otherwise it will +leave the minibuffer open so you can navigate among the matches. +@end itemize + +@node Related Packages +@chapter Related Packages + +There are several packages that offer functionality similar +to Embark's. + +@table @asis +@item Acting on minibuffer completion candidates +The popular Ivy and +Helm packages have support for acting on the completion candidates +of commands written using their APIs, and there is an extensive +ecosystem of packages meant for Helm and for Ivy (the Ivy ones +usually have ``counsel'' in the name) providing commands and +appropriate actions. +@item Acting on things at point +The built-in @samp{context-menu-mode} provides +a mouse-driven context-sensitive configurable menu. The @samp{do-at-point} +package by Philip Kaludercic (available on GNU ELPA), on the other +hand is keyboard-driven. +@item Collecting completion candidates into a buffer +The Ivy package +has the command @samp{ivy-occur} which is similar to @samp{embark-collect}. As +with Ivy actions, @samp{ivy-occur} only works for commands written using +the Ivy API@. +@end table + +@node Resources +@chapter Resources + +If you want to learn more about how others have used Embark here are +some links to read: + +@itemize +@item +@uref{https://karthinks.com/software/fifteen-ways-to-use-embark/, Fifteen ways to use Embark}, a blog post by Karthik Chikmagalur. +@item +@uref{https://protesilaos.com/dotemacs/, Protesilaos Stavrou's dotemacs}, look for the section called +``Extended minibuffer actions and more (embark.el and +prot-embark.el)'' +@end itemize + +And some videos to watch: + +@itemize +@item +@uref{https://protesilaos.com/codelog/2021-01-09-emacs-embark-extras/, Embark and my extras} by Protesilaos Stavrou. +@item +@uref{https://youtu.be/qpoQiiinCtY, Embark -- Key features and tweaks} by Raoul Comninos on the +Emacs-Elements YouTube channel. +@item +@uref{https://youtu.be/WsxXr1ncukY, Livestreamed: Adding an Embark context action to send a stream +message} by Sacha Chua. +@item +@uref{https://youtu.be/qk2Is_sC8Lk, System Crafters Live! - The Many Uses of Embark} by David Wilson. +@item +@uref{https://youtu.be/5ffb2at2d7w, Using Emacs Episode 80 - Vertico, Marginalia, Consult and Embark} by +Mike Zamansky. +@end itemize + +@node Contributions +@chapter Contributions + +Contributions to Embark are very welcome. There is a @uref{https://github.com/oantolin/embark/issues/95, wish list} for +actions, target finders, candidate collectors and exporters. For other +ideas you have for Embark, feel free to open an issue on the @uref{https://github.com/oantolin/embark/issues, issue +tracker}. Any neat configuration tricks you find might be a good fit +for the @uref{https://github.com/oantolin/embark/wiki, wiki}. + +Code contributions are very welcome too, but since Embark is now on +GNU ELPA, copyright assignment to the FSF is required before you can +contribute code. + +@node Acknowledgments +@chapter Acknowledgments + +While I, Omar Antolín Camarena, have written most of the Embark code +and remain very stubborn about some of the design decisions, Embark +has received substantial help from a number of other people which this +document has neglected to mention for far too long. In particular, +Daniel Mendler has been absolutely invaluable, implementing several +important features, and providing a lot of useful advice. + +Code contributions: + +@itemize +@item +@uref{https://github.com/minad, Daniel Mendler} +@item +@uref{https://github.com/clemera/, Clemens Radermacher} +@item +@uref{https://codeberg.org/jao/, José Antonio Ortega Ruiz} +@item +@uref{https://github.com/iyefrat, Itai Y@. Efrat} +@item +@uref{https://github.com/a13, a13} +@item +@uref{https://github.com/jakanakaevangeli, jakanakaevangeli} +@item +@uref{https://github.com/mihakam, mihakam} +@item +@uref{https://github.com/leungbk, Brian Leung} +@item +@uref{https://github.com/karthink, Karthik Chikmagalur} +@item +@uref{https://github.com/roshanshariff, Roshan Shariff} +@item +@uref{https://github.com/condy0919, condy0919} +@item +@uref{https://github.com/DamienCassou, Damien Cassou} +@item +@uref{https://github.com/JimDBh, JimDBh} +@end itemize + +Advice and useful discussions: + +@itemize +@item +@uref{https://github.com/minad, Daniel Mendler} +@item +@uref{https://gitlab.com/protesilaos/, Protesilaos Stavrou} +@item +@uref{https://github.com/clemera/, Clemens Radermacher} +@item +@uref{https://github.com/hmelman/, Howard Melman} +@item +@uref{https://github.com/astoff, Augusto Stoffel} +@item +@uref{https://github.com/bdarcus, Bruce d'Arcus} +@item +@uref{https://github.com/jdtsmith, JD Smith} +@item +@uref{https://github.com/karthink, Karthik Chikmagalur} +@item +@uref{https://github.com/jakanakaevangeli, jakanakaevangeli} +@item +@uref{https://github.com/iyefrat, Itai Y@. Efrat} +@item +@uref{https://github.com/mohkale, Mohsin Kaleem} +@end itemize + +@bye blob - 461858ed444ef6774cc72eb693ea2fa1df587006 (mode 644) blob + /dev/null --- elpa/embark-1.0.signed +++ /dev/null @@ -1,2 +0,0 @@ -Good signature from 066DAFCB81E42C40 GNU ELPA Signing Agent (2019) (trust undefined) created at 2023-12-08T11:05:03+0100 using RSA -Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) (trust undefined) created at 2023-12-08T11:05:03+0100 using EDDSA \ No newline at end of file blob - /dev/null blob + 9454dcdaf042e9b6a204910f906b51b857614d6b (mode 644) --- /dev/null +++ elpa/embark-1.1.signed @@ -0,0 +1 @@ +Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) (trust undefined) created at 2024-04-19T11:05:03+0200 using EDDSA \ No newline at end of file blob - 6d22de33f570450fc5a630a3b4d331ce4aeccc53 (mode 644) blob + /dev/null --- elpa/embark-consult-1.0/.dir-locals.el +++ /dev/null @@ -1,6 +0,0 @@ -;;; Directory Local Variables -;;; For more information see (info "(emacs) Directory Variables") - -((emacs-lisp-mode - (show-trailing-whitespace . t) - (indent-tabs-mode . nil))) blob - 7a694c9699a986b9adf1f6cb8a18a6e923e47ed9 (mode 644) blob + /dev/null --- elpa/embark-consult-1.0/.elpaignore +++ /dev/null @@ -1 +0,0 @@ -LICENSE \ No newline at end of file blob - 9cadf6d74d03b179ae017c1674c76b1eb739269d (mode 644) blob + /dev/null --- elpa/embark-consult-1.0/CHANGELOG.org +++ /dev/null @@ -1,85 +0,0 @@ -#+title: Embark changelog - -* Version 1.0 (2023-12-08) -- You can now use around action hooks with multitarget actions (that - you couldn't previously was an oversight). -- Users of the =embark-consult= package can now use consult async search - commands such as =consult-grep= as multitarget actions (through - =embark-act-all=) to search a list of files. For example, you can use - =consult-find= to search among file /names/ and once you have the - relevant files in the minibuffer, you can use =embark-act-all= to - search for some text in those files. When acting on buffers consult - async search commands will search the associated file if there is - one, or else the =default-directory= of the buffer. -- =embark-bindings= and similar commands now show definition of keyboard - macros. -- =embark-org= now recognizes Org links in non-org buffers. -- Now pressing RET in an =embark-collect= on a selection made by - using =embark-select= in a normal buffer will take you to the location - each target was collected from. -- Some functions renamed for greater consistency (these functions are - unlikely to be referred to in user's configuration): - - =embark-target-completion-at-point= → =embark-target-completion-list-candidate= - - =embark-target-top-minibuffer-completion= → =embark-target-top-minibuffer-candidate= - - =embark-completions-buffer-candidates= → =embark-completion-list-candidates= -* Version 0.23 (2023-09-19) -- Added a mode line indicator showing the number of selected targets in - the current buffer (contributed by @minad, thanks!) -- Now =embark-select= can also be called as a top-level command, from - outside =embark-act=. When called that way, it will select the first - target at point. -- =embark-org= now has support for acting on references to org headings - in other buffers, by jumping to the heading first and then running - the action. One source of references to org headings in other - buffers are agenda views: each agenda item is such a reference. But - this feature also supports some great third party commands which - produce references to org headings, such as =org-ql-find= from the - =org-ql= package or =consult-org-heading= from =consult=. -- Renamed =embark-isearch= to =embark-isearch-forward= and added - =embark-isearch-backward=. -- =embark-become= now removes any invisible text from the minibuffer - input on the grounds that users probably expect the target command - to receive exactly the input they can see. -- The meaning of the prefix argument in =embark-bindings= has flipped: - now by default global key bindings are excluded and you can use =C-u= - to include them. -- If any candidate in an embark-collect buffer contains a newline, - then candidates will be separated by horizontal lines. This is handy - for the kill-ring, which you can browse by calling =embark-collect= - from =yank-pop=. -* Version 0.22.1 (2023-04-20) -** New feature: selections -Now users can select several targets to make an ad hoc collection. The -commands =embark-act-all=, =embark-export= and =embark-collect= will act on -the selection if it is non-empty. To select or deselect a target use -the =embark-select= action (bound to =SPC= in =embark-general-map=). If you -have some targets selected, then using =embark-select= through -=embark-act-all= will deselect them. - -Before this change the Embark Collect buffers had their own -implementation of selections which has been removed. This is how to -translate the old bindings to the new feature (which is available in -all buffers, not just Embark Collect buffers!): - -| Task | Old binding | New binding | -|--------------------+-------------+---------------| -| Mark a candidate | m | a SPC | -| Unmark a candidate | u | a SPC | -| Unmark all | U | A SPC | -| Mark all [1] | t | A SPC | -| Toggle all marks | t | not available | - -[1] Marking all candidates (with either the old =t= or the new =A SPC=) -requires that there are no marked candidates to begin with. - -In order to make room for the binding of =embark-select= to -=SPC=, some other key bindings were moved: - -- =mark= in =embark-general-map= was moved to =C-SPC=. -- =outline-mark-subtree= in =embark-heading-map= was moved to =C-SPC=. -- =whitespace-cleanup-region= in =embark-region-map= was moved to =F=. - -* Version 0.21.1 (2020-01-30) -- Finally started this changelog on 2023-04-20. Known issues with the - changelog: it started very late, the first entry is not very - informative. blob - 655de6a04de6a2caa29977fb12cccfc6c80f5e80 (mode 644) blob + /dev/null --- elpa/embark-consult-1.0/README-elpa +++ /dev/null @@ -1,1392 +0,0 @@ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - EMBARK: EMACS MINI-BUFFER ACTIONS ROOTED IN - KEYMAPS - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - - - - -1 Overview -══════════ - - Embark makes it easy to choose a command to run based on what is near - point, both during a minibuffer completion session (in a way familiar - to Helm or Counsel users) and in normal buffers. Bind the command - `embark-act' to a key and it acts like prefix-key for a keymap of - /actions/ (commands) relevant to the /target/ around point. With point - on an URL in a buffer you can open the URL in a browser or eww or - download the file it points to. If while switching buffers you spot an - old one, you can kill it right there and continue to select another. - Embark comes preconfigured with over a hundred actions for common - types of targets such as files, buffers, identifiers, s-expressions, - sentences; and it is easy to add more actions and more target types. - Embark can also collect all the candidates in a minibuffer to an - occur-like buffer or export them to a buffer in a major-mode specific - to the type of candidates, such as dired for a set of files, ibuffer - for a set of buffers, or customize for a set of variables. - - -1.1 Acting on targets -───────────────────── - - You can think of `embark-act' as a keyboard-based version of a - right-click contextual menu. The `embark-act' command (which you - should bind to a convenient key), acts as a prefix for a keymap - offering you relevant /actions/ to use on a /target/ determined by the - context: - - • In the minibuffer, the target is the current top completion - candidate. - • In the `*Completions*' buffer the target is the completion at point. - • In a regular buffer, the target is the region if active, or else the - file, symbol, URL, s-expression or defun at point. - - Multiple targets can be present at the same location and you can cycle - between them by repeating the `embark-act' key binding. The type of - actions offered depend on the type of the target. Here is a sample of - a few of the actions offered in the default configuration: - - • For files you get offered actions like deleting, copying, renaming, - visiting in another window, running a shell command on the file, - etc. - • For buffers the actions include switching to or killing the buffer. - • For package names the actions include installing, removing or - visiting the homepage. - • For Emacs Lisp symbols the actions include finding the definition, - looking up documentation, evaluating (which for a variable - immediately shows the value, but for a function lets you pass it - some arguments first). There are some actions specific to variables, - such as setting the value directly or though the customize system, - and some actions specific to commands, such as binding it to a key. - - By default when you use `embark-act' if you don't immediately select - an action, after a short delay Embark will pop up a buffer showing a - list of actions and their corresponding key bindings. If you are using - `embark-act' outside the minibuffer, Embark will also highlight the - current target. These behaviors are configurable via the variable - `embark-indicators'. Instead of selecting an action via its key - binding, you can select it by name with completion by typing `C-h' - after `embark-act'. - - Everything is easily configurable: determining the current target, - classifying it, and deciding which actions are offered for each type - in the classification. The above introduction just mentions part of - the default configuration. - - Configuring which actions are offered for a type is particularly easy - and requires no programming: the variable `embark-keymap-alist' - associates target types with variables containing keymaps, and those - keymaps containing bindings for the actions. (To examine the available - categories and their associated keymaps, you can use `C-h v - embark-keymap-alist' or customize that variable.) For example, in the - default configuration the type `file' is associated with the symbol - `embark-file-map'. That symbol names a keymap with single-letter key - bindings for common Emacs file commands, for instance `c' is bound to - `copy-file'. This means that if you are in the minibuffer after - running a command that prompts for a file, such as `find-file' or - `rename-file', you can copy a file by running `embark-act' and then - pressing `c'. - - These action keymaps are very convenient but not strictly necessary - when using `embark-act': you can use any command that reads from the - minibuffer as an action and the target of the action will be inserted - at the first minibuffer prompt. After running `embark-act' all of your - key bindings and even `execute-extended-command' can be used to run a - command. For example, if you want to replace all occurrences of the - symbol at point, just use `M-%' as the action, there is no need to - bind `query-replace' in one of Embark's keymaps. Also, those action - keymaps are normal Emacs keymaps and you should feel free to bind in - them whatever commands you find useful as actions and want to be - available through convenient bindings. - - The actions in `embark-general-map' are available no matter what type - of completion you are in the middle of. By default this includes - bindings to save the current candidate in the kill ring and to insert - the current candidate in the previously selected buffer (the buffer - that was current when you executed a command that opened up the - minibuffer). - - Emacs's minibuffer completion system includes metadata indicating the - /category/ of what is being completed. For example, `find-file''s - metadata indicates a category of `file' and `switch-to-buffer''s - metadata indicates a category of `buffer'. Embark has the related - notion of the /type/ of a target for actions, and by default when - category metadata is present it is taken to be the type of minibuffer - completion candidates when used as targets. Emacs commands often do - not set useful category metadata so the [Marginalia] package, which - supplies this missing metadata, is highly recommended for use with - Embark. - - Embark's default configuration has actions for the following target - types: files, buffers, symbols, packages, URLs, bookmarks, and as a - somewhat special case, actions for when the region is active. You can - read about the [default actions and their key bindings] on the GitHub - project wiki. - - -[Marginalia] - -[default actions and their key bindings] - - - -1.2 The default action on a target -────────────────────────────────── - - Embark has a notion of default action for a target: - - • If the target is a minibuffer completion candidate, then the default - action is whatever command opened the minibuffer in the first place. - For example if you run `kill-buffer', then the default action will - be to kill buffers. - • If the target comes from a regular buffer (i.e., not a minibuffer), - then the default action is whatever is bound to `RET' in the keymap - of actions for that type of target. For example, in Embark's default - configuration for a URL found at point the default action is - `browse-url', because `RET' is bound to `browse-url' in the - `embark-url-map' keymap. - - To run the default action you can press `RET' after running - `embark-act'. Note that if there are several different targets at a - given location, each has its own default action, so first cycle to the - target you want and then press `RET' to run the corresponding default - action. - - There is also `embark-dwim' which runs the default action for the - first target found. It's pretty handy in non-minibuffer buffers: with - Embark's default configuration it will: - - • Open the file at point. - • Open the URL at point in a web browser (using the `browse-url' - command). - • Compose a new email to the email address at point. - • In an Emacs Lisp buffer, if point is on an opening parenthesis or - right after a closing one, it will evaluate the corresponding - expression. - • Go to the definition of an Emacs Lisp function, variable or macro at - point. - • Find the file corresponding to an Emacs Lisp library at point. - - -1.3 Working with sets of possible targets -───────────────────────────────────────── - - Besides acting individually on targets, Embark lets you work - collectively on a set of target /candidates/. For example, while you - are in the minibuffer the candidates are simply the possible - completions of your input. Embark provides three main commands to work - on candidate sets: - - • The `embark-act-all' command runs the same action on each of the - current candidates. It is just like using `embark-act' on each - candidate in turn. (Because you can easily act on many more - candidates than you meant to, by default Embark asks you to confirm - uses of `embark-act-all'; you can turn this off by setting the user - option `embark-confirm-act-all' to `nil'.) - - • The `embark-collect' command produces a buffer listing all the - current candidates, for you to peruse and run actions on at your - leisure. The candidates are displayed as a list showing additional - annotations. If any of the candidates contain newlines, then - horizontal lines are used to separate candidates. - - The Embark Collect buffer is somewhat "dired-like": you can select - and deselect candidates through `embark-select' (available as an - action in `embark-act', bound to `SPC'; but you could also give it a - global key binding). In an Embark Collect buffer `embark-act' is - bound to `a' and `embark-act-all' is bound to `A'; `embark-act-all' - will act on all currently marked candidates if there any, and will - act on all candidates if none are marked. In particular, this means - that `a SPC' will toggle whether the candidate at point is selected, - and `A SPC' will select all candidates if none are selected, or - deselect all selected candidates if there are some. - - • The `embark-export' command tries to open a buffer in an appropriate - major mode for the set of candidates. If the candidates are files - export produces a Dired buffer; if they are buffers, you get an - Ibuffer buffer; and if they are packages you get a buffer in package - menu mode. - - If you use the grepping commands from the [Consult] package, - `consult-grep', `consult-git-grep' or `consult-ripgrep', then you - should install the `embark-consult' package, which adds support for - exporting a list of grep results to an honest grep-mode buffer, on - which you can even use [wgrep] if you wish. - - When in doubt choosing between exporting and collecting, a good rule - of thumb is to always prefer `embark-export' since when an exporter to - a special major mode is available for a given type of target, it will - be more featureful than an Embark collect buffer, and if no such - exporter is configured the `embark-export' command falls back to the - generic `embark-collect'. - - These commands are always available as "actions" (although they do not - act on just the current target but on all candidates) for `embark-act' - and are bound to `A', `S' (for "snapshot"), and `E', respectively, in - `embark-general-map'. This means that you do not have to bind your own - key bindings for these (although you can, of course!), just a key - binding for `embark-act'. - - In Embark Collect or Embark Export buffers that were obtained by - running `embark-collect' or `embark-export' from within a minibuffer - completion session, `g' is bound to a command that restarts the - completion session, that is, the command that opened the minibuffer is - run again and the minibuffer contents restored. You can then interact - normally with the command, perhaps editing the minibuffer contents, - and, if you wish, you can rerun `embark-collect' or `embark-export' to - get an updated buffer. - - -[Consult] - -[wgrep] - -1.3.1 Selecting some targets to make an ad hoc candidate set -╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - - The commands for working with sets of candidates just described, - namely `embark-act-all', `embark-export' and `embark-collect' by - default work with all candidates defined in the current context. For - example, in the minibuffer they operate on all currently completion - candidates, or in a dired buffer they work on all marked files (or all - files if none are marked). Embark also has a notion of /selection/, - where you can accumulate an ad hoc list of targets for these commands - to work on. - - The selection is controlled by using the `embark-select' action, bound - to `SPC' in `embark-general-map' so that it is always available (you - can also give `embark-select' a global key binding if you wish; when - called directly, not as an action for `embark-act', it will select the - first target at point). Calling this action on a target toggles its - membership in the current buffer's Embark selection; that is, it adds - it to selection if not selected and removes it from the selection if - it was selected. Whenever the selection for a buffer is non-empty, the - commands `embark-act-all', `embark-export' and `embark-collect' will - act on the selection. - - To deselect all selected targets, you can use the `embark-select' - action through `embark-act-all', since this will run `embark-select' - on each member of the current selection. Similarly if no targets are - selected and you are in a minibuffer completion session, running - `embark-select' from `embark-act-all' will select all the current - completion candidates. - - By default, whenever some targets are selected in the current buffer, - a count of selected targets appears in the mode line. This can be - turned off or customized through the `embark-selection-indicator' user - option. - - The selection functionality is supported in every buffer: - - • In the minibuffer this gives a convenient way to act on several - completion candidates that don't follow any simple pattern: just go - through the completions selecting the ones you want, then use - `embark-act-all'. For example, you could attach several files at - once to an email. - • For Embark Collect buffers this functionality enables a dired-like - workflow, in which you mark various candidates and apply an action - to all at once. (It supersedes a previous ad hoc dired-like - interface that was implemented only in Embark Collect buffers, with - a slightly different interface.) - • In a eww buffer you could use this to select various links you wish - to follow up on, and then collect them into a buffer. Similarly, - while reading Emacs's info manual you could select some symbols you - want to read more about and export them to an `apropos-mode' buffer. - • You can use selections in regular text or programming buffers to do - complex editing operations. For example, if you have three - paragraphs scattered over a file and you want to bring them - together, you can select each one, insert them all somewhere and - finally delete all of them (from their original locations). - - -1.3.2 `embark-live' a live-updating variant of `embark-collect' -╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - - Finally, there is also an `embark-live' variant of the - `embark-collect' command which automatically updates the collection - after each change in the source buffer. Users of a completion UI that - automatically updates and displays the candidate list (such as - Vertico, Icomplete, Fido-mode, or MCT) will probably not want to use - `embark-live' from the minibuffer as they will then have two live - updating displays of the completion candidates! - - A more likely use of `embark-live' is to be called from a regular - buffer to display a sort of live updating "table of contents" for the - buffer. This depends on having appropriate candidate collectors - configured in `embark-candidate-collectors'. There are not many in - Embark's default configuration, but you can try this experiment: open - a dired buffer in a directory that has very many files, mark a few, - and run `embark-live'. You'll get an Embark Collect buffer containing - only the marked files, which updates as you mark or unmark files in - dired. To make `embark-live' genuinely useful other candidate - collectors are required. The `embark-consult' package (documented - near the end of this manual) contains a few: one for imenu items and - one for outline headings as used by `outline-minor-mode'. Those - collectors really do give `embark-live' a table-of-contents feel. - - -1.4 Switching to a different command without losing what you've typed -───────────────────────────────────────────────────────────────────── - - Embark also has the `embark-become' command which is useful for when - you run a command, start typing at the minibuffer and realize you - meant a different command. The most common case for me is that I run - `switch-to-buffer', start typing a buffer name and realize I haven't - opened the file I had in mind yet! I'll use this situation as a - running example to illustrate `embark-become'. When this happens I - can, of course, press `C-g' and then run `find-file' and open the - file, but this requires retyping the portion of the file name you - already typed. This process can be streamlined with `embark-become': - while still in the `switch-to-buffer' you can run `embark-become' and - effectively make the `switch-to-buffer' command become `find-file' for - this run. - - You can bind `embark-become' to a key in `minibuffer-local-map', but - it is also available as an action under the letter `B' (uppercase), so - you don't need a binding if you already have one for `embark-act'. So, - assuming I have `embark-act' bound to, say, `C-.', once I realize I - haven't open the file I can type `C-. B C-x C-f' to have - `switch-to-buffer' become `find-file' without losing what I have - already typed in the minibuffer. - - But for even more convenience, `embark-become' offers shorter key - bindings for commands you are likely to want the current command to - become. When you use `embark-become' it looks for the current command - in all keymaps named in the list `embark-become-keymaps' and then - activates all keymaps that contain it. For example, the default value - of `embark-become-keymaps' contains a keymap - `embark-become-file+buffer-map' with bindings for several commands - related to files and buffers, in particular, it binds - `switch-to-buffer' to `b' and `find-file' to `f'. So when I - accidentally try to switch to a buffer for a file I haven't opened - yet, `embark-become' finds that the command I ran, `switch-to-buffer', - is in the keymap `embark-become-file+buffer-map', so it activates that - keymap (and any others that also contain a binding for - `switch-to-buffer'). The end result is that I can type `C-. B f' to - switch to `find-file'. - - -2 Quick start -═════════════ - - The easiest way to install Embark is from GNU ELPA, just run `M-x - package-install RET embark RET'. (It is also available on MELPA.) It - is highly recommended to also install [Marginalia] (also available on - GNU ELPA), so that Embark can offer you preconfigured actions in more - contexts. For `use-package' users, the following is a very reasonable - starting configuration: - - ┌──── - │ (use-package marginalia - │ :ensure t - │ :config - │ (marginalia-mode)) - │ - │ (use-package embark - │ :ensure t - │ - │ :bind - │ (("C-." . embark-act) ;; pick some comfortable binding - │ ("C-;" . embark-dwim) ;; good alternative: M-. - │ ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' - │ - │ :init - │ - │ ;; Optionally replace the key help with a completing-read interface - │ (setq prefix-help-command #'embark-prefix-help-command) - │ - │ ;; Show the Embark target at point via Eldoc. You may adjust the Eldoc - │ ;; strategy, if you want to see the documentation from multiple providers. - │ (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target) - │ ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) - │ - │ :config - │ - │ ;; Hide the mode line of the Embark live/completions buffers - │ (add-to-list 'display-buffer-alist - │ '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" - │ nil - │ (window-parameters (mode-line-format . none))))) - │ - │ ;; Consult users will also want the embark-consult package. - │ (use-package embark-consult - │ :ensure t ; only need to install it, embark loads it after consult if found - │ :hook - │ (embark-collect-mode . consult-preview-at-point-mode)) - └──── - - About the suggested key bindings for `embark-act' and `embark-dwim': - • Those key bindings are unlikely to work in the terminal, but - terminal users are probably well aware of this and will know to - select different bindings. - • The suggested `C-.' binding is used by default in (at least some - installations of) GNOME to input emojis, and Emacs doesn't even get - a chance to respond to the binding. You can select a different key - binding for `embark-act' or use `ibus-setup' to change the shortcut - for emoji insertion (Emacs 29 will likely use `C-x 8 e e', in case - you want to set the same one system-wide). - • The suggested alternative of `M-.' for `embark-dwim' is bound by - default to `xref-find-definitions'. That is a very useful command - but overwriting it with `embark-dwim' is sensible since in Embark's - default configuration, `embark-dwim' will also find the definition - of the identifier at point. (Note that `xref-find-definitions' with - a prefix argument prompts you for an identifier, `embark-dwim' does - not cover this case). - - Other Embark commands such as `embark-act-all', `embark-become', - `embark-collect', and `embark-export' can be run through `embark-act' - as actions bound to `A', `B', `S' (for "snapshot"), and `E' - respectively, and thus don't really need a dedicated key binding, but - feel free to bind them directly if you so wish. If you do choose to - bind them directly, you'll probably want to bind them in - `minibuffer-local-map', since they are most useful in the minibuffer - (in fact, `embark-become' only works in the minibuffer). - - The command `embark-dwim' executes the default action at - point. Another good keybinding for `embark-dwim' is `M-.' since - `embark-dwim' acts like `xref-find-definitions' on the symbol at - point. `C-.' can be seen as a right-click context menu at point and - `M-.' acts like left-click. The keybindings are mnemonic, both act at - the point (`.'). - - Embark needs to know what your minibuffer completion system considers - to be the list of candidates and which one is the current candidate. - Embark works out of the box if you use Emacs's default tab completion, - the built-in `icomplete-mode' or `fido-mode', or the third-party - packages [Vertico] or [Ivy]. - - If you are a [Helm] or [Ivy] user you are unlikely to want Embark - since those packages include comprehensive functionality for acting on - minibuffer completion candidates. (Embark does come with Ivy - integration despite this.) - - -[Marginalia] - -[Vertico] - -[Ivy] - -[Helm] - - -3 Advanced configuration -════════════════════════ - -3.1 Showing information about available targets and actions -─────────────────────────────────────────────────────────── - - By default, if you run `embark-act' and do not immediately select an - action, after a short delay Embark will pop up a buffer called - `*Embark Actions*' containing a list of available actions with their - key bindings. You can scroll that buffer with the mouse of with the - usual commands `scroll-other-window' and `scroll-other-window-down' - (bound by default to `C-M-v' and `C-M-S-v'). - - That functionality is provided by the `embark-mixed-indicator', but - Embark has other indicators that can provide information about the - target and its type, what other targets you can cycle to, and which - actions have key bindings in the action map for the current type of - target. Any number of indicators can be active at once and the user - option `embark-indicators' should be set to a list of the desired - indicators. - - Embark comes with the following indicators: - - • `embark-minimal-indicator': shows a messages in the echo area or - minibuffer prompt showing the current target and the types of all - targets starting with the current one; this one is on by default. - - • `embark-highlight-indicator': highlights the target at point; also - on by default. - - • `embark-verbose-indicator': displays a table of actions and their - key bindings in a buffer; this is not on by default, in favor of the - mixed indicator described next. - - • `embark-mixed-indicator': starts out by behaving as the minimal - indicator but after a short delay acts as the verbose indicator; - this is on by default. - - • `embark-isearch-highlight-indicator': this only does something when - the current target is the symbol at point, in which case it lazily - highlights all occurrences of that symbol in the current buffer, - like isearch; also on by default. - - Users of the popular [which-key] package may prefer to use the - `embark-which-key-indicator' from the [Embark wiki]. Just copy its - definition from the wiki into your configuration and customize the - `embark-indicators' user option to exclude the mixed and verbose - indicators and to include `embark-which-key-indicator'. - - -[which-key] - -[Embark wiki] - - - -3.2 Selecting commands via completions instead of key bindings -────────────────────────────────────────────────────────────── - - As an alternative to reading the list of actions in the verbose or - mixed indicators (see the previous section for a description of - these), you can press the `embark-help-key', which is `C-h' by default - (but you may prefer `?' to free up `C-h' for use as a prefix) after - running `embark-act'. Pressing the help key will prompt you for the - name of an action with completion (but feel free to enter a command - that is not among the offered candidates!), and will also remind you - of the key bindings. You can press `embark-keymap-prompter-key', which - is `@' by default, at the prompt and then one of the key bindings to - enter the name of the corresponding action. - - You may think that with the `*Embark Actions*' buffer popping up to - remind you of the key bindings you'd never want to use completion to - select an action by name, but personally I find that typing a small - portion of the action name to narrow down the list of candidates feels - significantly faster than visually scanning the entire list of - actions. - - If you find you prefer entering actions that way, you can configure - embark to always prompt you for actions by setting the variable - `embark-prompter' to `embark-completing-read-prompter'. - - -3.3 Quitting the minibuffer after an action -─────────────────────────────────────────── - - By default, if you call `embark-act' from the minibuffer it quits the - minibuffer after performing the action. You can change this by setting - the user option `embark-quit-after-action' to `nil'. Having - `embark-act' /not/ quit the minibuffer can be useful to turn commands - into little "thing managers". For example, you can use `find-file' as - a little file manager or `describe-package' as a little package - manager: you can run those commands, perform a series of actions, and - then quit the command. - - If you want to control the quitting behavior in a fine-grained manner - depending on the action, you can set `embark-quit-after-action' to an - alist, associating commands to either `t' for quitting or `nil' for - not quitting. When using an alist, you can use the special key `t' to - specify the default behavior. For example, to specify that by default - actions should not quit the minibuffer but that using `kill-buffer' as - an action should quit, you can use the following configuration: - - ┌──── - │ (setq embark-quit-after-action '((kill-buffer . t) (t . nil))) - └──── - - The variable `embark-quit-after-action' only specifies a default, that - is, it only controls whether or not `embark-act' quits the minibuffer - when you call it without a prefix argument, and you can select the - opposite behavior to what the variable says by calling `embark-act' - with `C-u'. Also note that both the variable - `embark-quit-after-action' and `C-u' have no effect when you call - `embark-act' outside the minibuffer. - - If you find yourself using the quitting and non-quitting variants of - `embark-act' about equally often, independently of the action, you may - prefer to simply have separate commands for them instead of a single - command that you call with `C-u' half the time. You could, for - example, keep the default exiting behavior of `embark-act' and define - a non-quitting version as follows: - - ┌──── - │ (defun embark-act-noquit () - │ "Run action but don't quit the minibuffer afterwards." - │ (interactive) - │ (let ((embark-quit-after-action nil)) - │ (embark-act))) - └──── - - -3.4 Running some setup after injecting the target -───────────────────────────────────────────────── - - You can customize what happens after the target is inserted at the - minibuffer prompt of an action. There are - `embark-target-injection-hooks', that are run by default after - injecting the target into the minibuffer. The variable - `embark-target-injection-hooks' is an alist associating commands to - their setup hooks. There are two special keys: if no setup hook is - specified for a given action, the hook associated to `t' is run; and - the hook associated to `:always' is run regardless of the - action. (This variable used to have the less explicit name of - `embark-setup-action-hooks', so please update your configuration.) - - For example, consider using `shell-command' as an action during file - completion. It would be useful to insert a space before the target - file name and to leave the point at the beginning, so you can - immediately type the shell command to run on that file. That's why in - Embark's default configuration there is an entry in - `embark-target-injection-hooks' associating `shell-command' to a hook - that includes `embark--shell-prep', a simple helper function that - quotes all the spaces in the file name, inserts an extra space at the - beginning of the line and leaves point to the left of it. - - Now, the preparation that `embark--shell-prep' does would be useless - if Embark did what it normally does after it inserts the target of the - action at the minibuffer prompt, which is to "press `RET'" for you, - accepting the target as is; if Embark did that for `shell-command' you - wouldn't get a chance to type in the command to execute! That is why - in Embark's default configuration the entry for `shell-command' in - `embark-target-injection-hooks' also contains the function - `embark--allow-edit'. - - Embark used to have a dedicated variable `embark-allow-edit-actions' - to which you could add commands for which Embark should forgo pressing - `RET' for you after inserting the target. Since its effect can also be - achieved via the general `embark-target-injection-hooks' mechanism, - that variable has been removed to simplify Embark. Be sure to update - your configuration; if you had something like: - - ┌──── - │ (add-to-list 'embark-allow-edit-actions 'my-command) - └──── - - you should replace it with: - - ┌──── - │ (push 'embark--allow-edit - │ (alist-get 'my-command embark-target-injection-hooks)) - └──── - - - Also note that while you could abuse `embark--allow-edit' so that you - have to confirm "dangerous" actions such as `delete-file', it is - better to implement confirmation by adding the `embark--confirm' - function to the appropriate entry of a different hook alist, namely, - `embark-pre-action-hooks'. - - Besides `embark--allow-edit', Embark comes with another function that - is of general utility in action setup hooks: - `embark--ignore-target'. Use it for commands that do prompt you in the - minibuffer but for which inserting the target would be - inappropriate. This is not a common situation but does occasionally - arise. For example it is used by default for - `shell-command-on-region': that command is used as an action for - region targets, and it prompts you for a shell command; you typically - do /not/ want the target, that is the contents of the region, to be - entered at that prompt! - - -3.5 Running hooks before, after or around an action -─────────────────────────────────────────────────── - - Embark has three variables, `embark-pre-action-hooks', - `embark-post-action-hooks' and `embark-around-action-hooks', which are - alists associating commands to hooks that should run before or after - or as around advice for the command when used as an action. As with - `embark-target-injection-hooks', there are two special keys for the - alists: `t' designates the default hook to run when no specific hook - is specified for a command; and the hook associated to `:always' runs - regardless. - - The default values of those variables are fairly extensive, adding - creature comforts to make running actions a smooth experience. Embark - comes with several functions intended to be added to these hooks, and - used in the default values of `embark-pre-action-hooks', - `embark-post-action-hooks' and `embark-around-action-hooks'. - - For pre-action hooks: - - `embark--confirm' - Prompt the user for confirmation before executing the - action. This is used be default for commands deemed "dangerous", - or, more accurately, hard to undo, such as `delete-file' and - `kill-buffer'. - - `embark--unmark-target' - Unmark the active region. Use this for commands you want to act - on the region contents but without the region being active. The - default configuration uses this function as a pre-action hook - for `occur' and `query-replace', for example, so that you can - use them as actions with region targets to search the whole - buffer for the text contained in the region. Without this - pre-action hook using `occur' as an action for a region target - would be pointless: it would search for the the region contents - /in the region/, (typically, due to the details of regexps) - finding only one match! - - `embark--beginning-of-target' - Move to the beginning of the target (for targets that report - bounds). This is used by default for backward motion commands - such as `backward-sexp', so that they don't accidentally leave - you on the current target. - - `embark--end-of-target' - Move to the end of the target. This is used similarly to the - previous function, but also for commands that act on the last - s-expression like `eval-last-sexp'. This allow you to act on an - s-expression from anywhere inside it and still use - `eval-last-sexp' as an action. - - `embark--xref-push-markers' - Push the current location on the xref marker stack. Use this for - commands that take you somewhere and for which you'd like to be - able to come back to where you were using - `xref-pop-marker-stack'. This is used by default for - `find-library'. - - For post-action hooks: - - `embark--restart' - Restart the command currently prompting in the minibuffer, so - that the list of completion candidates is updated. This is - useful as a post action hook for commands that delete or rename - a completion candidate; for example the default value of - `embark-post-action-hooks' uses it for `delete-file', - `kill-buffer', `rename-file', `rename-buffer', etc. - - For around-action hooks: - - `embark--mark-target' - Save existing mark and point location, mark the target and run - the action. Most targets at point outside the minibuffer report - which region of the buffer they correspond to (this is the - information used by `embark-highlight-indicator' to know what - portion of the buffer to highlight); this function marks that - region. It is useful as an around action hook for commands that - expect a region to be marked, for example, it is used by default - for `indent-region' so that it works on s-expression targets, or - for `fill-region' so that it works on paragraph targets. - - `embark--cd' - Run the action with `default-directory' set to the directory - associated to the current target. The target should be of type - `file', `buffer', `bookmark' or `library', and the associated - directory is what you'd expect in each case. - - `embark--narrow-to-target' - Run the action with buffer narrowed to current target. Use this - as an around hook to localize the effect of actions that don't - already work on just the region. In the default configuration it - is used for `repunctuate-sentences'. - - `embark--save-excursion' - Run the action restoring point at the end. The current default - configuration doesn't use this but it is available for users. - - -3.6 Creating your own keymaps -───────────────────────────── - - All internal keymaps are defined with the standard helper macro - `defvar-keymap'. For example a simple version of the file action - keymap could be defined as follows: - - ┌──── - │ (defvar-keymap embark-file-map - │ :doc "Example keymap with a few file actions" - │ :parent embark-general-map - │ "d" #'delete-file - │ "r" #'rename-file - │ "c" #'copy-file) - └──── - - These action keymaps are perfectly normal Emacs keymaps. You may want - to inherit from the `embark-general-map' if you want to access the - default Embark actions. Note that `embark-collect' and `embark-export' - are also made available via `embark-general-map'. - - -3.7 Defining actions for new categories of targets -────────────────────────────────────────────────── - - It is easy to configure Embark to provide actions for new types of - targets, either in the minibuffer or outside it. I present below two - very detailed examples of how to do this. At several points I'll - explain more than one way to proceed, typically with the easiest - option first. I include the alternative options since there will be - similar situations where the easiest option is not available. - - -3.7.1 New minibuffer target example - tab-bar tabs -╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - - As an example, take the new [tab bars] from Emacs 27. I'll explain how - to configure Embark to offer tab-specific actions when you use the - tab-bar-mode commands that mention tabs by name. The configuration - explained here is now built-in to Embark (and Marginalia), but it's - still a good self-contained example. In order to setup up tab actions - you would need to: (1) make sure Embark knows those commands deal with - tabs, (2) define a keymap for tab actions and configure Embark so it - knows that's the keymap you want. - - -[tab bars] - - -◊ 3.7.1.1 Telling Embark about commands that prompt for tabs by name - - For step (1), it would be great if the `tab-bar-mode' commands - reported the completion category `tab' when asking you for a tab with - completion. (All built-in Emacs commands that prompt for file names, - for example, do have metadata indicating that they want a `file'.) - They do not, unfortunately, and I will describe a couple of ways to - deal with this. - - Maybe the easiest thing is to configure [Marginalia] to enhance those - commands. All of the `tab-bar-*-tab-by-name' commands have the words - "tab by name" in the minibuffer prompt, so you can use: - - ┌──── - │ (add-to-list 'marginalia-prompt-categories '("tab by name" . tab)) - └──── - - That's it! But in case you are ever in a situation where you don't - already have commands that prompt for the targets you want, I'll - describe how writing your own command with appropriate `category' - metadata looks: - - ┌──── - │ (defun my-select-tab-by-name (tab) - │ (interactive - │ (list - │ (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) - │ (tab-bar-tabs)) - │ (user-error "No tabs found")))) - │ (completing-read - │ "Tabs: " - │ (lambda (string predicate action) - │ (if (eq action 'metadata) - │ '(metadata (category . tab)) - │ (complete-with-action - │ action tab-list string predicate))))))) - │ (tab-bar-select-tab-by-name tab)) - └──── - - As you can see, the built-in support for setting the category - meta-datum is not very easy to use or pretty to look at. To help with - this I recommend the `consult--read' function from the excellent - [Consult] package. With that function we can rewrite the command as - follows: - - ┌──── - │ (defun my-select-tab-by-name (tab) - │ (interactive - │ (list - │ (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) - │ (tab-bar-tabs)) - │ (user-error "No tabs found")))) - │ (consult--read tab-list - │ :prompt "Tabs: " - │ :category 'tab)))) - │ (tab-bar-select-tab-by-name tab)) - └──── - - Much nicer! No matter how you define the `my-select-tab-by-name' - command, the first approach with Marginalia and prompt detection has - the following advantages: you get the `tab' category for all the - `tab-bar-*-bar-by-name' commands at once, also, you enhance built-in - commands, instead of defining new ones. - - - [Marginalia] - - [Consult] - - -◊ 3.7.1.2 Defining and configuring a keymap for tab actions - - Let's say we want to offer select, rename and close actions for tabs - (in addition to Embark general actions, such as saving the tab name to - the kill-ring, which you get for free). Then this will do: - - ┌──── - │ (defvar-keymap embark-tab-actions - │ :doc "Keymap for actions for tab-bar tabs (when mentioned by name)." - │ :parent embark-general-map - │ "s" #'tab-bar-select-tab-by-name - │ "r" #'tab-bar-rename-tab-by-name - │ "k" #'tab-bar-close-tab-by-name) - │ - │ (add-to-list 'embark-keymap-alist '(tab . embark-tab-actions)) - └──── - - What if after using this for a while you feel closing the tab without - confirmation is dangerous? You have a couple of options: - - 1. You can keep using the `tab-bar-close-tab-by-name' command, but - have Embark ask you for confirmation: - ┌──── - │ (push #'embark--confirm - │ (alist-get 'tab-bar-close-tab-by-name - │ embark-pre-action-hooks)) - └──── - - 2. You can write your own command that prompts for confirmation and - use that instead of `tab-bar-close-tab-by-name' in the above - keymap: - ┌──── - │ (defun my-confirm-close-tab-by-name (tab) - │ (interactive "sTab to close: ") - │ (when (y-or-n-p (format "Close tab '%s'? " tab)) - │ (tab-bar-close-tab-by-name tab))) - └──── - - Notice that this is a command you can also use directly from `M-x' - independently of Embark. Using it from `M-x' leaves something to be - desired, though, since you don't get completion for the tab names. - You can fix this if you wish as described in the previous section. - - -3.7.2 New target example in regular buffers - short Wikipedia links -╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ - - Say you want to teach Embark to treat text of the form - `wikipedia:Garry_Kasparov' in any regular buffer as a link to - Wikipedia, with actions to open the Wikipedia page in eww or an - external browser or to save the URL of the page in the kill-ring. We - can take advantage of the actions that Embark has preconfigured for - URLs, so all we need to do is teach Embark that - `wikipedia:Garry_Kasparov' stands for the URL - `https://en.wikipedia.org/wiki/Garry_Kasparov'. - - You can be as fancy as you want with the recognized syntax. Here, to - keep the example simple, I'll assume the link matches the regexp - `wikipedia:[[:alnum:]_]+'. We will write a function that looks for a - match surrounding point, and returns a dotted list of the form `'(url - URL-OF-THE-PAGE START . END)' where `START' and `END' are the buffer - positions bounding the target, and are used by Embark to highlight it - if you have `embark-highlight-indicator' included in the list - `embark-indicators'. (There are a couple of other options for the - return value of a target finder: the bounding positions are optional - and a single target finder is allowed to return multiple targets; see - the documentation for `embark-target-finders' for details.) - - ┌──── - │ (defun my-short-wikipedia-link () - │ "Target a link at point of the form wikipedia:Page_Name." - │ (save-excursion - │ (let* ((start (progn (skip-chars-backward "[:alnum:]_:") (point))) - │ (end (progn (skip-chars-forward "[:alnum:]_:") (point))) - │ (str (buffer-substring-no-properties start end))) - │ (save-match-data - │ (when (string-match "wikipedia:\\([[:alnum:]_]+\\)" str) - │ `(url - │ ,(format "https://en.wikipedia.org/wiki/%s" - │ (match-string 1 str)) - │ ,start . ,end)))))) - │ - │ (add-to-list 'embark-target-finders 'my-short-wikipedia-link) - └──── - - -4 How does Embark call the actions? -═══════════════════════════════════ - - Embark actions are normal Emacs commands, that is, functions with an - interactive specification. In order to execute an action, Embark calls - the command with `call-interactively', so the command reads user input - exactly as if run directly by the user. For example the command may - open a minibuffer and read a string (`read-from-minibuffer') or open a - completion interface (`completing-read'). If this happens, Embark - takes the target string and inserts it automatically into the - minibuffer, simulating user input this way. After inserting the - string, Embark exits the minibuffer, submitting the input. (The - immediate minibuffer exit can be disabled for specific actions in - order to allow editing the input; this is done by adding the - `embark--allow-edit' function to the appropriate entry of - `embark-target-injection-hooks'). Embark inserts the target string at - the first minibuffer opened by the action command, and if the command - happens to prompt the user for input more than once, the user still - interacts with the second and further prompts in the normal - fashion. Note that if a command does not prompt the user for input in - the minibuffer, Embark still allows you to use it as an action, but of - course, never inserts the target anywhere. (There are plenty of - examples in the default configuration of commands that do not prompt - the user bound to keys in the action maps, most of the region actions, - for instance.) - - This is how Embark manages to reuse normal commands as actions. The - mechanism allows you to use as Embark actions commands that were not - written with Embark in mind (and indeed almost all actions that are - bound by default in Embark's action keymaps are standard Emacs - commands). It also allows you to write new custom actions in such a - way that they are useful even without Embark. - - Staring from version 28.1, Emacs has a variable - `y-or-n-p-use-read-key', which when set to `t' causes `y-or-n-p' to - use `read-key' instead of `read-from-minibuffer'. Setting - `y-or-n-p-use-read-key' to `t' is recommended for Embark users because - it keeps Embark from attempting to insert the target at a `y-or-n-p' - prompt, which would almost never be sensible. Also consider this as a - warning to structure your own action commands so that if they use - `y-or-n-p', they do so only after the prompting for the target. - - Here is a simple example illustrating the various ways of reading - input from the user mentioned above. Bind the following commands to - the `embark-symbol-map' to be used as actions, then put the point on - some symbol and run them with `embark-act': - - ┌──── - │ (defun example-action-command1 () - │ (interactive) - │ (message "The input was `%s'." (read-from-minibuffer "Input: "))) - │ - │ (defun example-action-command2 (arg input1 input2) - │ (interactive "P\nsInput 1: \nsInput 2: ") - │ (message "The first input %swas `%s', and the second was `%s'." - │ (if arg "truly " "") - │ input1 - │ input2)) - │ - │ (defun example-action-command3 () - │ (interactive) - │ (message "Your selection was `%s'." - │ (completing-read "Select: " '("E" "M" "B" "A" "R" "K")))) - │ - │ (defun example-action-command4 () - │ (interactive) - │ (message "I don't prompt you for input and thus ignore the target!")) - │ - │ (keymap-set embark-symbol-map "X 1" #'example-action-command1) - │ (keymap-set embark-symbol-map "X 2" #'example-action-command2) - │ (keymap-set embark-symbol-map "X 3" #'example-action-command3) - │ (keymap-set embark-symbol-map "X 4" #'example-action-command4) - └──── - - Also note that if you are using the key bindings to call actions, you - can pass prefix arguments to actions in the normal way. For example, - you can use `C-u X2' with the above demonstration actions to make the - message printed by `example-action-command2' more emphatic. This - ability to pass prefix arguments to actions is useful for some actions - in the default configuration, such as - `embark-shell-command-on-buffer'. - - -4.1 Non-interactive functions as actions -──────────────────────────────────────── - - Alternatively, Embark does support one other type of action: a - non-interactive function of a single argument. The target is passed as - argument to the function. For example: - - ┌──── - │ (defun example-action-function (target) - │ (message "The target was `%s'." target)) - │ - │ (keymap-set embark-symbol-map "X 4" #'example-action-function) - └──── - - Note that normally binding non-interactive functions in a keymap is - useless, since when attempting to run them using the key binding you - get an error message similar to "Wrong type argument: commandp, - example-action-function". In general it is more flexible to write any - new Embark actions as commands, that is, as interactive functions, - because that way you can also run them directly, without Embark. But - there are a couple of reasons to use non-interactive functions as - actions: - - 1. You may already have the function lying around, and it is - convenient to simply reuse it. - - 2. For command actions the targets can only be simple string, with no - text properties. For certain advanced uses you may want the action - to receive a string /with/ some text properties, or even a - non-string target. - - -5 Embark, Marginalia and Consult -════════════════════════════════ - - Embark cooperates well with the [Marginalia] and [Consult] packages. - Neither of those packages is a dependency of Embark, but both are - highly recommended companions to Embark, for opposite reasons: - Marginalia greatly enhances Embark's usefulness, while Embark can help - enhance Consult. - - In the remainder of this section I'll explain what exactly Marginalia - does for Embark, and what Embark can do for Consult. - - -[Marginalia] - -[Consult] - -5.1 Marginalia -────────────── - - Embark comes with actions for symbols (commands, functions, variables - with actions such as finding the definition, looking up the - documentation, evaluating, etc.) in the `embark-symbol-map' keymap, - and for packages (actions like install, delete, browse url, etc.) in - the `embark-package-keymap'. - - Unfortunately Embark does not automatically offers you these keymaps - when relevant, because many built-in Emacs commands don't report - accurate category metadata. For example, a command like - `describe-package', which reads a package name from the minibuffer, - does not have metadata indicating this fact. - - In an earlier Embark version, there were functions to supply this - missing metadata, but they have been moved to Marginalia, which - augments many Emacs command to report accurate category metadata. - Simply activating `marginalia-mode' allows Embark to offer you the - package and symbol actions when appropriate again. Candidate - annotations in the Embark collect buffer are also provided by the - Marginalia package: - - • If you install Marginalia and activate `marginalia-mode', Embark - Collect buffers will use the Marginalia annotations automatically. - - • If you don't install Marginalia, you will see only the annotations - that come with Emacs (such as key bindings in `M-x', or the unicode - characters in `C-x 8 RET'). - - -5.2 Consult -─────────── - - The excellent Consult package provides many commands that use - minibuffer completion, via the `completing-read' function; plenty of - its commands can be considered enhanced versions of built-in Emacs - commands, and some are completely new functionality. One common - enhancement provided in all commands for which it makes sense is - preview functionality, for example `consult-buffer' will show you a - quick preview of a buffer before you actually switch to it. - - If you use both Consult and Embark you should install the - `embark-consult' package which provides integration between the - two. It provides exporters for several Consult commands and also - tweaks the behavior of many Consult commands when used as actions with - `embark-act' in subtle ways that you may not even notice, but make for - a smoother experience. You need only install it to get these benefits: - Embark will automatically load it after Consult if found. - - The `embark-consult' package provides the following exporters: - - • You can use `embark-export' from `consult-line', `consult-outline', - or `consult-mark' to obtain an `occur-mode' buffer. As with the - built-in `occur' command you use that buffer to jump to a match and - after that, you can then use `next-error' and `previous-error' to - navigate to other matches. You can also press `e' to activate - `occur-edit-mode' and edit the matches in place! - - • You can export from any of the Consult asynchronous search commands, - `consult-grep', `consult-git-grep', or `consult-ripgrep' to get a - `grep-mode' buffer. Here too you can use `next-error' and - `previous-error' to navigate among matches, and, if you install the - [wgrep] package, you can use it to edit the matches in place. - - In both cases, pressing `g' will rerun the Consult command you had - exported from and re-enter the input you had typed (which is similar - to reverting but a little more flexible). You can then proceed to - re-export if that's what you want, but you can also edit the input - changing the search terms or simply cancel if you see you are done - with that search. - - The `embark-consult' also contains some candidates collectors that - allow you to run `embark-live' to get a live-updating table of - contents for your buffer: - - • `embark-consult-outline-candidates' produces the outline headings of - the current buffer, using `consult-outline'. - • `embark-consult-imenu-candidates' produces the imenu items of the - current buffer, using `consult-imenu'. - • `embark-consult-imenu-or-outline-candidates' is a simple combination - of the two previous functions: it produces imenu items in buffers - deriving from `prog-mode' and otherwise outline headings. - - The way to configure `embark-live' (or `embark-collect' and - `embark-export' for that matter) to use one of these function is to - add it at the end of the `embark-candidate-collectors' list. The - `embark-consult' package by default adds the last one, which seems to - be the most sensible default. - - Besides those exporters and candidate collectors, the `embark-consult' - package provides many subtle tweaks and small integrations between - Embark and Consult. Some examples are: - - • When used as actions, the asynchronous search commands will search - only the files associated to the targets: if the targets /are/ - files, it searches those files; for buffers it will search either - the associated file if there is one, else all files in the buffer's - `default-directory'; for bookmarks it will search the file they - point to, same for Emacs Lisp libraries. This is particularly - powerful when using `embark-act-all' to act on multiple files at - once, for example you can use `consult-find' to search among file - /names/ and then `embark-act-all' and `consult-grep' to search - within the matching files. - - • For all other target types, those that do not have a sensible - notion of associated file, a Consult search command (asynchronous - or not) will search for the text of the target but leave the - minibuffer open so you can interact with the Consult command. - - • `consult-imenu' will search for the target and take you directly to - the location if it matches a unique imenu entry, otherwise it will - leave the minibuffer open so you can navigate among the matches. - - -[wgrep] - - -6 Related Packages -══════════════════ - - There are several packages that offer functionality similar to - Embark's. - - Acting on minibuffer completion candidates - The popular Ivy and Helm packages have support for acting on the - completion candidates of commands written using their APIs, and - there is an extensive ecosystem of packages meant for Helm and - for Ivy (the Ivy ones usually have "counsel" in the name) - providing commands and appropriate actions. - Acting on things at point - The built-in `context-menu-mode' provides a mouse-driven - context-sensitive configurable menu. The `do-at-point' package - by Philip Kaludercic (available on GNU ELPA), on the other hand - is keyboard-driven. - Collecting completion candidates into a buffer - The Ivy package has the command `ivy-occur' which is similar to - `embark-collect'. As with Ivy actions, `ivy-occur' only works - for commands written using the Ivy API. - - -7 Resources -═══════════ - - If you want to learn more about how others have used Embark here are - some links to read: - - • [Fifteen ways to use Embark], a blog post by Karthik Chikmagalur. - • [Protesilaos Stavrou's dotemacs], look for the section called - "Extended minibuffer actions and more (embark.el and - prot-embark.el)" - - And some videos to watch: - - • [Embark and my extras] by Protesilaos Stavrou. - • [Embark – Key features and tweaks] by Raoul Comninos on the - Emacs-Elements YouTube channel. - • [Livestreamed: Adding an Embark context action to send a stream - message] by Sacha Chua. - • [System Crafters Live! - The Many Uses of Embark] by David Wilson. - • [Using Emacs Episode 80 - Vertico, Marginalia, Consult and Embark] - by Mike Zamansky. - - -[Fifteen ways to use Embark] - - -[Protesilaos Stavrou's dotemacs] - -[Embark and my extras] - - -[Embark – Key features and tweaks] - -[Livestreamed: Adding an Embark context action to send a stream message] - - -[System Crafters Live! - The Many Uses of Embark] - - -[Using Emacs Episode 80 - Vertico, Marginalia, Consult and Embark] - - - -8 Contributions -═══════════════ - - Contributions to Embark are very welcome. There is a [wish list] for - actions, target finders, candidate collectors and exporters. For other - ideas you have for Embark, feel free to open an issue on the [issue - tracker]. Any neat configuration tricks you find might be a good fit - for the [wiki]. - - Code contributions are very welcome too, but since Embark is now on - GNU ELPA, copyright assignment to the FSF is required before you can - contribute code. - - -[wish list] - -[issue tracker] - -[wiki] - - -9 Acknowledgments -═════════════════ - - While I, Omar Antolín Camarena, have written most of the Embark code - and remain very stubborn about some of the design decisions, Embark - has received substantial help from a number of other people which this - document has neglected to mention for far too long. In particular, - Daniel Mendler has been absolutely invaluable, implementing several - important features, and providing a lot of useful advice. - - Code contributions: - - • [Daniel Mendler] - • [Clemens Radermacher] - • [José Antonio Ortega Ruiz] - • [Itai Y. Efrat] - • [a13] - • [jakanakaevangeli] - • [mihakam] - • [Brian Leung] - • [Karthik Chikmagalur] - • [Roshan Shariff] - • [condy0919] - • [Damien Cassou] - • [JimDBh] - - Advice and useful discussions: - - • [Daniel Mendler] - • [Protesilaos Stavrou] - • [Clemens Radermacher] - • [Howard Melman] - • [Augusto Stoffel] - • [Bruce d'Arcus] - • [JD Smith] - • [Karthik Chikmagalur] - • [jakanakaevangeli] - • [Itai Y. Efrat] - • [Mohsin Kaleem] - - -[Daniel Mendler] - -[Clemens Radermacher] - -[José Antonio Ortega Ruiz] - -[Itai Y. Efrat] - -[a13] - -[jakanakaevangeli] - -[mihakam] - -[Brian Leung] - -[Karthik Chikmagalur] - -[Roshan Shariff] - -[condy0919] - -[Damien Cassou] - -[JimDBh] - -[Protesilaos Stavrou] - -[Howard Melman] - -[Augusto Stoffel] - -[Bruce d'Arcus] - -[JD Smith] - -[Mohsin Kaleem] blob - a010932c153e26f07bde44b61c8c9456caab9761 (mode 644) blob + /dev/null --- elpa/embark-consult-1.0/README.org +++ /dev/null @@ -1,1206 +0,0 @@ -#+TITLE: Embark: Emacs Mini-Buffer Actions Rooted in Keymaps -#+OPTIONS: d:nil -#+EXPORT_FILE_NAME: embark.texi -#+TEXINFO_DIR_CATEGORY: Emacs misc features -#+TEXINFO_DIR_TITLE: Embark: (embark). -#+TEXINFO_DIR_DESC: Emacs Mini-Buffer Actions Rooted in Keymaps - -#+html: GNU ELPA -#+html: GNU-devel ELPA -#+html: MELPA -#+html: MELPA Stable - -* Overview - -Embark makes it easy to choose a command to run based on what is near -point, both during a minibuffer completion session (in a way familiar -to Helm or Counsel users) and in normal buffers. Bind the command -=embark-act= to a key and it acts like prefix-key for a keymap of -/actions/ (commands) relevant to the /target/ around point. With point on -an URL in a buffer you can open the URL in a browser or eww or -download the file it points to. If while switching buffers you spot an -old one, you can kill it right there and continue to select another. -Embark comes preconfigured with over a hundred actions for common -types of targets such as files, buffers, identifiers, s-expressions, -sentences; and it is easy to add more actions and more target types. -Embark can also collect all the candidates in a minibuffer to an -occur-like buffer or export them to a buffer in a major-mode specific -to the type of candidates, such as dired for a set of files, ibuffer -for a set of buffers, or customize for a set of variables. - -** Acting on targets - -You can think of =embark-act= as a keyboard-based version of a -right-click contextual menu. The =embark-act= command (which you should -bind to a convenient key), acts as a prefix for a keymap offering you -relevant /actions/ to use on a /target/ determined by the context: - -- In the minibuffer, the target is the current top completion - candidate. -- In the =*Completions*= buffer the target is the completion at point. -- In a regular buffer, the target is the region if active, or else the - file, symbol, URL, s-expression or defun at point. - -Multiple targets can be present at the same location and you can cycle -between them by repeating the =embark-act= key binding. The type of -actions offered depend on the type of the target. Here is a sample of -a few of the actions offered in the default configuration: - -- For files you get offered actions like deleting, copying, - renaming, visiting in another window, running a shell command on the - file, etc. -- For buffers the actions include switching to or killing the buffer. -- For package names the actions include installing, removing or - visiting the homepage. -- For Emacs Lisp symbols the actions include finding the definition, - looking up documentation, evaluating (which for a variable - immediately shows the value, but for a function lets you pass it - some arguments first). There are some actions specific to variables, - such as setting the value directly or though the customize system, - and some actions specific to commands, such as binding it to a key. - -By default when you use =embark-act= if you don't immediately select an -action, after a short delay Embark will pop up a buffer showing a list -of actions and their corresponding key bindings. If you are using -=embark-act= outside the minibuffer, Embark will also highlight the -current target. These behaviors are configurable via the variable -=embark-indicators=. Instead of selecting an action via its key binding, -you can select it by name with completion by typing =C-h= after -=embark-act=. - -Everything is easily configurable: determining the current target, -classifying it, and deciding which actions are offered for each type -in the classification. The above introduction just mentions part of -the default configuration. - -Configuring which actions are offered for a type is particularly easy -and requires no programming: the variable =embark-keymap-alist= -associates target types with variables containing keymaps, and those -keymaps containing bindings for the actions. (To examine the available -categories and their associated keymaps, you can use =C-h v -embark-keymap-alist= or customize that variable.) For example, in the -default configuration the type =file= is associated with the symbol -=embark-file-map=. That symbol names a keymap with single-letter key -bindings for common Emacs file commands, for instance =c= is bound to -=copy-file=. This means that if you are in the minibuffer after running -a command that prompts for a file, such as =find-file= or =rename-file=, -you can copy a file by running =embark-act= and then pressing =c=. - -These action keymaps are very convenient but not strictly necessary -when using =embark-act=: you can use any command that reads from the -minibuffer as an action and the target of the action will be inserted -at the first minibuffer prompt. After running =embark-act= all of your -key bindings and even =execute-extended-command= can be used to run a -command. For example, if you want to replace all occurrences of the -symbol at point, just use =M-%= as the action, there is no need to bind -=query-replace= in one of Embark's keymaps. Also, those action keymaps -are normal Emacs keymaps and you should feel free to bind in them -whatever commands you find useful as actions and want to be available -through convenient bindings. - -The actions in =embark-general-map= are available no matter what type -of completion you are in the middle of. By default this includes -bindings to save the current candidate in the kill ring and to insert -the current candidate in the previously selected buffer (the buffer -that was current when you executed a command that opened up the -minibuffer). - -Emacs's minibuffer completion system includes metadata indicating the -/category/ of what is being completed. For example, =find-file='s -metadata indicates a category of =file= and =switch-to-buffer='s metadata -indicates a category of =buffer=. Embark has the related notion of the -/type/ of a target for actions, and by default when category metadata -is present it is taken to be the type of minibuffer completion -candidates when used as targets. Emacs commands often do not set -useful category metadata so the [[https://github.com/minad/marginalia][Marginalia]] package, which supplies -this missing metadata, is highly recommended for use with Embark. - -Embark's default configuration has actions for the following target -types: files, buffers, symbols, packages, URLs, bookmarks, and as a -somewhat special case, actions for when the region is active. You can -read about the [[https://github.com/oantolin/embark/wiki/Default-Actions][default actions and their key bindings]] on the GitHub -project wiki. - -** The default action on a target - -Embark has a notion of default action for a target: - -- If the target is a minibuffer completion candidate, then the default - action is whatever command opened the minibuffer in the first place. - For example if you run =kill-buffer=, then the default action will be - to kill buffers. -- If the target comes from a regular buffer (i.e., not a minibuffer), - then the default action is whatever is bound to =RET= in the keymap of - actions for that type of target. For example, in Embark's default - configuration for a URL found at point the default action is - =browse-url=, because =RET= is bound to =browse-url= in the =embark-url-map= - keymap. - -To run the default action you can press =RET= after running =embark-act=. -Note that if there are several different targets at a given location, -each has its own default action, so first cycle to the target you want -and then press =RET= to run the corresponding default action. - -There is also =embark-dwim= which runs the default action for the first -target found. It's pretty handy in non-minibuffer buffers: with -Embark's default configuration it will: - -- Open the file at point. -- Open the URL at point in a web browser (using the =browse-url= - command). -- Compose a new email to the email address at point. -- In an Emacs Lisp buffer, if point is on an opening parenthesis or - right after a closing one, it will evaluate the corresponding - expression. -- Go to the definition of an Emacs Lisp function, variable or macro at - point. -- Find the file corresponding to an Emacs Lisp library at point. - -** Working with sets of possible targets - -Besides acting individually on targets, Embark lets you work -collectively on a set of target /candidates/. For example, while you are -in the minibuffer the candidates are simply the possible completions -of your input. Embark provides three main commands to work on candidate -sets: - -- The =embark-act-all= command runs the same action on each of the - current candidates. It is just like using =embark-act= on each - candidate in turn. (Because you can easily act on many more - candidates than you meant to, by default Embark asks you to confirm - uses of =embark-act-all=; you can turn this off by setting the user - option =embark-confirm-act-all= to =nil=.) - -- The =embark-collect= command produces a buffer listing all the current - candidates, for you to peruse and run actions on at your leisure. - The candidates are displayed as a list showing additional - annotations. If any of the candidates contain newlines, then - horizontal lines are used to separate candidates. - - The Embark Collect buffer is somewhat "dired-like": you can select - and deselect candidates through =embark-select= (available as an - action in =embark-act=, bound to =SPC=; but you could also give it a - global key binding). In an Embark Collect buffer =embark-act= is bound - to =a= and =embark-act-all= is bound to =A=; =embark-act-all= will act on - all currently marked candidates if there any, and will act on all - candidates if none are marked. In particular, this means that =a SPC= - will toggle whether the candidate at point is selected, and =A SPC= - will select all candidates if none are selected, or deselect all - selected candidates if there are some. - -- The =embark-export= command tries to open a buffer in an appropriate - major mode for the set of candidates. If the candidates are files - export produces a Dired buffer; if they are buffers, you get an - Ibuffer buffer; and if they are packages you get a buffer in - package menu mode. - - If you use the grepping commands from the [[https://github.com/minad/consult/][Consult]] package, - =consult-grep=, =consult-git-grep= or =consult-ripgrep=, then you should - install the =embark-consult= package, which adds support for exporting a - list of grep results to an honest grep-mode buffer, on which you can - even use [[https://github.com/mhayashi1120/Emacs-wgrep][wgrep]] if you wish. - -When in doubt choosing between exporting and collecting, a good rule -of thumb is to always prefer =embark-export= since when an exporter to a -special major mode is available for a given type of target, it will be -more featureful than an Embark collect buffer, and if no such exporter -is configured the =embark-export= command falls back to the generic -=embark-collect=. - -These commands are always available as "actions" (although they do not -act on just the current target but on all candidates) for =embark-act= -and are bound to =A=, =S= (for "snapshot"), and =E=, respectively, in -=embark-general-map=. This means that you do not have to bind your own -key bindings for these (although you can, of course!), just a key -binding for =embark-act=. - -In Embark Collect or Embark Export buffers that were obtained by -running =embark-collect= or =embark-export= from within a minibuffer -completion session, =g= is bound to a command that restarts the -completion session, that is, the command that opened the minibuffer is -run again and the minibuffer contents restored. You can then interact -normally with the command, perhaps editing the minibuffer contents, -and, if you wish, you can rerun =embark-collect= or =embark-export= to get -an updated buffer. - -*** Selecting some targets to make an ad hoc candidate set - -The commands for working with sets of candidates just described, -namely =embark-act-all=, =embark-export= and =embark-collect= by default -work with all candidates defined in the current context. For example, -in the minibuffer they operate on all currently completion candidates, -or in a dired buffer they work on all marked files (or all files if -none are marked). Embark also has a notion of /selection/, where you can -accumulate an ad hoc list of targets for these commands to work on. - -The selection is controlled by using the =embark-select= action, bound -to =SPC= in =embark-general-map= so that it is always available (you can -also give =embark-select= a global key binding if you wish; when called -directly, not as an action for =embark-act=, it will select the first -target at point). Calling this action on a target toggles its -membership in the current buffer's Embark selection; that is, it adds -it to selection if not selected and removes it from the selection if -it was selected. Whenever the selection for a buffer is non-empty, the -commands =embark-act-all=, =embark-export= and =embark-collect= will act on -the selection. - -To deselect all selected targets, you can use the =embark-select= action -through =embark-act-all=, since this will run =embark-select= on each -member of the current selection. Similarly if no targets are selected -and you are in a minibuffer completion session, running =embark-select= -from =embark-act-all= will select all the current completion candidates. - -By default, whenever some targets are selected in the current buffer, -a count of selected targets appears in the mode line. This can be -turned off or customized through the =embark-selection-indicator= user -option. - -The selection functionality is supported in every buffer: - -- In the minibuffer this gives a convenient way to act on several - completion candidates that don't follow any simple pattern: just go - through the completions selecting the ones you want, then use - =embark-act-all=. For example, you could attach several files at once - to an email. -- For Embark Collect buffers this functionality enables a dired-like - workflow, in which you mark various candidates and apply an action - to all at once. (It supersedes a previous ad hoc dired-like - interface that was implemented only in Embark Collect buffers, with - a slightly different interface.) -- In a eww buffer you could use this to select various links you wish - to follow up on, and then collect them into a buffer. Similarly, - while reading Emacs's info manual you could select some symbols you - want to read more about and export them to an =apropos-mode= buffer. -- You can use selections in regular text or programming buffers to do - complex editing operations. For example, if you have three - paragraphs scattered over a file and you want to bring them - together, you can select each one, insert them all somewhere and - finally delete all of them (from their original locations). - -*** =embark-live= a live-updating variant of =embark-collect= - -Finally, there is also an =embark-live= variant of the =embark-collect= -command which automatically updates the collection after each change -in the source buffer. Users of a completion UI that automatically -updates and displays the candidate list (such as Vertico, Icomplete, -Fido-mode, or MCT) will probably not want to use -=embark-live= from the minibuffer as they will then have two live -updating displays of the completion candidates! - -A more likely use of =embark-live= is to be called from a regular buffer -to display a sort of live updating "table of contents" for the buffer. -This depends on having appropriate candidate collectors configured in -=embark-candidate-collectors=. There are not many in Embark's default -configuration, but you can try this experiment: open a dired buffer in -a directory that has very many files, mark a few, and run =embark-live=. -You'll get an Embark Collect buffer containing only the marked files, -which updates as you mark or unmark files in dired. To make -=embark-live= genuinely useful other candidate collectors are required. -The =embark-consult= package (documented near the end of this manual) -contains a few: one for imenu items and one for outline headings as -used by =outline-minor-mode=. Those collectors really do give -=embark-live= a table-of-contents feel. - -** Switching to a different command without losing what you've typed - -Embark also has the =embark-become= command which is useful for when -you run a command, start typing at the minibuffer and realize you -meant a different command. The most common case for me is that I run -=switch-to-buffer=, start typing a buffer name and realize I haven't -opened the file I had in mind yet! I'll use this situation as a -running example to illustrate =embark-become=. When this happens I can, -of course, press =C-g= and then run =find-file= and open the file, but -this requires retyping the portion of the file name you already -typed. This process can be streamlined with =embark-become=: while still -in the =switch-to-buffer= you can run =embark-become= and effectively -make the =switch-to-buffer= command become =find-file= for this run. - -You can bind =embark-become= to a key in =minibuffer-local-map=, but it is -also available as an action under the letter =B= (uppercase), so you -don't need a binding if you already have one for =embark-act=. So, -assuming I have =embark-act= bound to, say, =C-.=, once I realize I -haven't open the file I can type =C-. B C-x C-f= to have -=switch-to-buffer= become =find-file= without losing what I have already -typed in the minibuffer. - -But for even more convenience, =embark-become= offers shorter key -bindings for commands you are likely to want the current command to -become. When you use =embark-become= it looks for the current command in -all keymaps named in the list =embark-become-keymaps= and then activates -all keymaps that contain it. For example, the default value of -=embark-become-keymaps= contains a keymap =embark-become-file+buffer-map= -with bindings for several commands related to files and buffers, in -particular, it binds =switch-to-buffer= to =b= and =find-file= to =f=. So when -I accidentally try to switch to a buffer for a file I haven't opened -yet, =embark-become= finds that the command I ran, =switch-to-buffer=, is -in the keymap =embark-become-file+buffer-map=, so it activates that -keymap (and any others that also contain a binding for -=switch-to-buffer=). The end result is that I can type =C-. B f= to -switch to =find-file=. - -* Quick start - -The easiest way to install Embark is from GNU ELPA, just run =M-x -package-install RET embark RET=. (It is also available on MELPA.) It is -highly recommended to also install [[https://github.com/minad/marginalia][Marginalia]] (also available on GNU -ELPA), so that Embark can offer you preconfigured actions in more -contexts. For =use-package= users, the following is a very reasonable -starting configuration: - -#+begin_src emacs-lisp - (use-package marginalia - :ensure t - :config - (marginalia-mode)) - - (use-package embark - :ensure t - - :bind - (("C-." . embark-act) ;; pick some comfortable binding - ("C-;" . embark-dwim) ;; good alternative: M-. - ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' - - :init - - ;; Optionally replace the key help with a completing-read interface - (setq prefix-help-command #'embark-prefix-help-command) - - ;; Show the Embark target at point via Eldoc. You may adjust the Eldoc - ;; strategy, if you want to see the documentation from multiple providers. - (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target) - ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) - - :config - - ;; Hide the mode line of the Embark live/completions buffers - (add-to-list 'display-buffer-alist - '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" - nil - (window-parameters (mode-line-format . none))))) - - ;; Consult users will also want the embark-consult package. - (use-package embark-consult - :ensure t ; only need to install it, embark loads it after consult if found - :hook - (embark-collect-mode . consult-preview-at-point-mode)) -#+end_src - -About the suggested key bindings for =embark-act= and =embark-dwim=: -- Those key bindings are unlikely to work in the terminal, but - terminal users are probably well aware of this and will know to - select different bindings. -- The suggested =C-.= binding is used by default in (at least some - installations of) GNOME to input emojis, and Emacs doesn't even get - a chance to respond to the binding. You can select a different key - binding for =embark-act= or use =ibus-setup= to change the shortcut for - emoji insertion (Emacs 29 will likely use =C-x 8 e e=, in case you - want to set the same one system-wide). -- The suggested alternative of =M-.= for =embark-dwim= is bound by default - to =xref-find-definitions=. That is a very useful command but - overwriting it with =embark-dwim= is sensible since in Embark's - default configuration, =embark-dwim= will also find the definition of - the identifier at point. (Note that =xref-find-definitions= with a - prefix argument prompts you for an identifier, =embark-dwim= does not - cover this case). - -Other Embark commands such as =embark-act-all=, =embark-become=, -=embark-collect=, and =embark-export= can be run through =embark-act= as -actions bound to =A=, =B=, =S= (for "snapshot"), and =E= respectively, and -thus don't really need a dedicated key binding, but feel free to bind -them directly if you so wish. If you do choose to bind them directly, -you'll probably want to bind them in =minibuffer-local-map=, since they -are most useful in the minibuffer (in fact, =embark-become= only works -in the minibuffer). - -The command =embark-dwim= executes the default action at point. Another good -keybinding for =embark-dwim= is =M-.= since =embark-dwim= acts like -=xref-find-definitions= on the symbol at point. =C-.= can be seen as a -right-click context menu at point and =M-.= acts like left-click. The -keybindings are mnemonic, both act at the point (=.=). - -Embark needs to know what your minibuffer completion system considers -to be the list of candidates and which one is the current candidate. -Embark works out of the box if you use Emacs's default tab completion, -the built-in =icomplete-mode= or =fido-mode=, or the third-party packages -[[https://github.com/minad/vertico][Vertico]] or [[https://github.com/abo-abo/swiper][Ivy]]. - -If you are a [[https://emacs-helm.github.io/helm/][Helm]] or [[https://github.com/abo-abo/swiper][Ivy]] user you are unlikely to want Embark since -those packages include comprehensive functionality for acting on -minibuffer completion candidates. (Embark does come with Ivy -integration despite this.) - -* Advanced configuration -** Showing information about available targets and actions - -By default, if you run =embark-act= and do not immediately select an -action, after a short delay Embark will pop up a buffer called =*Embark -Actions*= containing a list of available actions with their key -bindings. You can scroll that buffer with the mouse of with the usual -commands =scroll-other-window= and =scroll-other-window-down= (bound by -default to =C-M-v= and =C-M-S-v=). - -That functionality is provided by the =embark-mixed-indicator=, but -Embark has other indicators that can provide information about the -target and its type, what other targets you can cycle to, and which -actions have key bindings in the action map for the current type of -target. Any number of indicators can be active at once and the user -option =embark-indicators= should be set to a list of the desired -indicators. - -Embark comes with the following indicators: - -- =embark-minimal-indicator=: shows a messages in the echo area or - minibuffer prompt showing the current target and the types of all - targets starting with the current one; this one is on by default. - -- =embark-highlight-indicator=: highlights the target at point; - also on by default. - -- =embark-verbose-indicator=: displays a table of actions and their key - bindings in a buffer; this is not on by default, in favor of the - mixed indicator described next. - -- =embark-mixed-indicator=: starts out by behaving as the minimal - indicator but after a short delay acts as the verbose indicator; - this is on by default. - -- =embark-isearch-highlight-indicator=: this only does something when - the current target is the symbol at point, in which case it - lazily highlights all occurrences of that symbol in the current - buffer, like isearch; also on by default. - -Users of the popular [[https://github.com/justbur/emacs-which-key][which-key]] package may prefer to use the -=embark-which-key-indicator= from the [[https://github.com/oantolin/embark/wiki/Additional-Configuration#use-which-key-like-a-key-menu-prompt][Embark wiki]]. Just copy its -definition from the wiki into your configuration and customize the -=embark-indicators= user option to exclude the mixed and verbose -indicators and to include =embark-which-key-indicator=. - -** Selecting commands via completions instead of key bindings - -As an alternative to reading the list of actions in the verbose or -mixed indicators (see the previous section for a description of -these), you can press the =embark-help-key=, which is =C-h= by default -(but you may prefer =?= to free up =C-h= for use as a prefix) after -running =embark-act=. Pressing the help key will prompt you for the name -of an action with completion (but feel free to enter a command that is -not among the offered candidates!), and will also remind you of the -key bindings. You can press =embark-keymap-prompter-key=, which is =@= by -default, at the prompt and then one of the key bindings to enter the -name of the corresponding action. - -You may think that with the =*Embark Actions*= buffer popping up to -remind you of the key bindings you'd never want to use completion to -select an action by name, but personally I find that typing a small -portion of the action name to narrow down the list of candidates feels -significantly faster than visually scanning the entire list of actions. - -If you find you prefer entering actions that way, you can configure -embark to always prompt you for actions by setting the variable -=embark-prompter= to =embark-completing-read-prompter=. - -** Quitting the minibuffer after an action - -By default, if you call =embark-act= from the minibuffer it quits the -minibuffer after performing the action. You can change this by setting -the user option =embark-quit-after-action= to =nil=. Having =embark-act= /not/ -quit the minibuffer can be useful to turn commands into little "thing -managers". For example, you can use =find-file= as a little file manager -or =describe-package= as a little package manager: you can run those -commands, perform a series of actions, and then quit the command. - -If you want to control the quitting behavior in a fine-grained manner -depending on the action, you can set =embark-quit-after-action= to an -alist, associating commands to either =t= for quitting or =nil= for not -quitting. When using an alist, you can use the special key =t= to -specify the default behavior. For example, to specify that by default -actions should not quit the minibuffer but that using =kill-buffer= as -an action should quit, you can use the following configuration: - -#+begin_src emacs-lisp - (setq embark-quit-after-action '((kill-buffer . t) (t . nil))) -#+end_src - -The variable =embark-quit-after-action= only specifies a default, that -is, it only controls whether or not =embark-act= quits the minibuffer -when you call it without a prefix argument, and you can select the -opposite behavior to what the variable says by calling =embark-act= with -=C-u=. Also note that both the variable =embark-quit-after-action= and =C-u= -have no effect when you call =embark-act= outside the minibuffer. - -If you find yourself using the quitting and non-quitting variants of -=embark-act= about equally often, independently of the action, you may -prefer to simply have separate commands for them instead of a single -command that you call with =C-u= half the time. You could, for example, -keep the default exiting behavior of =embark-act= and define a -non-quitting version as follows: - -#+begin_src emacs-lisp - (defun embark-act-noquit () - "Run action but don't quit the minibuffer afterwards." - (interactive) - (let ((embark-quit-after-action nil)) - (embark-act))) -#+end_src - -** Running some setup after injecting the target - -You can customize what happens after the target is inserted at the -minibuffer prompt of an action. There are -=embark-target-injection-hooks=, that are run by default after injecting -the target into the minibuffer. The variable -=embark-target-injection-hooks= is an alist associating commands to -their setup hooks. There are two special keys: if no setup hook is -specified for a given action, the hook associated to =t= is run; and the -hook associated to =:always= is run regardless of the action. (This -variable used to have the less explicit name of -=embark-setup-action-hooks=, so please update your configuration.) - -For example, consider using =shell-command= as an action during file -completion. It would be useful to insert a space before the target -file name and to leave the point at the beginning, so you can -immediately type the shell command to run on that file. That's why in -Embark's default configuration there is an entry in -=embark-target-injection-hooks= associating =shell-command= to a hook that -includes =embark--shell-prep=, a simple helper function that quotes all -the spaces in the file name, inserts an extra space at the beginning -of the line and leaves point to the left of it. - -Now, the preparation that =embark--shell-prep= does would be useless if -Embark did what it normally does after it inserts the target of the -action at the minibuffer prompt, which is to "press =RET=" for you, -accepting the target as is; if Embark did that for =shell-command= you -wouldn't get a chance to type in the command to execute! That is why -in Embark's default configuration the entry for =shell-command= in -=embark-target-injection-hooks= also contains the function -=embark--allow-edit=. - -Embark used to have a dedicated variable =embark-allow-edit-actions= to -which you could add commands for which Embark should forgo pressing -=RET= for you after inserting the target. Since its effect can also be -achieved via the general =embark-target-injection-hooks= mechanism, that -variable has been removed to simplify Embark. Be sure to update your -configuration; if you had something like: - -#+begin_src emacs-lisp - (add-to-list 'embark-allow-edit-actions 'my-command) -#+end_src - -you should replace it with: - -#+begin_src emacs-lisp - (push 'embark--allow-edit - (alist-get 'my-command embark-target-injection-hooks)) -#+end_src - - -Also note that while you could abuse =embark--allow-edit= so that you -have to confirm "dangerous" actions such as =delete-file=, it is better -to implement confirmation by adding the =embark--confirm= function to -the appropriate entry of a different hook alist, namely, -=embark-pre-action-hooks=. - -Besides =embark--allow-edit=, Embark comes with another function that is -of general utility in action setup hooks: =embark--ignore-target=. Use -it for commands that do prompt you in the minibuffer but for which -inserting the target would be inappropriate. This is not a common -situation but does occasionally arise. For example it is used by -default for =shell-command-on-region=: that command is used as an action -for region targets, and it prompts you for a shell command; you -typically do /not/ want the target, that is the contents of the region, -to be entered at that prompt! - -** Running hooks before, after or around an action - -Embark has three variables, =embark-pre-action-hooks=, -=embark-post-action-hooks= and =embark-around-action-hooks=, which are -alists associating commands to hooks that should run before or after -or as around advice for the command when used as an action. As with -=embark-target-injection-hooks=, there are two special keys for the -alists: =t= designates the default hook to run when no specific hook is -specified for a command; and the hook associated to =:always= runs -regardless. - -The default values of those variables are fairly extensive, adding -creature comforts to make running actions a smooth experience. Embark -comes with several functions intended to be added to these hooks, and -used in the default values of =embark-pre-action-hooks=, -=embark-post-action-hooks= and =embark-around-action-hooks=. - -For pre-action hooks: - -- =embark--confirm= :: Prompt the user for confirmation before executing - the action. This is used be default for commands deemed "dangerous", - or, more accurately, hard to undo, such as =delete-file= and - =kill-buffer=. - -- =embark--unmark-target= :: Unmark the active region. Use this for - commands you want to act on the region contents but without the - region being active. The default configuration uses this function as - a pre-action hook for =occur= and =query-replace=, for example, so that - you can use them as actions with region targets to search the whole - buffer for the text contained in the region. Without this pre-action - hook using =occur= as an action for a region target would be - pointless: it would search for the the region contents /in the - region/, (typically, due to the details of regexps) finding only one - match! - -- =embark--beginning-of-target= :: Move to the beginning of the target - (for targets that report bounds). This is used by default for - backward motion commands such as =backward-sexp=, so that they don't - accidentally leave you on the current target. - -- =embark--end-of-target= :: Move to the end of the target. This is used - similarly to the previous function, but also for commands that act - on the last s-expression like =eval-last-sexp=. This allow you to act - on an s-expression from anywhere inside it and still use - =eval-last-sexp= as an action. - -- =embark--xref-push-markers= :: Push the current location on the xref - marker stack. Use this for commands that take you somewhere and for - which you'd like to be able to come back to where you were using - =xref-pop-marker-stack=. This is used by default for =find-library=. - -For post-action hooks: - -- =embark--restart= :: Restart the command currently prompting in the - minibuffer, so that the list of completion candidates is updated. - This is useful as a post action hook for commands that delete or - rename a completion candidate; for example the default value of - =embark-post-action-hooks= uses it for =delete-file=, =kill-buffer=, - =rename-file=, =rename-buffer=, etc. - -For around-action hooks: - -- =embark--mark-target= :: Save existing mark and point location, mark - the target and run the action. Most targets at point outside the - minibuffer report which region of the buffer they correspond to - (this is the information used by =embark-highlight-indicator= to - know what portion of the buffer to highlight); this function marks - that region. It is useful as an around action hook for commands that - expect a region to be marked, for example, it is used by default for - =indent-region= so that it works on s-expression targets, or for - =fill-region= so that it works on paragraph targets. - -- =embark--cd= :: Run the action with =default-directory= set to the - directory associated to the current target. The target should be of - type =file=, =buffer=, =bookmark= or =library=, and the associated directory - is what you'd expect in each case. - -- =embark--narrow-to-target= :: Run the action with buffer narrowed to - current target. Use this as an around hook to localize the effect of - actions that don't already work on just the region. In the default - configuration it is used for =repunctuate-sentences=. - -- =embark--save-excursion= :: Run the action restoring point at the end. - The current default configuration doesn't use this but it is - available for users. - -** Creating your own keymaps - -All internal keymaps are defined with the standard helper macro -=defvar-keymap=. For example a simple version of the file action keymap -could be defined as follows: - -#+BEGIN_SRC emacs-lisp - (defvar-keymap embark-file-map - :doc "Example keymap with a few file actions" - :parent embark-general-map - "d" #'delete-file - "r" #'rename-file - "c" #'copy-file) -#+END_SRC - -These action keymaps are perfectly normal Emacs -keymaps. You may want to inherit from the =embark-general-map= if you -want to access the default Embark actions. Note that =embark-collect= -and =embark-export= are also made available via =embark-general-map=. - -** Defining actions for new categories of targets - -It is easy to configure Embark to provide actions for new types of -targets, either in the minibuffer or outside it. I present below two -very detailed examples of how to do this. At several points I'll -explain more than one way to proceed, typically with the easiest -option first. I include the alternative options since there will be -similar situations where the easiest option is not available. - -*** New minibuffer target example - tab-bar tabs - -As an example, take the new [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Tab-Bars.html][tab bars]] from Emacs 27. I'll explain how -to configure Embark to offer tab-specific actions when you use the -tab-bar-mode commands that mention tabs by name. The configuration -explained here is now built-in to Embark (and Marginalia), but it's -still a good self-contained example. In order to setup up tab actions -you would need to: (1) make sure Embark knows those commands deal with -tabs, (2) define a keymap for tab actions and configure Embark so it -knows that's the keymap you want. - -**** Telling Embark about commands that prompt for tabs by name - -For step (1), it would be great if the =tab-bar-mode= commands reported -the completion category =tab= when asking you for a tab with -completion. (All built-in Emacs commands that prompt for file names, -for example, do have metadata indicating that they want a =file=.) They -do not, unfortunately, and I will describe a couple of ways to deal -with this. - -Maybe the easiest thing is to configure [[https://github.com/minad/marginalia][Marginalia]] to enhance those -commands. All of the =tab-bar-*-tab-by-name= commands have the words -"tab by name" in the minibuffer prompt, so you can use: - -#+begin_src emacs-lisp - (add-to-list 'marginalia-prompt-categories '("tab by name" . tab)) -#+end_src - -That's it! But in case you are ever in a situation where you don't -already have commands that prompt for the targets you want, I'll -describe how writing your own command with appropriate =category= -metadata looks: - -#+begin_src emacs-lisp - (defun my-select-tab-by-name (tab) - (interactive - (list - (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) - (tab-bar-tabs)) - (user-error "No tabs found")))) - (completing-read - "Tabs: " - (lambda (string predicate action) - (if (eq action 'metadata) - '(metadata (category . tab)) - (complete-with-action - action tab-list string predicate))))))) - (tab-bar-select-tab-by-name tab)) -#+end_src - -As you can see, the built-in support for setting the category -meta-datum is not very easy to use or pretty to look at. To help with -this I recommend the =consult--read= function from the excellent -[[https://github.com/minad/consult/][Consult]] package. With that function we can rewrite the command as -follows: - -#+begin_src emacs-lisp - (defun my-select-tab-by-name (tab) - (interactive - (list - (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) - (tab-bar-tabs)) - (user-error "No tabs found")))) - (consult--read tab-list - :prompt "Tabs: " - :category 'tab)))) - (tab-bar-select-tab-by-name tab)) -#+end_src - -Much nicer! No matter how you define the =my-select-tab-by-name= -command, the first approach with Marginalia and prompt detection has -the following advantages: you get the =tab= category for all the -=tab-bar-*-bar-by-name= commands at once, also, you enhance built-in -commands, instead of defining new ones. - -**** Defining and configuring a keymap for tab actions - - Let's say we want to offer select, rename and close actions for tabs - (in addition to Embark general actions, such as saving the tab name to - the kill-ring, which you get for free). Then this will do: - - #+begin_src emacs-lisp - (defvar-keymap embark-tab-actions - :doc "Keymap for actions for tab-bar tabs (when mentioned by name)." - :parent embark-general-map - "s" #'tab-bar-select-tab-by-name - "r" #'tab-bar-rename-tab-by-name - "k" #'tab-bar-close-tab-by-name) - - (add-to-list 'embark-keymap-alist '(tab . embark-tab-actions)) - #+end_src - - What if after using this for a while you feel closing the tab - without confirmation is dangerous? You have a couple of options: - - 1. You can keep using the =tab-bar-close-tab-by-name= command, but have - Embark ask you for confirmation: - #+begin_src emacs-lisp - (push #'embark--confirm - (alist-get 'tab-bar-close-tab-by-name - embark-pre-action-hooks)) - #+end_src - - 2. You can write your own command that prompts for confirmation and - use that instead of =tab-bar-close-tab-by-name= in the above keymap: - #+begin_src emacs-lisp - (defun my-confirm-close-tab-by-name (tab) - (interactive "sTab to close: ") - (when (y-or-n-p (format "Close tab '%s'? " tab)) - (tab-bar-close-tab-by-name tab))) - #+end_src - - Notice that this is a command you can also use directly from =M-x= - independently of Embark. Using it from =M-x= leaves something to be - desired, though, since you don't get completion for the tab names. - You can fix this if you wish as described in the previous section. - -*** New target example in regular buffers - short Wikipedia links - -Say you want to teach Embark to treat text of the form -=wikipedia:Garry_Kasparov= in any regular buffer as a link to Wikipedia, -with actions to open the Wikipedia page in eww or an external browser -or to save the URL of the page in the kill-ring. We can take advantage -of the actions that Embark has preconfigured for URLs, so all we need -to do is teach Embark that =wikipedia:Garry_Kasparov= stands for the URL -=https://en.wikipedia.org/wiki/Garry_Kasparov=. - -You can be as fancy as you want with the recognized syntax. Here, to -keep the example simple, I'll assume the link matches the regexp -=wikipedia:[[:alnum:]_]+=. We will write a function that looks for a -match surrounding point, and returns a dotted list of the form ='(url -URL-OF-THE-PAGE START . END)= where =START= and =END= are the buffer -positions bounding the target, and are used by Embark to highlight it -if you have =embark-highlight-indicator= included in the list -=embark-indicators=. (There are a couple of other options for the return -value of a target finder: the bounding positions are optional and a -single target finder is allowed to return multiple targets; see the -documentation for =embark-target-finders= for details.) - -#+begin_src emacs-lisp - (defun my-short-wikipedia-link () - "Target a link at point of the form wikipedia:Page_Name." - (save-excursion - (let* ((start (progn (skip-chars-backward "[:alnum:]_:") (point))) - (end (progn (skip-chars-forward "[:alnum:]_:") (point))) - (str (buffer-substring-no-properties start end))) - (save-match-data - (when (string-match "wikipedia:\\([[:alnum:]_]+\\)" str) - `(url - ,(format "https://en.wikipedia.org/wiki/%s" - (match-string 1 str)) - ,start . ,end)))))) - - (add-to-list 'embark-target-finders 'my-short-wikipedia-link) -#+end_src - -* How does Embark call the actions? - - Embark actions are normal Emacs commands, that is, functions with an - interactive specification. In order to execute an action, Embark - calls the command with =call-interactively=, so the command reads user - input exactly as if run directly by the user. For example the - command may open a minibuffer and read a string - (=read-from-minibuffer=) or open a completion interface - (=completing-read=). If this happens, Embark takes the target string - and inserts it automatically into the minibuffer, simulating user - input this way. After inserting the string, Embark exits the - minibuffer, submitting the input. (The immediate minibuffer exit can - be disabled for specific actions in order to allow editing the - input; this is done by adding the =embark--allow-edit= function to the - appropriate entry of =embark-target-injection-hooks=). Embark inserts - the target string at the first minibuffer opened by the action - command, and if the command happens to prompt the user for input - more than once, the user still interacts with the second and further - prompts in the normal fashion. Note that if a command does not - prompt the user for input in the minibuffer, Embark still allows you - to use it as an action, but of course, never inserts the target - anywhere. (There are plenty of examples in the default configuration - of commands that do not prompt the user bound to keys in the action - maps, most of the region actions, for instance.) - - This is how Embark manages to reuse normal commands as actions. The - mechanism allows you to use as Embark actions commands that were not - written with Embark in mind (and indeed almost all actions that are - bound by default in Embark's action keymaps are standard Emacs - commands). It also allows you to write new custom actions in such a - way that they are useful even without Embark. - - Staring from version 28.1, Emacs has a variable - =y-or-n-p-use-read-key=, which when set to =t= causes =y-or-n-p= to use - =read-key= instead of =read-from-minibuffer=. Setting - =y-or-n-p-use-read-key= to =t= is recommended for Embark users because - it keeps Embark from attempting to insert the target at a =y-or-n-p= - prompt, which would almost never be sensible. Also consider this as - a warning to structure your own action commands so that if they use - =y-or-n-p=, they do so only after the prompting for the target. - - Here is a simple example illustrating the various ways of reading - input from the user mentioned above. Bind the following commands to - the =embark-symbol-map= to be used as actions, then put the point on - some symbol and run them with =embark-act=: - - #+begin_src emacs-lisp - (defun example-action-command1 () - (interactive) - (message "The input was `%s'." (read-from-minibuffer "Input: "))) - - (defun example-action-command2 (arg input1 input2) - (interactive "P\nsInput 1: \nsInput 2: ") - (message "The first input %swas `%s', and the second was `%s'." - (if arg "truly " "") - input1 - input2)) - - (defun example-action-command3 () - (interactive) - (message "Your selection was `%s'." - (completing-read "Select: " '("E" "M" "B" "A" "R" "K")))) - - (defun example-action-command4 () - (interactive) - (message "I don't prompt you for input and thus ignore the target!")) - - (keymap-set embark-symbol-map "X 1" #'example-action-command1) - (keymap-set embark-symbol-map "X 2" #'example-action-command2) - (keymap-set embark-symbol-map "X 3" #'example-action-command3) - (keymap-set embark-symbol-map "X 4" #'example-action-command4) - #+end_src - - Also note that if you are using the key bindings to call actions, - you can pass prefix arguments to actions in the normal way. For - example, you can use =C-u X2= with the above demonstration actions to - make the message printed by =example-action-command2= more emphatic. - This ability to pass prefix arguments to actions is useful for some - actions in the default configuration, such as - =embark-shell-command-on-buffer=. - -** Non-interactive functions as actions - - Alternatively, Embark does support one other type of action: a - non-interactive function of a single argument. The target is passed - as argument to the function. For example: - - #+begin_src emacs-lisp - (defun example-action-function (target) - (message "The target was `%s'." target)) - - (keymap-set embark-symbol-map "X 4" #'example-action-function) - #+end_src - - Note that normally binding non-interactive functions in a keymap is - useless, since when attempting to run them using the key binding you - get an error message similar to "Wrong type argument: commandp, - example-action-function". In general it is more flexible to write - any new Embark actions as commands, that is, as interactive - functions, because that way you can also run them directly, without - Embark. But there are a couple of reasons to use non-interactive - functions as actions: - - 1. You may already have the function lying around, and it is - convenient to simply reuse it. - - 2. For command actions the targets can only be simple string, with - no text properties. For certain advanced uses you may want the - action to receive a string /with/ some text properties, or even a - non-string target. - -* Embark, Marginalia and Consult - -Embark cooperates well with the [[https://github.com/minad/marginalia][Marginalia]] and [[https://github.com/minad/consult][Consult]] packages. -Neither of those packages is a dependency of Embark, but both are -highly recommended companions to Embark, for opposite reasons: -Marginalia greatly enhances Embark's usefulness, while Embark can help -enhance Consult. - -In the remainder of this section I'll explain what exactly Marginalia -does for Embark, and what Embark can do for Consult. - -** Marginalia - -Embark comes with actions for symbols (commands, functions, variables -with actions such as finding the definition, looking up the -documentation, evaluating, etc.) in the =embark-symbol-map= keymap, and -for packages (actions like install, delete, browse url, etc.) in the -=embark-package-keymap=. - -Unfortunately Embark does not automatically offers you these keymaps -when relevant, because many built-in Emacs commands don't report -accurate category metadata. For example, a command like -=describe-package=, which reads a package name from the minibuffer, -does not have metadata indicating this fact. - -In an earlier Embark version, there were functions to supply this -missing metadata, but they have been moved to Marginalia, which -augments many Emacs command to report accurate category metadata. -Simply activating =marginalia-mode= allows Embark to offer you the -package and symbol actions when appropriate again. Candidate -annotations in the Embark collect buffer are also provided by the -Marginalia package: - -- If you install Marginalia and activate =marginalia-mode=, Embark - Collect buffers will use the Marginalia annotations automatically. - -- If you don't install Marginalia, you will see only the annotations - that come with Emacs (such as key bindings in =M-x=, or the unicode - characters in =C-x 8 RET=). - -** Consult - -The excellent Consult package provides many commands that use -minibuffer completion, via the =completing-read= function; plenty of its -commands can be considered enhanced versions of built-in Emacs -commands, and some are completely new functionality. One common -enhancement provided in all commands for which it makes sense is -preview functionality, for example =consult-buffer= will show you a -quick preview of a buffer before you actually switch to it. - -If you use both Consult and Embark you should install the -=embark-consult= package which provides integration between the two. It -provides exporters for several Consult commands and also tweaks the -behavior of many Consult commands when used as actions with =embark-act= -in subtle ways that you may not even notice, but make for a smoother -experience. You need only install it to get these benefits: Embark -will automatically load it after Consult if found. - -The =embark-consult= package provides the following exporters: - -- You can use =embark-export= from =consult-line=, =consult-outline=, or - =consult-mark= to obtain an =occur-mode= buffer. As with the built-in - =occur= command you use that buffer to jump to a match and after that, - you can then use =next-error= and =previous-error= to navigate to other - matches. You can also press =e= to activate =occur-edit-mode= and edit - the matches in place! - -- You can export from any of the Consult asynchronous search commands, - =consult-grep=, =consult-git-grep=, or =consult-ripgrep= to get a - =grep-mode= buffer. Here too you can use =next-error= and =previous-error= - to navigate among matches, and, if you install the [[http://github.com/mhayashi1120/Emacs-wgrep/raw/master/wgrep.el ][wgrep]] package, - you can use it to edit the matches in place. - -In both cases, pressing =g= will rerun the Consult command you had -exported from and re-enter the input you had typed (which is similar -to reverting but a little more flexible). You can then proceed to -re-export if that's what you want, but you can also edit the input -changing the search terms or simply cancel if you see you are done -with that search. - -The =embark-consult= also contains some candidates collectors that allow -you to run =embark-live= to get a live-updating table of contents for -your buffer: - -- =embark-consult-outline-candidates= produces the outline headings of - the current buffer, using =consult-outline=. -- =embark-consult-imenu-candidates= produces the imenu items of - the current buffer, using =consult-imenu=. -- =embark-consult-imenu-or-outline-candidates= is a simple combination - of the two previous functions: it produces imenu items in buffers - deriving from =prog-mode= and otherwise outline headings. - -The way to configure =embark-live= (or =embark-collect= and =embark-export= -for that matter) to use one of these function is to add it at the end -of the =embark-candidate-collectors= list. The =embark-consult= package by -default adds the last one, which seems to be the most sensible -default. - -Besides those exporters and candidate collectors, the =embark-consult= -package provides many subtle tweaks and small integrations between -Embark and Consult. Some examples are: - -- When used as actions, the asynchronous search commands will search - only the files associated to the targets: if the targets /are/ files, - it searches those files; for buffers it will search either the - associated file if there is one, else all files in the buffer's - =default-directory=; for bookmarks it will search the file they point - to, same for Emacs Lisp libraries. This is particularly powerful - when using =embark-act-all= to act on multiple files at once, for - example you can use =consult-find= to search among file /names/ and then - =embark-act-all= and =consult-grep= to search within the matching files. - - - For all other target types, those that do not have a sensible - notion of associated file, a Consult search command (asynchronous - or not) will search for the text of the target but leave the - minibuffer open so you can interact with the Consult command. - -- =consult-imenu= will search for the target and take you directly to - the location if it matches a unique imenu entry, otherwise it will - leave the minibuffer open so you can navigate among the matches. - -* Related Packages - -There are several packages that offer functionality similar -to Embark's. - -- Acting on minibuffer completion candidates :: The popular Ivy and - Helm packages have support for acting on the completion candidates - of commands written using their APIs, and there is an extensive - ecosystem of packages meant for Helm and for Ivy (the Ivy ones - usually have "counsel" in the name) providing commands and - appropriate actions. -- Acting on things at point :: The built-in =context-menu-mode= provides - a mouse-driven context-sensitive configurable menu. The =do-at-point= - package by Philip Kaludercic (available on GNU ELPA), on the other - hand is keyboard-driven. -- Collecting completion candidates into a buffer :: The Ivy package - has the command =ivy-occur= which is similar to =embark-collect=. As - with Ivy actions, =ivy-occur= only works for commands written using - the Ivy API. - -* Resources - -If you want to learn more about how others have used Embark here are -some links to read: - -- [[https://karthinks.com/software/fifteen-ways-to-use-embark/][Fifteen ways to use Embark]], a blog post by Karthik Chikmagalur. -- [[https://protesilaos.com/dotemacs/][Protesilaos Stavrou's dotemacs]], look for the section called - "Extended minibuffer actions and more (embark.el and - prot-embark.el)" - -And some videos to watch: - -- [[https://protesilaos.com/codelog/2021-01-09-emacs-embark-extras/][Embark and my extras]] by Protesilaos Stavrou. -- [[https://youtu.be/qpoQiiinCtY][Embark -- Key features and tweaks]] by Raoul Comninos on the - Emacs-Elements YouTube channel. -- [[https://youtu.be/WsxXr1ncukY][Livestreamed: Adding an Embark context action to send a stream - message]] by Sacha Chua. -- [[https://youtu.be/qk2Is_sC8Lk][System Crafters Live! - The Many Uses of Embark]] by David Wilson. -- [[https://youtu.be/5ffb2at2d7w][Using Emacs Episode 80 - Vertico, Marginalia, Consult and Embark]] by - Mike Zamansky. - -* Contributions - -Contributions to Embark are very welcome. There is a [[https://github.com/oantolin/embark/issues/95][wish list]] for -actions, target finders, candidate collectors and exporters. For other -ideas you have for Embark, feel free to open an issue on the [[https://github.com/oantolin/embark/issues][issue -tracker]]. Any neat configuration tricks you find might be a good fit -for the [[https://github.com/oantolin/embark/wiki][wiki]]. - -Code contributions are very welcome too, but since Embark is now on -GNU ELPA, copyright assignment to the FSF is required before you can -contribute code. - -* Acknowledgments - -While I, Omar Antolín Camarena, have written most of the Embark code -and remain very stubborn about some of the design decisions, Embark -has received substantial help from a number of other people which this -document has neglected to mention for far too long. In particular, -Daniel Mendler has been absolutely invaluable, implementing several -important features, and providing a lot of useful advice. - -Code contributions: - -- [[https://github.com/minad][Daniel Mendler]] -- [[https://github.com/clemera/][Clemens Radermacher]] -- [[https://codeberg.org/jao/][José Antonio Ortega Ruiz]] -- [[https://github.com/iyefrat][Itai Y. Efrat]] -- [[https://github.com/a13][a13]] -- [[https://github.com/jakanakaevangeli][jakanakaevangeli]] -- [[https://github.com/mihakam][mihakam]] -- [[https://github.com/leungbk][Brian Leung]] -- [[https://github.com/karthink][Karthik Chikmagalur]] -- [[https://github.com/roshanshariff][Roshan Shariff]] -- [[https://github.com/condy0919][condy0919]] -- [[https://github.com/DamienCassou][Damien Cassou]] -- [[https://github.com/JimDBh][JimDBh]] - -Advice and useful discussions: - -- [[https://github.com/minad][Daniel Mendler]] -- [[https://gitlab.com/protesilaos/][Protesilaos Stavrou]] -- [[https://github.com/clemera/][Clemens Radermacher]] -- [[https://github.com/hmelman/][Howard Melman]] -- [[https://github.com/astoff][Augusto Stoffel]] -- [[https://github.com/bdarcus][Bruce d'Arcus]] -- [[https://github.com/jdtsmith][JD Smith]] -- [[https://github.com/karthink][Karthik Chikmagalur]] -- [[https://github.com/jakanakaevangeli][jakanakaevangeli]] -- [[https://github.com/iyefrat][Itai Y. Efrat]] -- [[https://github.com/mohkale][Mohsin Kaleem]] blob - 8bc9ea9a4406cb2149b341c21c0a20aa31ce4cba (mode 644) blob + /dev/null --- elpa/embark-consult-1.0/embark-consult-autoloads.el +++ /dev/null @@ -1,28 +0,0 @@ -;;; embark-consult-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 embark-consult.el - -(register-definition-prefixes "embark-consult" '("embark-consult-")) - -;;; End of scraped data - -(provide 'embark-consult-autoloads) - -;; Local Variables: -;; version-control: never -;; no-byte-compile: t -;; no-update-autoloads: t -;; no-native-compile: t -;; coding: utf-8-emacs-unix -;; End: - -;;; embark-consult-autoloads.el ends here blob - b11013c5c34ad5416d7e8179a158465f8089a71f (mode 644) blob + /dev/null --- elpa/embark-consult-1.0/embark-consult-pkg.el +++ /dev/null @@ -1,2 +0,0 @@ -;; Generated package description from embark-consult.el -*- no-byte-compile: t -*- -(define-package "embark-consult" "1.0" "Consult integration for Embark" '((emacs "27.1") (compat "29.1.4.0") (embark "1.0") (consult "1.0")) :commit "47b0c75d4bf4f72a7af839667c877c80bd493cdb" :authors '(("Omar Antolín Camarena" . "omar@matem.unam.mx")) :maintainer '("Omar Antolín Camarena" . "omar@matem.unam.mx") :keywords '("convenience") :url "https://github.com/oantolin/embark") blob - c5cf45208402001987b8118f53f5089d684d81b9 (mode 644) blob + /dev/null --- elpa/embark-consult-1.0/embark-consult.el +++ /dev/null @@ -1,425 +0,0 @@ -;;; embark-consult.el --- Consult integration for Embark -*- lexical-binding: t; -*- - -;; Copyright (C) 2021-2023 Free Software Foundation, Inc. - -;; Author: Omar Antolín Camarena -;; Maintainer: Omar Antolín Camarena -;; Keywords: convenience -;; Version: 1.0 -;; Homepage: https://github.com/oantolin/embark -;; Package-Requires: ((emacs "27.1") (compat "29.1.4.0") (embark "1.0") (consult "1.0")) - -;; 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: - -;; This package provides integration between Embark and Consult. The package -;; will be loaded automatically by Embark. - -;; Some of the functionality here was previously contained in Embark -;; itself: - -;; - Support for consult-buffer, so that you get the correct actions -;; for each type of entry in consult-buffer's list. - -;; - Support for consult-line, consult-outline, consult-mark and -;; consult-global-mark, so that the insert and save actions don't -;; include a weird unicode character at the start of the line, and so -;; you can export from them to an occur buffer (where occur-edit-mode -;; works!). - -;; Just load this package to get the above functionality, no further -;; configuration is necessary. - -;; Additionally this package contains some functionality that has -;; never been in Embark: access to Consult preview from auto-updating -;; Embark Collect buffer that is associated to an active minibuffer -;; for a Consult command. For information on Consult preview, see -;; Consult's info manual or its readme on GitHub. - -;; If you always want the minor mode enabled whenever it possible use: - -;; (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode) - -;; If you don't want the minor mode automatically on and prefer to -;; trigger the consult previews manually use this instead: - -;; (keymap-set embark-collect-mode-map "C-j" -;; #'consult-preview-at-point) - -;;; Code: - -(require 'embark) -(require 'consult) - -(eval-when-compile - (require 'cl-lib)) - -;;; Consult preview from Embark Collect buffers - -(defun embark-consult--collect-candidate () - "Return candidate at point in collect buffer." - (cadr (embark-target-collect-candidate))) - -(add-hook 'consult--completion-candidate-hook #'embark-consult--collect-candidate) - -;;; Support for consult-location - -(defun embark-consult--strip (string) - "Strip substrings marked with the `consult-strip' property from STRING." - (if (text-property-not-all 0 (length string) 'consult-strip nil string) - (let ((end (length string)) (pos 0) (chunks)) - (while (< pos end) - (let ((next (next-single-property-change pos 'consult-strip string end))) - (unless (get-text-property pos 'consult-strip string) - (push (substring string pos next) chunks)) - (setq pos next))) - (apply #'concat (nreverse chunks))) - string)) - -(defun embark-consult--target-strip (type target) - "Remove the unicode suffix character from a TARGET of TYPE." - (cons type (embark-consult--strip target))) - -(setf (alist-get 'consult-location embark-transformer-alist) - #'embark-consult--target-strip) - -(defun embark-consult-goto-location (target) - "Jump to consult location TARGET." - (consult--jump (car (consult--get-location target))) - (pulse-momentary-highlight-one-line (point))) - -(setf (alist-get 'consult-location embark-default-action-overrides) - #'embark-consult-goto-location) - -(defun embark-consult-export-occur (lines) - "Create an occur mode buffer listing LINES. -The elements of LINES are assumed to be values of category `consult-line'." - (let ((buf (generate-new-buffer "*Embark Export Occur*")) - (mouse-msg "mouse-2: go to this occurrence") - last-buf) - (with-current-buffer buf - (dolist (line lines) - (pcase-let* - ((`(,loc . ,num) (consult--get-location line)) - ;; the text properties added to the following strings are - ;; taken from occur-engine - (lineno (propertize (format "%7d:" num) - 'occur-prefix t - ;; Allow insertion of text at the end - ;; of the prefix (for Occur Edit mode). - 'front-sticky t - 'rear-nonsticky t - 'occur-target loc - 'follow-link t - 'help-echo mouse-msg)) - (contents (propertize (embark-consult--strip line) - 'occur-target loc - 'occur-match t - 'follow-link t - 'help-echo mouse-msg)) - (nl (propertize "\n" 'occur-target loc)) - (this-buf (marker-buffer loc))) - (unless (eq this-buf last-buf) - (insert (propertize - (format "lines from buffer: %s\n" this-buf) - 'face list-matching-lines-buffer-name-face)) - (setq last-buf this-buf)) - (insert (concat lineno contents nl)))) - (goto-char (point-min)) - (occur-mode)) - (pop-to-buffer buf))) - -(defun embark-consult--upgrade-markers () - "Upgrade consult-location cheap markers to real markers. -This function is meant to be added to `embark-collect-mode-hook'." - (when (eq embark--type 'consult-location) - (dolist (entry tabulated-list-entries) - (when (car entry) - (consult--get-location (car entry)))))) - -(setf (alist-get 'consult-location embark-exporters-alist) - #'embark-consult-export-occur) -(cl-pushnew #'embark-consult--upgrade-markers embark-collect-mode-hook) - -;;; Support for consult-grep - -(defvar grep-mode-line-matches) -(defvar grep-num-matches-found) -(declare-function wgrep-setup "ext:wgrep") - -(defvar-keymap embark-consult-revert-map - :doc "A keymap with a binding for revert-buffer." - :parent nil - "g" #'revert-buffer) - -(defun embark-consult--wgrep-prepare () - "Mark header as read-only." - (goto-char (point-min)) - (forward-line 2) - (add-text-properties (point-min) (point) - '(read-only t wgrep-header t front-sticky t))) - -(defun embark-consult-export-grep (lines) - "Create a grep mode buffer listing LINES." - (let ((buf (generate-new-buffer "*Embark Export Grep*")) - (count 0) - prop) - (with-current-buffer buf - (insert (propertize "Exported grep results:\n\n" 'wgrep-header t)) - (dolist (line lines) (insert line "\n")) - (goto-char (point-min)) - (while (setq prop (text-property-search-forward - 'face 'consult-highlight-match t)) - (cl-incf count) - (put-text-property (prop-match-beginning prop) - (prop-match-end prop) - 'font-lock-face - 'match)) - (goto-char (point-min)) - (grep-mode) - (when (> count 0) - (setq-local grep-num-matches-found count - mode-line-process grep-mode-line-matches)) - ;; Make this buffer current for next/previous-error - (setq next-error-last-buffer buf) - ;; Set up keymap before possible wgrep-setup, so that wgrep - ;; restores our binding too when the user finishes editing. - (use-local-map (make-composed-keymap - embark-consult-revert-map - (current-local-map))) - ;; TODO Wgrep 3.0 and development versions use different names for the - ;; parser variable. - (defvar wgrep-header/footer-parser) - (defvar wgrep-header&footer-parser) - (setq-local wgrep-header/footer-parser #'embark-consult--wgrep-prepare - wgrep-header&footer-parser #'embark-consult--wgrep-prepare) - (when (fboundp 'wgrep-setup) (wgrep-setup))) - (pop-to-buffer buf))) - -(defun embark-consult-goto-grep (location) - "Go to LOCATION, which should be a string with a grep match." - (consult--jump (consult--grep-position location)) - (pulse-momentary-highlight-one-line (point))) - -(setf (alist-get 'consult-grep embark-default-action-overrides) - #'embark-consult-goto-grep) -(setf (alist-get 'consult-grep embark-exporters-alist) - #'embark-consult-export-grep) - -;;; Support for consult-xref - -(declare-function xref--show-xref-buffer "ext:xref") -(declare-function consult-xref "ext:consult-xref") -(defvar xref-auto-jump-to-first-xref) -(defvar consult-xref--fetcher) - -(defun embark-consult-export-xref (items) - "Create an xref buffer listing ITEMS." - (cl-flet ((xref-items (items) - (mapcar (lambda (item) (get-text-property 0 'consult-xref item)) - items))) - (let ((fetcher consult-xref--fetcher) - (input (minibuffer-contents))) - (set-buffer - (xref--show-xref-buffer - (lambda () - (let ((candidates (funcall fetcher))) - (if (null (cdr candidates)) - candidates - (catch 'xref-items - (minibuffer-with-setup-hook - (lambda () - (insert input) - (add-hook - 'minibuffer-exit-hook - (lambda () - (throw 'xref-items - (xref-items - (or - (plist-get - (embark--maybe-transform-candidates) - :candidates) - (user-error "No candidates for export"))))) - nil t)) - (consult-xref fetcher)))))) - `((fetched-xrefs . ,(xref-items items)) - (window . ,(embark--target-window)) - (auto-jump . ,xref-auto-jump-to-first-xref) - (display-action))))))) - -(setf (alist-get 'consult-xref embark-exporters-alist) - #'embark-consult-export-xref) - -;;; Support for consult-find and consult-locate - -(setf (alist-get '(file . consult-find) embark-default-action-overrides - nil nil #'equal) - #'find-file) - -(setf (alist-get '(file . consult-locate) embark-default-action-overrides - nil nil #'equal) - #'find-file) - -;;; Support for consult-isearch-history - -(setf (alist-get 'consult-isearch-history embark-transformer-alist) - #'embark-consult--target-strip) - -;;; Support for consult-man and consult-info - -(defun embark-consult-man (cand) - "Default action override for `consult-man', open CAND man page." - (man (get-text-property 0 'consult-man cand))) - -(setf (alist-get 'consult-man embark-default-action-overrides) - #'embark-consult-man) - -(declare-function consult-info--action "ext:consult-info") - -(defun embark-consult-info (cand) - "Default action override for `consult-info', open CAND info manual." - (consult-info--action cand) - (pulse-momentary-highlight-one-line (point))) - -(setf (alist-get 'consult-info embark-default-action-overrides) - #'embark-consult-info) - -(setf (alist-get 'consult-info embark-transformer-alist) - #'embark-consult--target-strip) - -;;; Bindings for consult commands in embark keymaps - -(keymap-set embark-become-file+buffer-map "C b" #'consult-buffer) -(keymap-set embark-become-file+buffer-map "C 4 b" #'consult-buffer-other-window) - -;;; Support for Consult search commands - -(defvar-keymap embark-consult-sync-search-map - :doc "Keymap for Consult sync search commands" - :parent nil - "o" #'consult-outline - "i" 'consult-imenu - "I" 'consult-imenu-multi - "l" #'consult-line - "L" #'consult-line-multi) - -(defvar-keymap embark-consult-async-search-map - :doc "Keymap for Consult async search commands" - :parent nil - "g" #'consult-grep - "r" #'consult-ripgrep - "G" #'consult-git-grep - "f" #'consult-find - "F" #'consult-locate) - -(defvar embark-consult-search-map - (keymap-canonicalize - (make-composed-keymap embark-consult-sync-search-map - embark-consult-async-search-map)) - "Keymap for all Consult search commands.") - -(fset 'embark-consult-sync-search-map embark-consult-sync-search-map) -(keymap-set embark-become-match-map "C" 'embark-consult-sync-search-map) - -(cl-pushnew 'embark-consult-async-search-map embark-become-keymaps) - -(fset 'embark-consult-search-map embark-consult-search-map) -(keymap-set embark-general-map "C" 'embark-consult-search-map) - -(map-keymap - (lambda (_key cmd) - (cl-pushnew 'embark--unmark-target - (alist-get cmd embark-pre-action-hooks)) - (cl-pushnew 'embark--allow-edit - (alist-get cmd embark-target-injection-hooks))) - embark-consult-search-map) - -(defun embark-consult--unique-match (&rest _) - "If there is a unique matching candidate, accept it. -This is intended to be used in `embark-target-injection-hooks'." - (let ((candidates (cdr (embark-minibuffer-candidates)))) - (if (or (null candidates) (cdr candidates)) - (embark--allow-edit) - (delete-minibuffer-contents) - (insert (car candidates))))) - -(dolist (cmd '(consult-outline consult-imenu consult-imenu-multi)) - (setf (alist-get cmd embark-target-injection-hooks) - (remq 'embark--allow-edit - (alist-get cmd embark-target-injection-hooks))) - (cl-pushnew #'embark-consult--unique-match - (alist-get cmd embark-target-injection-hooks))) - -(cl-defun embark-consult--async-search-dwim - (&key action type target candidates &allow-other-keys) - "DWIM when using a Consult async search command as an ACTION. -If the TYPE of the target(s) has a notion of associated -file (files, buffers, libraries and some bookmarks do), then run -the ACTION with `consult-project-function' set to nil, and search -only the files associated to the TARGET or CANDIDATES. For other -types, run the ACTION with TARGET or CANDIDATES as initial input." - (if-let ((file-fn (cdr (assq type embark--associated-file-fn-alist)))) - (let (consult-project-function) - (funcall action - (delq nil (mapcar file-fn (or candidates (list target)))))) - (funcall action nil (or target (string-join candidates " "))))) - -(map-keymap - (lambda (_key cmd) - (unless (eq cmd #'consult-locate) - (cl-pushnew cmd embark-multitarget-actions) - (cl-pushnew #'embark-consult--async-search-dwim - (alist-get cmd embark-around-action-hooks)))) - embark-consult-async-search-map) - -;;; Tables of contents for buffers: imenu and outline candidate collectors - -(defun embark-consult-outline-candidates () - "Collect all outline headings in the current buffer." - (cons 'consult-location (consult--outline-candidates))) - -(autoload 'consult-imenu--items "consult-imenu") - -(defun embark-consult-imenu-candidates () - "Collect all imenu items in the current buffer." - (cons 'imenu (mapcar #'car (consult-imenu--items)))) - -(declare-function consult-imenu--group "ext:consult-imenu") - -(defun embark-consult--imenu-group-function (type prop) - "Return a suitable group-function for imenu. -TYPE is the completion category. -PROP is the metadata property. -Meant as :after-until advice for `embark-collect--metadatum'." - (when (and (eq type 'imenu) (eq prop 'group-function)) - (consult-imenu--group))) - -(advice-add #'embark-collect--metadatum :after-until - #'embark-consult--imenu-group-function) - -(defun embark-consult-imenu-or-outline-candidates () - "Collect imenu items in prog modes buffer or outline headings otherwise." - (if (derived-mode-p 'prog-mode) - (embark-consult-imenu-candidates) - (embark-consult-outline-candidates))) - -(setf (alist-get 'imenu embark-default-action-overrides) 'consult-imenu) - -(add-to-list 'embark-candidate-collectors - #'embark-consult-imenu-or-outline-candidates - 'append) - -(provide 'embark-consult) -;;; embark-consult.el ends here blob - /dev/null blob + 6d22de33f570450fc5a630a3b4d331ce4aeccc53 (mode 644) --- /dev/null +++ elpa/embark-consult-1.1/.dir-locals.el @@ -0,0 +1,6 @@ +;;; Directory Local Variables +;;; For more information see (info "(emacs) Directory Variables") + +((emacs-lisp-mode + (show-trailing-whitespace . t) + (indent-tabs-mode . nil))) blob - /dev/null blob + 7a694c9699a986b9adf1f6cb8a18a6e923e47ed9 (mode 644) --- /dev/null +++ elpa/embark-consult-1.1/.elpaignore @@ -0,0 +1 @@ +LICENSE \ No newline at end of file blob - /dev/null blob + 058be1ce20770245b36f435560955810398007cc (mode 644) --- /dev/null +++ elpa/embark-consult-1.1/CHANGELOG.org @@ -0,0 +1,105 @@ +#+title: Embark changelog + +* Version 1.1 (2024-04-18) +- The =embark-consult= package contains a new exporter for + =consult-location= targets (produced by several =consult= commands such + as =consult-line=), which exports to a grep mode buffer. Users wishing + to use the new grep mode exporter can use the following + configuration: + #+begin_src emacs-lisp + (setf (alist-get 'consult-location embark-exporters-alist) + #'embark-consult-export-location-grep) + #+end_src + The main reason for adding the new exporter is that users of the + =wgrep= package will be able to make use of a feature that =wgrep= has + and the built-in =occur-edit-mode= lacks: when editing search results + you can add new lines to a result location. There are also some + disadvantages of grep mode compared to occur mode (which is why the + previously existing occur mode exporter continues to be the + default): (1) =wgrep= is a third party package while =occur-edit-mode= + is built-in; (2) occur mode buffers can list lines in any kind of + buffer, but grep mode and =wgrep= are meant for lines of files + exclusively. +* Version 1.0 (2023-12-08) +- You can now use around action hooks with multitarget actions (that + you couldn't previously was an oversight). +- Users of the =embark-consult= package can now use consult async search + commands such as =consult-grep= as multitarget actions (through + =embark-act-all=) to search a list of files. For example, you can use + =consult-find= to search among file /names/ and once you have the + relevant files in the minibuffer, you can use =embark-act-all= to + search for some text in those files. When acting on buffers consult + async search commands will search the associated file if there is + one, or else the =default-directory= of the buffer. +- =embark-bindings= and similar commands now show definition of keyboard + macros. +- =embark-org= now recognizes Org links in non-org buffers. +- Now pressing RET in an =embark-collect= on a selection made by + using =embark-select= in a normal buffer will take you to the location + each target was collected from. +- Some functions renamed for greater consistency (these functions are + unlikely to be referred to in user's configuration): + - =embark-target-completion-at-point= → =embark-target-completion-list-candidate= + - =embark-target-top-minibuffer-completion= → =embark-target-top-minibuffer-candidate= + - =embark-completions-buffer-candidates= → =embark-completion-list-candidates= +* Version 0.23 (2023-09-19) +- Added a mode line indicator showing the number of selected targets in + the current buffer (contributed by @minad, thanks!) +- Now =embark-select= can also be called as a top-level command, from + outside =embark-act=. When called that way, it will select the first + target at point. +- =embark-org= now has support for acting on references to org headings + in other buffers, by jumping to the heading first and then running + the action. One source of references to org headings in other + buffers are agenda views: each agenda item is such a reference. But + this feature also supports some great third party commands which + produce references to org headings, such as =org-ql-find= from the + =org-ql= package or =consult-org-heading= from =consult=. +- Renamed =embark-isearch= to =embark-isearch-forward= and added + =embark-isearch-backward=. +- =embark-become= now removes any invisible text from the minibuffer + input on the grounds that users probably expect the target command + to receive exactly the input they can see. +- The meaning of the prefix argument in =embark-bindings= has flipped: + now by default global key bindings are excluded and you can use =C-u= + to include them. +- If any candidate in an embark-collect buffer contains a newline, + then candidates will be separated by horizontal lines. This is handy + for the kill-ring, which you can browse by calling =embark-collect= + from =yank-pop=. +* Version 0.22.1 (2023-04-20) +** New feature: selections +Now users can select several targets to make an ad hoc collection. The +commands =embark-act-all=, =embark-export= and =embark-collect= will act on +the selection if it is non-empty. To select or deselect a target use +the =embark-select= action (bound to =SPC= in =embark-general-map=). If you +have some targets selected, then using =embark-select= through +=embark-act-all= will deselect them. + +Before this change the Embark Collect buffers had their own +implementation of selections which has been removed. This is how to +translate the old bindings to the new feature (which is available in +all buffers, not just Embark Collect buffers!): + +| Task | Old binding | New binding | +|--------------------+-------------+---------------| +| Mark a candidate | m | a SPC | +| Unmark a candidate | u | a SPC | +| Unmark all | U | A SPC | +| Mark all [1] | t | A SPC | +| Toggle all marks | t | not available | + +[1] Marking all candidates (with either the old =t= or the new =A SPC=) +requires that there are no marked candidates to begin with. + +In order to make room for the binding of =embark-select= to +=SPC=, some other key bindings were moved: + +- =mark= in =embark-general-map= was moved to =C-SPC=. +- =outline-mark-subtree= in =embark-heading-map= was moved to =C-SPC=. +- =whitespace-cleanup-region= in =embark-region-map= was moved to =F=. + +* Version 0.21.1 (2020-01-30) +- Finally started this changelog on 2023-04-20. Known issues with the + changelog: it started very late, the first entry is not very + informative. blob - /dev/null blob + 0ac8be62137458349c1935f910593e8d6529a826 (mode 644) --- /dev/null +++ elpa/embark-consult-1.1/README-elpa @@ -0,0 +1,1471 @@ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + EMBARK: EMACS MINI-BUFFER ACTIONS ROOTED IN + KEYMAPS + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + + + + +1 Overview +══════════ + + Embark makes it easy to choose a command to run based on what is near + point, both during a minibuffer completion session (in a way familiar + to Helm or Counsel users) and in normal buffers. Bind the command + `embark-act' to a key and it acts like prefix-key for a keymap of + /actions/ (commands) relevant to the /target/ around point. With point + on an URL in a buffer you can open the URL in a browser or eww or + download the file it points to. If while switching buffers you spot an + old one, you can kill it right there and continue to select another. + Embark comes preconfigured with over a hundred actions for common + types of targets such as files, buffers, identifiers, s-expressions, + sentences; and it is easy to add more actions and more target types. + Embark can also collect all the candidates in a minibuffer to an + occur-like buffer or export them to a buffer in a major-mode specific + to the type of candidates, such as dired for a set of files, ibuffer + for a set of buffers, or customize for a set of variables. + + +1.1 Acting on targets +───────────────────── + + You can think of `embark-act' as a keyboard-based version of a + right-click contextual menu. The `embark-act' command (which you + should bind to a convenient key), acts as a prefix for a keymap + offering you relevant /actions/ to use on a /target/ determined by the + context: + + • In the minibuffer, the target is the current top completion + candidate. + • In the `*Completions*' buffer the target is the completion at point. + • In a regular buffer, the target is the region if active, or else the + file, symbol, URL, s-expression or defun at point. + + Multiple targets can be present at the same location and you can cycle + between them by repeating the `embark-act' key binding. The type of + actions offered depend on the type of the target. Here is a sample of + a few of the actions offered in the default configuration: + + • For files you get offered actions like deleting, copying, renaming, + visiting in another window, running a shell command on the file, + etc. + • For buffers the actions include switching to or killing the buffer. + • For package names the actions include installing, removing or + visiting the homepage. + • For Emacs Lisp symbols the actions include finding the definition, + looking up documentation, evaluating (which for a variable + immediately shows the value, but for a function lets you pass it + some arguments first). There are some actions specific to variables, + such as setting the value directly or though the customize system, + and some actions specific to commands, such as binding it to a key. + + By default when you use `embark-act' if you don't immediately select + an action, after a short delay Embark will pop up a buffer showing a + list of actions and their corresponding key bindings. If you are using + `embark-act' outside the minibuffer, Embark will also highlight the + current target. These behaviors are configurable via the variable + `embark-indicators'. Instead of selecting an action via its key + binding, you can select it by name with completion by typing `C-h' + after `embark-act'. + + Everything is easily configurable: determining the current target, + classifying it, and deciding which actions are offered for each type + in the classification. The above introduction just mentions part of + the default configuration. + + Configuring which actions are offered for a type is particularly easy + and requires no programming: the variable `embark-keymap-alist' + associates target types with variables containing keymaps, and those + keymaps containing bindings for the actions. (To examine the available + categories and their associated keymaps, you can use `C-h v + embark-keymap-alist' or customize that variable.) For example, in the + default configuration the type `file' is associated with the symbol + `embark-file-map'. That symbol names a keymap with single-letter key + bindings for common Emacs file commands, for instance `c' is bound to + `copy-file'. This means that if you are in the minibuffer after + running a command that prompts for a file, such as `find-file' or + `rename-file', you can copy a file by running `embark-act' and then + pressing `c'. + + These action keymaps are very convenient but not strictly necessary + when using `embark-act': you can use any command that reads from the + minibuffer as an action and the target of the action will be inserted + at the first minibuffer prompt. After running `embark-act' all of your + key bindings and even `execute-extended-command' can be used to run a + command. For example, if you want to replace all occurrences of the + symbol at point, just use `M-%' as the action, there is no need to + bind `query-replace' in one of Embark's keymaps. Also, those action + keymaps are normal Emacs keymaps and you should feel free to bind in + them whatever commands you find useful as actions and want to be + available through convenient bindings. + + The actions in `embark-general-map' are available no matter what type + of completion you are in the middle of. By default this includes + bindings to save the current candidate in the kill ring and to insert + the current candidate in the previously selected buffer (the buffer + that was current when you executed a command that opened up the + minibuffer). + + Emacs's minibuffer completion system includes metadata indicating the + /category/ of what is being completed. For example, `find-file''s + metadata indicates a category of `file' and `switch-to-buffer''s + metadata indicates a category of `buffer'. Embark has the related + notion of the /type/ of a target for actions, and by default when + category metadata is present it is taken to be the type of minibuffer + completion candidates when used as targets. Emacs commands often do + not set useful category metadata so the [Marginalia] package, which + supplies this missing metadata, is highly recommended for use with + Embark. + + Embark's default configuration has actions for the following target + types: files, buffers, symbols, packages, URLs, bookmarks, and as a + somewhat special case, actions for when the region is active. You can + read about the [default actions and their key bindings] on the GitHub + project wiki. + + +[Marginalia] + +[default actions and their key bindings] + + + +1.2 The default action on a target +────────────────────────────────── + + Embark has a notion of default action for a target: + + • If the target is a minibuffer completion candidate, then the default + action is whatever command opened the minibuffer in the first place. + For example if you run `kill-buffer', then the default action will + be to kill buffers. + • If the target comes from a regular buffer (i.e., not a minibuffer), + then the default action is whatever is bound to `RET' in the keymap + of actions for that type of target. For example, in Embark's default + configuration for a URL found at point the default action is + `browse-url', because `RET' is bound to `browse-url' in the + `embark-url-map' keymap. + + To run the default action you can press `RET' after running + `embark-act'. Note that if there are several different targets at a + given location, each has its own default action, so first cycle to the + target you want and then press `RET' to run the corresponding default + action. + + There is also `embark-dwim' which runs the default action for the + first target found. It's pretty handy in non-minibuffer buffers: with + Embark's default configuration it will: + + • Open the file at point. + • Open the URL at point in a web browser (using the `browse-url' + command). + • Compose a new email to the email address at point. + • In an Emacs Lisp buffer, if point is on an opening parenthesis or + right after a closing one, it will evaluate the corresponding + expression. + • Go to the definition of an Emacs Lisp function, variable or macro at + point. + • Find the file corresponding to an Emacs Lisp library at point. + + +1.3 Working with sets of possible targets +───────────────────────────────────────── + + Besides acting individually on targets, Embark lets you work + collectively on a set of target /candidates/. For example, while you + are in the minibuffer the candidates are simply the possible + completions of your input. Embark provides three main commands to work + on candidate sets: + + • The `embark-act-all' command runs the same action on each of the + current candidates. It is just like using `embark-act' on each + candidate in turn. (Because you can easily act on many more + candidates than you meant to, by default Embark asks you to confirm + uses of `embark-act-all'; you can turn this off by setting the user + option `embark-confirm-act-all' to `nil'.) + + • The `embark-collect' command produces a buffer listing all the + current candidates, for you to peruse and run actions on at your + leisure. The candidates are displayed as a list showing additional + annotations. If any of the candidates contain newlines, then + horizontal lines are used to separate candidates. + + The Embark Collect buffer is somewhat "dired-like": you can select + and deselect candidates through `embark-select' (available as an + action in `embark-act', bound to `SPC'; but you could also give it a + global key binding). In an Embark Collect buffer `embark-act' is + bound to `a' and `embark-act-all' is bound to `A'; `embark-act-all' + will act on all currently marked candidates if there any, and will + act on all candidates if none are marked. In particular, this means + that `a SPC' will toggle whether the candidate at point is selected, + and `A SPC' will select all candidates if none are selected, or + deselect all selected candidates if there are some. + + • The `embark-export' command tries to open a buffer in an appropriate + major mode for the set of candidates. If the candidates are files + export produces a Dired buffer; if they are buffers, you get an + Ibuffer buffer; and if they are packages you get a buffer in package + menu mode. + + If you use the grepping commands from the [Consult] package, + `consult-grep', `consult-git-grep' or `consult-ripgrep', then you + should install the `embark-consult' package, which adds support for + exporting a list of grep results to an honest grep-mode buffer, on + which you can even use [wgrep] if you wish. + + When in doubt choosing between exporting and collecting, a good rule + of thumb is to always prefer `embark-export' since when an exporter to + a special major mode is available for a given type of target, it will + be more featureful than an Embark collect buffer, and if no such + exporter is configured the `embark-export' command falls back to the + generic `embark-collect'. + + These commands are always available as "actions" (although they do not + act on just the current target but on all candidates) for `embark-act' + and are bound to `A', `S' (for "snapshot"), and `E', respectively, in + `embark-general-map'. This means that you do not have to bind your own + key bindings for these (although you can, of course!), just a key + binding for `embark-act'. + + In Embark Collect or Embark Export buffers that were obtained by + running `embark-collect' or `embark-export' from within a minibuffer + completion session, `g' is bound to a command that restarts the + completion session, that is, the command that opened the minibuffer is + run again and the minibuffer contents restored. You can then interact + normally with the command, perhaps editing the minibuffer contents, + and, if you wish, you can rerun `embark-collect' or `embark-export' to + get an updated buffer. + + +[Consult] + +[wgrep] + +1.3.1 Selecting some targets to make an ad hoc candidate set +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + The commands for working with sets of candidates just described, + namely `embark-act-all', `embark-export' and `embark-collect' by + default work with all candidates defined in the current context. For + example, in the minibuffer they operate on all currently completion + candidates, or in a dired buffer they work on all marked files (or all + files if none are marked). Embark also has a notion of /selection/, + where you can accumulate an ad hoc list of targets for these commands + to work on. + + The selection is controlled by using the `embark-select' action, bound + to `SPC' in `embark-general-map' so that it is always available (you + can also give `embark-select' a global key binding if you wish; when + called directly, not as an action for `embark-act', it will select the + first target at point). Calling this action on a target toggles its + membership in the current buffer's Embark selection; that is, it adds + it to selection if not selected and removes it from the selection if + it was selected. Whenever the selection for a buffer is non-empty, the + commands `embark-act-all', `embark-export' and `embark-collect' will + act on the selection. + + To deselect all selected targets, you can use the `embark-select' + action through `embark-act-all', since this will run `embark-select' + on each member of the current selection. Similarly if no targets are + selected and you are in a minibuffer completion session, running + `embark-select' from `embark-act-all' will select all the current + completion candidates. + + By default, whenever some targets are selected in the current buffer, + a count of selected targets appears in the mode line. This can be + turned off or customized through the `embark-selection-indicator' user + option. + + The selection functionality is supported in every buffer: + + • In the minibuffer this gives a convenient way to act on several + completion candidates that don't follow any simple pattern: just go + through the completions selecting the ones you want, then use + `embark-act-all'. For example, you could attach several files at + once to an email. + • For Embark Collect buffers this functionality enables a dired-like + workflow, in which you mark various candidates and apply an action + to all at once. (It supersedes a previous ad hoc dired-like + interface that was implemented only in Embark Collect buffers, with + a slightly different interface.) + • In a eww buffer you could use this to select various links you wish + to follow up on, and then collect them into a buffer. Similarly, + while reading Emacs's info manual you could select some symbols you + want to read more about and export them to an `apropos-mode' buffer. + • You can use selections in regular text or programming buffers to do + complex editing operations. For example, if you have three + paragraphs scattered over a file and you want to bring them + together, you can select each one, insert them all somewhere and + finally delete all of them (from their original locations). + + +1.3.2 `embark-live' a live-updating variant of `embark-collect' +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + Finally, there is also an `embark-live' variant of the + `embark-collect' command which automatically updates the collection + after each change in the source buffer. Users of a completion UI that + automatically updates and displays the candidate list (such as + Vertico, Icomplete, Fido-mode, or MCT) will probably not want to use + `embark-live' from the minibuffer as they will then have two live + updating displays of the completion candidates! + + A more likely use of `embark-live' is to be called from a regular + buffer to display a sort of live updating "table of contents" for the + buffer. This depends on having appropriate candidate collectors + configured in `embark-candidate-collectors'. There are not many in + Embark's default configuration, but you can try this experiment: open + a dired buffer in a directory that has very many files, mark a few, + and run `embark-live'. You'll get an Embark Collect buffer containing + only the marked files, which updates as you mark or unmark files in + dired. To make `embark-live' genuinely useful other candidate + collectors are required. The `embark-consult' package (documented + near the end of this manual) contains a few: one for imenu items and + one for outline headings as used by `outline-minor-mode'. Those + collectors really do give `embark-live' a table-of-contents feel. + + +1.4 Switching to a different command without losing what you've typed +───────────────────────────────────────────────────────────────────── + + Embark also has the `embark-become' command which is useful for when + you run a command, start typing at the minibuffer and realize you + meant a different command. The most common case for me is that I run + `switch-to-buffer', start typing a buffer name and realize I haven't + opened the file I had in mind yet! I'll use this situation as a + running example to illustrate `embark-become'. When this happens I + can, of course, press `C-g' and then run `find-file' and open the + file, but this requires retyping the portion of the file name you + already typed. This process can be streamlined with `embark-become': + while still in the `switch-to-buffer' you can run `embark-become' and + effectively make the `switch-to-buffer' command become `find-file' for + this run. + + You can bind `embark-become' to a key in `minibuffer-local-map', but + it is also available as an action under the letter `B' (uppercase), so + you don't need a binding if you already have one for `embark-act'. So, + assuming I have `embark-act' bound to, say, `C-.', once I realize I + haven't open the file I can type `C-. B C-x C-f' to have + `switch-to-buffer' become `find-file' without losing what I have + already typed in the minibuffer. + + But for even more convenience, `embark-become' offers shorter key + bindings for commands you are likely to want the current command to + become. When you use `embark-become' it looks for the current command + in all keymaps named in the list `embark-become-keymaps' and then + activates all keymaps that contain it. For example, the default value + of `embark-become-keymaps' contains a keymap + `embark-become-file+buffer-map' with bindings for several commands + related to files and buffers, in particular, it binds + `switch-to-buffer' to `b' and `find-file' to `f'. So when I + accidentally try to switch to a buffer for a file I haven't opened + yet, `embark-become' finds that the command I ran, `switch-to-buffer', + is in the keymap `embark-become-file+buffer-map', so it activates that + keymap (and any others that also contain a binding for + `switch-to-buffer'). The end result is that I can type `C-. B f' to + switch to `find-file'. + + +2 Quick start +═════════════ + + The easiest way to install Embark is from GNU ELPA, just run `M-x + package-install RET embark RET'. (It is also available on MELPA.) It + is highly recommended to also install [Marginalia] (also available on + GNU ELPA), so that Embark can offer you preconfigured actions in more + contexts. For `use-package' users, the following is a very reasonable + starting configuration: + + ┌──── + │ (use-package marginalia + │ :ensure t + │ :config + │ (marginalia-mode)) + │ + │ (use-package embark + │ :ensure t + │ + │ :bind + │ (("C-." . embark-act) ;; pick some comfortable binding + │ ("C-;" . embark-dwim) ;; good alternative: M-. + │ ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' + │ + │ :init + │ + │ ;; Optionally replace the key help with a completing-read interface + │ (setq prefix-help-command #'embark-prefix-help-command) + │ + │ ;; Show the Embark target at point via Eldoc. You may adjust the + │ ;; Eldoc strategy, if you want to see the documentation from + │ ;; multiple providers. Beware that using this can be a little + │ ;; jarring since the message shown in the minibuffer can be more + │ ;; than one line, causing the modeline to move up and down: + │ + │ ;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target) + │ ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) + │ + │ :config + │ + │ ;; Hide the mode line of the Embark live/completions buffers + │ (add-to-list 'display-buffer-alist + │ '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" + │ nil + │ (window-parameters (mode-line-format . none))))) + │ + │ ;; Consult users will also want the embark-consult package. + │ (use-package embark-consult + │ :ensure t ; only need to install it, embark loads it after consult if found + │ :hook + │ (embark-collect-mode . consult-preview-at-point-mode)) + └──── + + About the suggested key bindings for `embark-act' and `embark-dwim': + • Those key bindings are unlikely to work in the terminal, but + terminal users are probably well aware of this and will know to + select different bindings. + • The suggested `C-.' binding is used by default in (at least some + installations of) GNOME to input emojis, and Emacs doesn't even get + a chance to respond to the binding. You can select a different key + binding for `embark-act' or use `ibus-setup' to change the shortcut + for emoji insertion (Emacs 29 will likely use `C-x 8 e e', in case + you want to set the same one system-wide). + • The suggested alternative of `M-.' for `embark-dwim' is bound by + default to `xref-find-definitions'. That is a very useful command + but overwriting it with `embark-dwim' is sensible since in Embark's + default configuration, `embark-dwim' will also find the definition + of the identifier at point. (Note that `xref-find-definitions' with + a prefix argument prompts you for an identifier, `embark-dwim' does + not cover this case). + + Other Embark commands such as `embark-act-all', `embark-become', + `embark-collect', and `embark-export' can be run through `embark-act' + as actions bound to `A', `B', `S' (for "snapshot"), and `E' + respectively, and thus don't really need a dedicated key binding, but + feel free to bind them directly if you so wish. If you do choose to + bind them directly, you'll probably want to bind them in + `minibuffer-local-map', since they are most useful in the minibuffer + (in fact, `embark-become' only works in the minibuffer). + + The command `embark-dwim' executes the default action at + point. Another good keybinding for `embark-dwim' is `M-.' since + `embark-dwim' acts like `xref-find-definitions' on the symbol at + point. `C-.' can be seen as a right-click context menu at point and + `M-.' acts like left-click. The keybindings are mnemonic, both act at + the point (`.'). + + Embark needs to know what your minibuffer completion system considers + to be the list of candidates and which one is the current candidate. + Embark works out of the box if you use Emacs's default tab completion, + the built-in `icomplete-mode' or `fido-mode', or the third-party + packages [Vertico] or [Ivy]. + + If you are a [Helm] or [Ivy] user you are unlikely to want Embark + since those packages include comprehensive functionality for acting on + minibuffer completion candidates. (Embark does come with Ivy + integration despite this.) + + +[Marginalia] + +[Vertico] + +[Ivy] + +[Helm] + + +3 Advanced configuration +════════════════════════ + +3.1 Showing information about available targets and actions +─────────────────────────────────────────────────────────── + + By default, if you run `embark-act' and do not immediately select an + action, after a short delay Embark will pop up a buffer called + `*Embark Actions*' containing a list of available actions with their + key bindings. You can scroll that buffer with the mouse of with the + usual commands `scroll-other-window' and `scroll-other-window-down' + (bound by default to `C-M-v' and `C-M-S-v'). + + That functionality is provided by the `embark-mixed-indicator', but + Embark has other indicators that can provide information about the + target and its type, what other targets you can cycle to, and which + actions have key bindings in the action map for the current type of + target. Any number of indicators can be active at once and the user + option `embark-indicators' should be set to a list of the desired + indicators. + + Embark comes with the following indicators: + + • `embark-minimal-indicator': shows a messages in the echo area or + minibuffer prompt showing the current target and the types of all + targets starting with the current one. + + • `embark-highlight-indicator': highlights the target at point; on by + default. + + • `embark-verbose-indicator': displays a table of actions and their + key bindings in a buffer; this is not on by default, in favor of the + mixed indicator described next. + + • `embark-mixed-indicator': starts out by behaving as the minimal + indicator but after a short delay acts as the verbose indicator; + this is on by default. + + • `embark-isearch-highlight-indicator': this only does something when + the current target is the symbol at point, in which case it lazily + highlights all occurrences of that symbol in the current buffer, + like isearch; also on by default. + + Users of the popular [which-key] package may prefer to use the + `embark-which-key-indicator' from the [Embark wiki]. Just copy its + definition from the wiki into your configuration and customize the + `embark-indicators' user option to exclude the mixed and verbose + indicators and to include `embark-which-key-indicator'. + + If you use [Vertico], there is an even easier way to get a + `which-key'-like display that also lets you use completion to narrow + down the list of alternatives, described at the end of the next + section. + + +[which-key] + +[Embark wiki] + + +[Vertico] + + +3.2 Selecting commands via completions instead of key bindings +────────────────────────────────────────────────────────────── + + As an alternative to reading the list of actions in the verbose or + mixed indicators (see the previous section for a description of + these), you can press the `embark-help-key', which is `C-h' by default + (but you may prefer `?' to free up `C-h' for use as a prefix) after + running `embark-act'. Pressing the help key will prompt you for the + name of an action with completion (but feel free to enter a command + that is not among the offered candidates!), and will also remind you + of the key bindings. You can press `embark-keymap-prompter-key', which + is `@' by default, at the prompt and then one of the key bindings to + enter the name of the corresponding action. + + You may think that with the `*Embark Actions*' buffer popping up to + remind you of the key bindings you'd never want to use completion to + select an action by name, but personally I find that typing a small + portion of the action name to narrow down the list of candidates feels + significantly faster than visually scanning the entire list of + actions. + + If you find you prefer selecting actions that way, you can configure + embark to always prompt you for actions by setting the variable + `embark-prompter' to `embark-completing-read-prompter'. + + On the other hand, you may wish to continue using key bindings for the + actions you perform most often, and to use completion only to explore + what further actions are available or when you've forgotten a key + binding. In that case, you may prefer to use the minimal indicator, + which does not pop-up an `*Embark Actions*' buffer at all, and to use + the `embark-help-key' whenever you need help. This unobtrusive setup + is achieved with the following configuration: + + ┌──── + │ (setq embark-indicators + │ '(embark-minimal-indicator ; default is embark-mixed-indicator + │ embark-highlight-indicator + │ embark-isearch-highlight-indicator)) + └──── + + [Vertico] users may wish to configure a grid display for the actions + and key-bindings, reminiscent of the popular package [which-key], but, + of course, enhanced by the use of completion to narrow the list of + commands. In order to get the grid display, put the following in your + Vertico configuration: + + ┌──── + │ (add-to-list 'vertico-multiform-categories '(embark-keybinding grid)) + │ (vertico-multiform-mode) + └──── + + This will make the available keys be shown in a compact grid like in + `which-key'. The `vertico-multiform-mode' also enables keys such as + `M-V', `M-G', `M-B', and `M-U' for manually switching between layouts + in Vertico buffers. + + +[Vertico] + +[which-key] + +3.2.1 Selecting commands via completion outside of Embark +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + If you like this completion interface for exploring key bindings for + Embark actions, you may want to use it elsewhere in Emacs. You can use + Embark's completion-based command prompter to list: + + • key bindings under a prefix, + • local key bindings, or + • all key bindings. + + To use it for key bindings under a prefix (you can use this to replace + the `which-key' package, for example), use this configuration: + + ┌──── + │ (setq prefix-help-command #'embark-prefix-help-command) + └──── + + Now, when you have started on a prefix sequence such as `C-x' or + `C-c', pressing `C-h' will bring up the Embark version of the built-in + `prefix-help-command', which will list the keys under that prefix and + their bindings, and lets you select the one you wanted with + completion, or by key binding if you press + `embark-keymap-prompter-key'. + + To list local or global key bindings, use the command + `embark-bindings'. You can bind that to `C-h b', which is the default + key binding for the built-in `describe-bindings' command, which this + command can replace. By default, `embark-bindings' lists local key + bindings, typically those bound in the major mode keymap; to get + global bindings as well, call it with a `C-u' prefix argument. + + +3.3 Quitting the minibuffer after an action +─────────────────────────────────────────── + + By default, if you call `embark-act' from the minibuffer it quits the + minibuffer after performing the action. You can change this by setting + the user option `embark-quit-after-action' to `nil'. Having + `embark-act' /not/ quit the minibuffer can be useful to turn commands + into little "thing managers". For example, you can use `find-file' as + a little file manager or `describe-package' as a little package + manager: you can run those commands, perform a series of actions, and + then quit the command. + + If you want to control the quitting behavior in a fine-grained manner + depending on the action, you can set `embark-quit-after-action' to an + alist, associating commands to either `t' for quitting or `nil' for + not quitting. When using an alist, you can use the special key `t' to + specify the default behavior. For example, to specify that by default + actions should not quit the minibuffer but that using `kill-buffer' as + an action should quit, you can use the following configuration: + + ┌──── + │ (setq embark-quit-after-action '((kill-buffer . t) (t . nil))) + └──── + + The variable `embark-quit-after-action' only specifies a default, that + is, it only controls whether or not `embark-act' quits the minibuffer + when you call it without a prefix argument, and you can select the + opposite behavior to what the variable says by calling `embark-act' + with `C-u'. Also note that both the variable + `embark-quit-after-action' and `C-u' have no effect when you call + `embark-act' outside the minibuffer. + + If you find yourself using the quitting and non-quitting variants of + `embark-act' about equally often, independently of the action, you may + prefer to simply have separate commands for them instead of a single + command that you call with `C-u' half the time. You could, for + example, keep the default exiting behavior of `embark-act' and define + a non-quitting version as follows: + + ┌──── + │ (defun embark-act-noquit () + │ "Run action but don't quit the minibuffer afterwards." + │ (interactive) + │ (let ((embark-quit-after-action nil)) + │ (embark-act))) + └──── + + +3.4 Running some setup after injecting the target +───────────────────────────────────────────────── + + You can customize what happens after the target is inserted at the + minibuffer prompt of an action. There are + `embark-target-injection-hooks', that are run by default after + injecting the target into the minibuffer. The variable + `embark-target-injection-hooks' is an alist associating commands to + their setup hooks. There are two special keys: if no setup hook is + specified for a given action, the hook associated to `t' is run; and + the hook associated to `:always' is run regardless of the + action. (This variable used to have the less explicit name of + `embark-setup-action-hooks', so please update your configuration.) + + For example, consider using `shell-command' as an action during file + completion. It would be useful to insert a space before the target + file name and to leave the point at the beginning, so you can + immediately type the shell command to run on that file. That's why in + Embark's default configuration there is an entry in + `embark-target-injection-hooks' associating `shell-command' to a hook + that includes `embark--shell-prep', a simple helper function that + quotes all the spaces in the file name, inserts an extra space at the + beginning of the line and leaves point to the left of it. + + Now, the preparation that `embark--shell-prep' does would be useless + if Embark did what it normally does after it inserts the target of the + action at the minibuffer prompt, which is to "press `RET'" for you, + accepting the target as is; if Embark did that for `shell-command' you + wouldn't get a chance to type in the command to execute! That is why + in Embark's default configuration the entry for `shell-command' in + `embark-target-injection-hooks' also contains the function + `embark--allow-edit'. + + Embark used to have a dedicated variable `embark-allow-edit-actions' + to which you could add commands for which Embark should forgo pressing + `RET' for you after inserting the target. Since its effect can also be + achieved via the general `embark-target-injection-hooks' mechanism, + that variable has been removed to simplify Embark. Be sure to update + your configuration; if you had something like: + + ┌──── + │ (add-to-list 'embark-allow-edit-actions 'my-command) + └──── + + you should replace it with: + + ┌──── + │ (push 'embark--allow-edit + │ (alist-get 'my-command embark-target-injection-hooks)) + └──── + + + Also note that while you could abuse `embark--allow-edit' so that you + have to confirm "dangerous" actions such as `delete-file', it is + better to implement confirmation by adding the `embark--confirm' + function to the appropriate entry of a different hook alist, namely, + `embark-pre-action-hooks'. + + Besides `embark--allow-edit', Embark comes with another function that + is of general utility in action setup hooks: + `embark--ignore-target'. Use it for commands that do prompt you in the + minibuffer but for which inserting the target would be + inappropriate. This is not a common situation but does occasionally + arise. For example it is used by default for + `shell-command-on-region': that command is used as an action for + region targets, and it prompts you for a shell command; you typically + do /not/ want the target, that is the contents of the region, to be + entered at that prompt! + + +3.5 Running hooks before, after or around an action +─────────────────────────────────────────────────── + + Embark has three variables, `embark-pre-action-hooks', + `embark-post-action-hooks' and `embark-around-action-hooks', which are + alists associating commands to hooks that should run before or after + or as around advice for the command when used as an action. As with + `embark-target-injection-hooks', there are two special keys for the + alists: `t' designates the default hook to run when no specific hook + is specified for a command; and the hook associated to `:always' runs + regardless. + + The default values of those variables are fairly extensive, adding + creature comforts to make running actions a smooth experience. Embark + comes with several functions intended to be added to these hooks, and + used in the default values of `embark-pre-action-hooks', + `embark-post-action-hooks' and `embark-around-action-hooks'. + + For pre-action hooks: + + `embark--confirm' + Prompt the user for confirmation before executing the + action. This is used be default for commands deemed "dangerous", + or, more accurately, hard to undo, such as `delete-file' and + `kill-buffer'. + + `embark--unmark-target' + Unmark the active region. Use this for commands you want to act + on the region contents but without the region being active. The + default configuration uses this function as a pre-action hook + for `occur' and `query-replace', for example, so that you can + use them as actions with region targets to search the whole + buffer for the text contained in the region. Without this + pre-action hook using `occur' as an action for a region target + would be pointless: it would search for the the region contents + /in the region/, (typically, due to the details of regexps) + finding only one match! + + `embark--beginning-of-target' + Move to the beginning of the target (for targets that report + bounds). This is used by default for backward motion commands + such as `backward-sexp', so that they don't accidentally leave + you on the current target. + + `embark--end-of-target' + Move to the end of the target. This is used similarly to the + previous function, but also for commands that act on the last + s-expression like `eval-last-sexp'. This allow you to act on an + s-expression from anywhere inside it and still use + `eval-last-sexp' as an action. + + `embark--xref-push-markers' + Push the current location on the xref marker stack. Use this for + commands that take you somewhere and for which you'd like to be + able to come back to where you were using + `xref-pop-marker-stack'. This is used by default for + `find-library'. + + For post-action hooks: + + `embark--restart' + Restart the command currently prompting in the minibuffer, so + that the list of completion candidates is updated. This is + useful as a post action hook for commands that delete or rename + a completion candidate; for example the default value of + `embark-post-action-hooks' uses it for `delete-file', + `kill-buffer', `rename-file', `rename-buffer', etc. + + For around-action hooks: + + `embark--mark-target' + Save existing mark and point location, mark the target and run + the action. Most targets at point outside the minibuffer report + which region of the buffer they correspond to (this is the + information used by `embark-highlight-indicator' to know what + portion of the buffer to highlight); this function marks that + region. It is useful as an around action hook for commands that + expect a region to be marked, for example, it is used by default + for `indent-region' so that it works on s-expression targets, or + for `fill-region' so that it works on paragraph targets. + + `embark--cd' + Run the action with `default-directory' set to the directory + associated to the current target. The target should be of type + `file', `buffer', `bookmark' or `library', and the associated + directory is what you'd expect in each case. + + `embark--narrow-to-target' + Run the action with buffer narrowed to current target. Use this + as an around hook to localize the effect of actions that don't + already work on just the region. In the default configuration it + is used for `repunctuate-sentences'. + + `embark--save-excursion' + Run the action restoring point at the end. The current default + configuration doesn't use this but it is available for users. + + +3.6 Creating your own keymaps +───────────────────────────── + + All internal keymaps are defined with the standard helper macro + `defvar-keymap'. For example a simple version of the file action + keymap could be defined as follows: + + ┌──── + │ (defvar-keymap embark-file-map + │ :doc "Example keymap with a few file actions" + │ :parent embark-general-map + │ "d" #'delete-file + │ "r" #'rename-file + │ "c" #'copy-file) + └──── + + These action keymaps are perfectly normal Emacs keymaps. You may want + to inherit from the `embark-general-map' if you want to access the + default Embark actions. Note that `embark-collect' and `embark-export' + are also made available via `embark-general-map'. + + +3.7 Defining actions for new categories of targets +────────────────────────────────────────────────── + + It is easy to configure Embark to provide actions for new types of + targets, either in the minibuffer or outside it. I present below two + very detailed examples of how to do this. At several points I'll + explain more than one way to proceed, typically with the easiest + option first. I include the alternative options since there will be + similar situations where the easiest option is not available. + + +3.7.1 New minibuffer target example - tab-bar tabs +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + As an example, take the new [tab bars] from Emacs 27. I'll explain how + to configure Embark to offer tab-specific actions when you use the + tab-bar-mode commands that mention tabs by name. The configuration + explained here is now built-in to Embark (and Marginalia), but it's + still a good self-contained example. In order to setup up tab actions + you would need to: (1) make sure Embark knows those commands deal with + tabs, (2) define a keymap for tab actions and configure Embark so it + knows that's the keymap you want. + + +[tab bars] + + +◊ 3.7.1.1 Telling Embark about commands that prompt for tabs by name + + For step (1), it would be great if the `tab-bar-mode' commands + reported the completion category `tab' when asking you for a tab with + completion. (All built-in Emacs commands that prompt for file names, + for example, do have metadata indicating that they want a `file'.) + They do not, unfortunately, and I will describe a couple of ways to + deal with this. + + Maybe the easiest thing is to configure [Marginalia] to enhance those + commands. All of the `tab-bar-*-tab-by-name' commands have the words + "tab by name" in the minibuffer prompt, so you can use: + + ┌──── + │ (add-to-list 'marginalia-prompt-categories '("tab by name" . tab)) + └──── + + That's it! But in case you are ever in a situation where you don't + already have commands that prompt for the targets you want, I'll + describe how writing your own command with appropriate `category' + metadata looks: + + ┌──── + │ (defun my-select-tab-by-name (tab) + │ (interactive + │ (list + │ (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) + │ (tab-bar-tabs)) + │ (user-error "No tabs found")))) + │ (completing-read + │ "Tabs: " + │ (lambda (string predicate action) + │ (if (eq action 'metadata) + │ '(metadata (category . tab)) + │ (complete-with-action + │ action tab-list string predicate))))))) + │ (tab-bar-select-tab-by-name tab)) + └──── + + As you can see, the built-in support for setting the category + meta-datum is not very easy to use or pretty to look at. To help with + this I recommend the `consult--read' function from the excellent + [Consult] package. With that function we can rewrite the command as + follows: + + ┌──── + │ (defun my-select-tab-by-name (tab) + │ (interactive + │ (list + │ (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) + │ (tab-bar-tabs)) + │ (user-error "No tabs found")))) + │ (consult--read tab-list + │ :prompt "Tabs: " + │ :category 'tab)))) + │ (tab-bar-select-tab-by-name tab)) + └──── + + Much nicer! No matter how you define the `my-select-tab-by-name' + command, the first approach with Marginalia and prompt detection has + the following advantages: you get the `tab' category for all the + `tab-bar-*-bar-by-name' commands at once, also, you enhance built-in + commands, instead of defining new ones. + + + [Marginalia] + + [Consult] + + +◊ 3.7.1.2 Defining and configuring a keymap for tab actions + + Let's say we want to offer select, rename and close actions for tabs + (in addition to Embark general actions, such as saving the tab name to + the kill-ring, which you get for free). Then this will do: + + ┌──── + │ (defvar-keymap embark-tab-actions + │ :doc "Keymap for actions for tab-bar tabs (when mentioned by name)." + │ :parent embark-general-map + │ "s" #'tab-bar-select-tab-by-name + │ "r" #'tab-bar-rename-tab-by-name + │ "k" #'tab-bar-close-tab-by-name) + │ + │ (add-to-list 'embark-keymap-alist '(tab . embark-tab-actions)) + └──── + + What if after using this for a while you feel closing the tab without + confirmation is dangerous? You have a couple of options: + + 1. You can keep using the `tab-bar-close-tab-by-name' command, but + have Embark ask you for confirmation: + ┌──── + │ (push #'embark--confirm + │ (alist-get 'tab-bar-close-tab-by-name + │ embark-pre-action-hooks)) + └──── + + 2. You can write your own command that prompts for confirmation and + use that instead of `tab-bar-close-tab-by-name' in the above + keymap: + ┌──── + │ (defun my-confirm-close-tab-by-name (tab) + │ (interactive "sTab to close: ") + │ (when (y-or-n-p (format "Close tab '%s'? " tab)) + │ (tab-bar-close-tab-by-name tab))) + └──── + + Notice that this is a command you can also use directly from `M-x' + independently of Embark. Using it from `M-x' leaves something to be + desired, though, since you don't get completion for the tab names. + You can fix this if you wish as described in the previous section. + + +3.7.2 New target example in regular buffers - short Wikipedia links +╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ + + Say you want to teach Embark to treat text of the form + `wikipedia:Garry_Kasparov' in any regular buffer as a link to + Wikipedia, with actions to open the Wikipedia page in eww or an + external browser or to save the URL of the page in the kill-ring. We + can take advantage of the actions that Embark has preconfigured for + URLs, so all we need to do is teach Embark that + `wikipedia:Garry_Kasparov' stands for the URL + `https://en.wikipedia.org/wiki/Garry_Kasparov'. + + You can be as fancy as you want with the recognized syntax. Here, to + keep the example simple, I'll assume the link matches the regexp + `wikipedia:[[:alnum:]_]+'. We will write a function that looks for a + match surrounding point, and returns a dotted list of the form `'(url + URL-OF-THE-PAGE START . END)' where `START' and `END' are the buffer + positions bounding the target, and are used by Embark to highlight it + if you have `embark-highlight-indicator' included in the list + `embark-indicators'. (There are a couple of other options for the + return value of a target finder: the bounding positions are optional + and a single target finder is allowed to return multiple targets; see + the documentation for `embark-target-finders' for details.) + + ┌──── + │ (defun my-short-wikipedia-link () + │ "Target a link at point of the form wikipedia:Page_Name." + │ (save-excursion + │ (let* ((start (progn (skip-chars-backward "[:alnum:]_:") (point))) + │ (end (progn (skip-chars-forward "[:alnum:]_:") (point))) + │ (str (buffer-substring-no-properties start end))) + │ (save-match-data + │ (when (string-match "wikipedia:\\([[:alnum:]_]+\\)" str) + │ `(url + │ ,(format "https://en.wikipedia.org/wiki/%s" + │ (match-string 1 str)) + │ ,start . ,end)))))) + │ + │ (add-to-list 'embark-target-finders 'my-short-wikipedia-link) + └──── + + +4 How does Embark call the actions? +═══════════════════════════════════ + + Embark actions are normal Emacs commands, that is, functions with an + interactive specification. In order to execute an action, Embark calls + the command with `call-interactively', so the command reads user input + exactly as if run directly by the user. For example the command may + open a minibuffer and read a string (`read-from-minibuffer') or open a + completion interface (`completing-read'). If this happens, Embark + takes the target string and inserts it automatically into the + minibuffer, simulating user input this way. After inserting the + string, Embark exits the minibuffer, submitting the input. (The + immediate minibuffer exit can be disabled for specific actions in + order to allow editing the input; this is done by adding the + `embark--allow-edit' function to the appropriate entry of + `embark-target-injection-hooks'). Embark inserts the target string at + the first minibuffer opened by the action command, and if the command + happens to prompt the user for input more than once, the user still + interacts with the second and further prompts in the normal + fashion. Note that if a command does not prompt the user for input in + the minibuffer, Embark still allows you to use it as an action, but of + course, never inserts the target anywhere. (There are plenty of + examples in the default configuration of commands that do not prompt + the user bound to keys in the action maps, most of the region actions, + for instance.) + + This is how Embark manages to reuse normal commands as actions. The + mechanism allows you to use as Embark actions commands that were not + written with Embark in mind (and indeed almost all actions that are + bound by default in Embark's action keymaps are standard Emacs + commands). It also allows you to write new custom actions in such a + way that they are useful even without Embark. + + Staring from version 28.1, Emacs has a variable + `y-or-n-p-use-read-key', which when set to `t' causes `y-or-n-p' to + use `read-key' instead of `read-from-minibuffer'. Setting + `y-or-n-p-use-read-key' to `t' is recommended for Embark users because + it keeps Embark from attempting to insert the target at a `y-or-n-p' + prompt, which would almost never be sensible. Also consider this as a + warning to structure your own action commands so that if they use + `y-or-n-p', they do so only after the prompting for the target. + + Here is a simple example illustrating the various ways of reading + input from the user mentioned above. Bind the following commands to + the `embark-symbol-map' to be used as actions, then put the point on + some symbol and run them with `embark-act': + + ┌──── + │ (defun example-action-command1 () + │ (interactive) + │ (message "The input was `%s'." (read-from-minibuffer "Input: "))) + │ + │ (defun example-action-command2 (arg input1 input2) + │ (interactive "P\nsInput 1: \nsInput 2: ") + │ (message "The first input %swas `%s', and the second was `%s'." + │ (if arg "truly " "") + │ input1 + │ input2)) + │ + │ (defun example-action-command3 () + │ (interactive) + │ (message "Your selection was `%s'." + │ (completing-read "Select: " '("E" "M" "B" "A" "R" "K")))) + │ + │ (defun example-action-command4 () + │ (interactive) + │ (message "I don't prompt you for input and thus ignore the target!")) + │ + │ (keymap-set embark-symbol-map "X 1" #'example-action-command1) + │ (keymap-set embark-symbol-map "X 2" #'example-action-command2) + │ (keymap-set embark-symbol-map "X 3" #'example-action-command3) + │ (keymap-set embark-symbol-map "X 4" #'example-action-command4) + └──── + + Also note that if you are using the key bindings to call actions, you + can pass prefix arguments to actions in the normal way. For example, + you can use `C-u X2' with the above demonstration actions to make the + message printed by `example-action-command2' more emphatic. This + ability to pass prefix arguments to actions is useful for some actions + in the default configuration, such as + `embark-shell-command-on-buffer'. + + +4.1 Non-interactive functions as actions +──────────────────────────────────────── + + Alternatively, Embark does support one other type of action: a + non-interactive function of a single argument. The target is passed as + argument to the function. For example: + + ┌──── + │ (defun example-action-function (target) + │ (message "The target was `%s'." target)) + │ + │ (keymap-set embark-symbol-map "X 4" #'example-action-function) + └──── + + Note that normally binding non-interactive functions in a keymap is + useless, since when attempting to run them using the key binding you + get an error message similar to "Wrong type argument: commandp, + example-action-function". In general it is more flexible to write any + new Embark actions as commands, that is, as interactive functions, + because that way you can also run them directly, without Embark. But + there are a couple of reasons to use non-interactive functions as + actions: + + 1. You may already have the function lying around, and it is + convenient to simply reuse it. + + 2. For command actions the targets can only be simple string, with no + text properties. For certain advanced uses you may want the action + to receive a string /with/ some text properties, or even a + non-string target. + + +5 Embark, Marginalia and Consult +════════════════════════════════ + + Embark cooperates well with the [Marginalia] and [Consult] packages. + Neither of those packages is a dependency of Embark, but both are + highly recommended companions to Embark, for opposite reasons: + Marginalia greatly enhances Embark's usefulness, while Embark can help + enhance Consult. + + In the remainder of this section I'll explain what exactly Marginalia + does for Embark, and what Embark can do for Consult. + + +[Marginalia] + +[Consult] + +5.1 Marginalia +────────────── + + Embark comes with actions for symbols (commands, functions, variables + with actions such as finding the definition, looking up the + documentation, evaluating, etc.) in the `embark-symbol-map' keymap, + and for packages (actions like install, delete, browse url, etc.) in + the `embark-package-keymap'. + + Unfortunately Embark does not automatically offers you these keymaps + when relevant, because many built-in Emacs commands don't report + accurate category metadata. For example, a command like + `describe-package', which reads a package name from the minibuffer, + does not have metadata indicating this fact. + + In an earlier Embark version, there were functions to supply this + missing metadata, but they have been moved to Marginalia, which + augments many Emacs command to report accurate category metadata. + Simply activating `marginalia-mode' allows Embark to offer you the + package and symbol actions when appropriate again. Candidate + annotations in the Embark collect buffer are also provided by the + Marginalia package: + + • If you install Marginalia and activate `marginalia-mode', Embark + Collect buffers will use the Marginalia annotations automatically. + + • If you don't install Marginalia, you will see only the annotations + that come with Emacs (such as key bindings in `M-x', or the unicode + characters in `C-x 8 RET'). + + +5.2 Consult +─────────── + + The excellent Consult package provides many commands that use + minibuffer completion, via the `completing-read' function; plenty of + its commands can be considered enhanced versions of built-in Emacs + commands, and some are completely new functionality. One common + enhancement provided in all commands for which it makes sense is + preview functionality, for example `consult-buffer' will show you a + quick preview of a buffer before you actually switch to it. + + If you use both Consult and Embark you should install the + `embark-consult' package which provides integration between the + two. It provides exporters for several Consult commands and also + tweaks the behavior of many Consult commands when used as actions with + `embark-act' in subtle ways that you may not even notice, but make for + a smoother experience. You need only install it to get these benefits: + Embark will automatically load it after Consult if found. + + The `embark-consult' package provides the following exporters: + + • You can use `embark-export' from `consult-line', `consult-outline', + or `consult-mark' to obtain an `occur-mode' buffer. As with the + built-in `occur' command you use that buffer to jump to a match and + after that, you can then use `next-error' and `previous-error' to + navigate to other matches. You can also press `e' to activate + `occur-edit-mode' and edit the matches in place! + + • You can export from any of the Consult asynchronous search commands, + `consult-grep', `consult-git-grep', or `consult-ripgrep' to get a + `grep-mode' buffer. Here too you can use `next-error' and + `previous-error' to navigate among matches, and, if you install the + [wgrep] package, you can use it to edit the matches in place. + + In both cases, pressing `g' will rerun the Consult command you had + exported from and re-enter the input you had typed (which is similar + to reverting but a little more flexible). You can then proceed to + re-export if that's what you want, but you can also edit the input + changing the search terms or simply cancel if you see you are done + with that search. + + The `embark-consult' also contains some candidates collectors that + allow you to run `embark-live' to get a live-updating table of + contents for your buffer: + + • `embark-consult-outline-candidates' produces the outline headings of + the current buffer, using `consult-outline'. + • `embark-consult-imenu-candidates' produces the imenu items of the + current buffer, using `consult-imenu'. + • `embark-consult-imenu-or-outline-candidates' is a simple combination + of the two previous functions: it produces imenu items in buffers + deriving from `prog-mode' and otherwise outline headings. + + The way to configure `embark-live' (or `embark-collect' and + `embark-export' for that matter) to use one of these function is to + add it at the end of the `embark-candidate-collectors' list. The + `embark-consult' package by default adds the last one, which seems to + be the most sensible default. + + Besides those exporters and candidate collectors, the `embark-consult' + package provides many subtle tweaks and small integrations between + Embark and Consult. Some examples are: + + • When used as actions, the asynchronous search commands will search + only the files associated to the targets: if the targets /are/ + files, it searches those files; for buffers it will search either + the associated file if there is one, else all files in the buffer's + `default-directory'; for bookmarks it will search the file they + point to, same for Emacs Lisp libraries. This is particularly + powerful when using `embark-act-all' to act on multiple files at + once, for example you can use `consult-find' to search among file + /names/ and then `embark-act-all' and `consult-grep' to search + within the matching files. + + • For all other target types, those that do not have a sensible + notion of associated file, a Consult search command (asynchronous + or not) will search for the text of the target but leave the + minibuffer open so you can interact with the Consult command. + + • `consult-imenu' will search for the target and take you directly to + the location if it matches a unique imenu entry, otherwise it will + leave the minibuffer open so you can navigate among the matches. + + +[wgrep] + + +6 Related Packages +══════════════════ + + There are several packages that offer functionality similar to + Embark's. + + Acting on minibuffer completion candidates + The popular Ivy and Helm packages have support for acting on the + completion candidates of commands written using their APIs, and + there is an extensive ecosystem of packages meant for Helm and + for Ivy (the Ivy ones usually have "counsel" in the name) + providing commands and appropriate actions. + Acting on things at point + The built-in `context-menu-mode' provides a mouse-driven + context-sensitive configurable menu. The `do-at-point' package + by Philip Kaludercic (available on GNU ELPA), on the other hand + is keyboard-driven. + Collecting completion candidates into a buffer + The Ivy package has the command `ivy-occur' which is similar to + `embark-collect'. As with Ivy actions, `ivy-occur' only works + for commands written using the Ivy API. + + +7 Resources +═══════════ + + If you want to learn more about how others have used Embark here are + some links to read: + + • [Fifteen ways to use Embark], a blog post by Karthik Chikmagalur. + • [Protesilaos Stavrou's dotemacs], look for the section called + "Extended minibuffer actions and more (embark.el and + prot-embark.el)" + + And some videos to watch: + + • [Embark and my extras] by Protesilaos Stavrou. + • [Embark – Key features and tweaks] by Raoul Comninos on the + Emacs-Elements YouTube channel. + • [Livestreamed: Adding an Embark context action to send a stream + message] by Sacha Chua. + • [System Crafters Live! - The Many Uses of Embark] by David Wilson. + • [Using Emacs Episode 80 - Vertico, Marginalia, Consult and Embark] + by Mike Zamansky. + + +[Fifteen ways to use Embark] + + +[Protesilaos Stavrou's dotemacs] + +[Embark and my extras] + + +[Embark – Key features and tweaks] + +[Livestreamed: Adding an Embark context action to send a stream message] + + +[System Crafters Live! - The Many Uses of Embark] + + +[Using Emacs Episode 80 - Vertico, Marginalia, Consult and Embark] + + + +8 Contributions +═══════════════ + + Contributions to Embark are very welcome. There is a [wish list] for + actions, target finders, candidate collectors and exporters. For other + ideas you have for Embark, feel free to open an issue on the [issue + tracker]. Any neat configuration tricks you find might be a good fit + for the [wiki]. + + Code contributions are very welcome too, but since Embark is now on + GNU ELPA, copyright assignment to the FSF is required before you can + contribute code. + + +[wish list] + +[issue tracker] + +[wiki] + + +9 Acknowledgments +═════════════════ + + While I, Omar Antolín Camarena, have written most of the Embark code + and remain very stubborn about some of the design decisions, Embark + has received substantial help from a number of other people which this + document has neglected to mention for far too long. In particular, + Daniel Mendler has been absolutely invaluable, implementing several + important features, and providing a lot of useful advice. + + Code contributions: + + • [Daniel Mendler] + • [Clemens Radermacher] + • [José Antonio Ortega Ruiz] + • [Itai Y. Efrat] + • [a13] + • [jakanakaevangeli] + • [mihakam] + • [Brian Leung] + • [Karthik Chikmagalur] + • [Roshan Shariff] + • [condy0919] + • [Damien Cassou] + • [JimDBh] + + Advice and useful discussions: + + • [Daniel Mendler] + • [Protesilaos Stavrou] + • [Clemens Radermacher] + • [Howard Melman] + • [Augusto Stoffel] + • [Bruce d'Arcus] + • [JD Smith] + • [Karthik Chikmagalur] + • [jakanakaevangeli] + • [Itai Y. Efrat] + • [Mohsin Kaleem] + + +[Daniel Mendler] + +[Clemens Radermacher] + +[José Antonio Ortega Ruiz] + +[Itai Y. Efrat] + +[a13] + +[jakanakaevangeli] + +[mihakam] + +[Brian Leung] + +[Karthik Chikmagalur] + +[Roshan Shariff] + +[condy0919] + +[Damien Cassou] + +[JimDBh] + +[Protesilaos Stavrou] + +[Howard Melman] + +[Augusto Stoffel] + +[Bruce d'Arcus] + +[JD Smith] + +[Mohsin Kaleem] blob - /dev/null blob + 7af6a2f487aac027b13b6f61f94a81fcf8776255 (mode 644) --- /dev/null +++ elpa/embark-consult-1.1/README.org @@ -0,0 +1,1276 @@ +#+TITLE: Embark: Emacs Mini-Buffer Actions Rooted in Keymaps +#+OPTIONS: d:nil +#+EXPORT_FILE_NAME: embark.texi +#+TEXINFO_DIR_CATEGORY: Emacs misc features +#+TEXINFO_DIR_TITLE: Embark: (embark). +#+TEXINFO_DIR_DESC: Emacs Mini-Buffer Actions Rooted in Keymaps + +#+html: GNU ELPA +#+html: GNU-devel ELPA +#+html: MELPA +#+html: MELPA Stable + +* Overview + +Embark makes it easy to choose a command to run based on what is near +point, both during a minibuffer completion session (in a way familiar +to Helm or Counsel users) and in normal buffers. Bind the command +=embark-act= to a key and it acts like prefix-key for a keymap of +/actions/ (commands) relevant to the /target/ around point. With point on +an URL in a buffer you can open the URL in a browser or eww or +download the file it points to. If while switching buffers you spot an +old one, you can kill it right there and continue to select another. +Embark comes preconfigured with over a hundred actions for common +types of targets such as files, buffers, identifiers, s-expressions, +sentences; and it is easy to add more actions and more target types. +Embark can also collect all the candidates in a minibuffer to an +occur-like buffer or export them to a buffer in a major-mode specific +to the type of candidates, such as dired for a set of files, ibuffer +for a set of buffers, or customize for a set of variables. + +** Acting on targets + +You can think of =embark-act= as a keyboard-based version of a +right-click contextual menu. The =embark-act= command (which you should +bind to a convenient key), acts as a prefix for a keymap offering you +relevant /actions/ to use on a /target/ determined by the context: + +- In the minibuffer, the target is the current top completion + candidate. +- In the =*Completions*= buffer the target is the completion at point. +- In a regular buffer, the target is the region if active, or else the + file, symbol, URL, s-expression or defun at point. + +Multiple targets can be present at the same location and you can cycle +between them by repeating the =embark-act= key binding. The type of +actions offered depend on the type of the target. Here is a sample of +a few of the actions offered in the default configuration: + +- For files you get offered actions like deleting, copying, + renaming, visiting in another window, running a shell command on the + file, etc. +- For buffers the actions include switching to or killing the buffer. +- For package names the actions include installing, removing or + visiting the homepage. +- For Emacs Lisp symbols the actions include finding the definition, + looking up documentation, evaluating (which for a variable + immediately shows the value, but for a function lets you pass it + some arguments first). There are some actions specific to variables, + such as setting the value directly or though the customize system, + and some actions specific to commands, such as binding it to a key. + +By default when you use =embark-act= if you don't immediately select an +action, after a short delay Embark will pop up a buffer showing a list +of actions and their corresponding key bindings. If you are using +=embark-act= outside the minibuffer, Embark will also highlight the +current target. These behaviors are configurable via the variable +=embark-indicators=. Instead of selecting an action via its key binding, +you can select it by name with completion by typing =C-h= after +=embark-act=. + +Everything is easily configurable: determining the current target, +classifying it, and deciding which actions are offered for each type +in the classification. The above introduction just mentions part of +the default configuration. + +Configuring which actions are offered for a type is particularly easy +and requires no programming: the variable =embark-keymap-alist= +associates target types with variables containing keymaps, and those +keymaps containing bindings for the actions. (To examine the available +categories and their associated keymaps, you can use =C-h v +embark-keymap-alist= or customize that variable.) For example, in the +default configuration the type =file= is associated with the symbol +=embark-file-map=. That symbol names a keymap with single-letter key +bindings for common Emacs file commands, for instance =c= is bound to +=copy-file=. This means that if you are in the minibuffer after running +a command that prompts for a file, such as =find-file= or =rename-file=, +you can copy a file by running =embark-act= and then pressing =c=. + +These action keymaps are very convenient but not strictly necessary +when using =embark-act=: you can use any command that reads from the +minibuffer as an action and the target of the action will be inserted +at the first minibuffer prompt. After running =embark-act= all of your +key bindings and even =execute-extended-command= can be used to run a +command. For example, if you want to replace all occurrences of the +symbol at point, just use =M-%= as the action, there is no need to bind +=query-replace= in one of Embark's keymaps. Also, those action keymaps +are normal Emacs keymaps and you should feel free to bind in them +whatever commands you find useful as actions and want to be available +through convenient bindings. + +The actions in =embark-general-map= are available no matter what type +of completion you are in the middle of. By default this includes +bindings to save the current candidate in the kill ring and to insert +the current candidate in the previously selected buffer (the buffer +that was current when you executed a command that opened up the +minibuffer). + +Emacs's minibuffer completion system includes metadata indicating the +/category/ of what is being completed. For example, =find-file='s +metadata indicates a category of =file= and =switch-to-buffer='s metadata +indicates a category of =buffer=. Embark has the related notion of the +/type/ of a target for actions, and by default when category metadata +is present it is taken to be the type of minibuffer completion +candidates when used as targets. Emacs commands often do not set +useful category metadata so the [[https://github.com/minad/marginalia][Marginalia]] package, which supplies +this missing metadata, is highly recommended for use with Embark. + +Embark's default configuration has actions for the following target +types: files, buffers, symbols, packages, URLs, bookmarks, and as a +somewhat special case, actions for when the region is active. You can +read about the [[https://github.com/oantolin/embark/wiki/Default-Actions][default actions and their key bindings]] on the GitHub +project wiki. + +** The default action on a target + +Embark has a notion of default action for a target: + +- If the target is a minibuffer completion candidate, then the default + action is whatever command opened the minibuffer in the first place. + For example if you run =kill-buffer=, then the default action will be + to kill buffers. +- If the target comes from a regular buffer (i.e., not a minibuffer), + then the default action is whatever is bound to =RET= in the keymap of + actions for that type of target. For example, in Embark's default + configuration for a URL found at point the default action is + =browse-url=, because =RET= is bound to =browse-url= in the =embark-url-map= + keymap. + +To run the default action you can press =RET= after running =embark-act=. +Note that if there are several different targets at a given location, +each has its own default action, so first cycle to the target you want +and then press =RET= to run the corresponding default action. + +There is also =embark-dwim= which runs the default action for the first +target found. It's pretty handy in non-minibuffer buffers: with +Embark's default configuration it will: + +- Open the file at point. +- Open the URL at point in a web browser (using the =browse-url= + command). +- Compose a new email to the email address at point. +- In an Emacs Lisp buffer, if point is on an opening parenthesis or + right after a closing one, it will evaluate the corresponding + expression. +- Go to the definition of an Emacs Lisp function, variable or macro at + point. +- Find the file corresponding to an Emacs Lisp library at point. + +** Working with sets of possible targets + +Besides acting individually on targets, Embark lets you work +collectively on a set of target /candidates/. For example, while you are +in the minibuffer the candidates are simply the possible completions +of your input. Embark provides three main commands to work on candidate +sets: + +- The =embark-act-all= command runs the same action on each of the + current candidates. It is just like using =embark-act= on each + candidate in turn. (Because you can easily act on many more + candidates than you meant to, by default Embark asks you to confirm + uses of =embark-act-all=; you can turn this off by setting the user + option =embark-confirm-act-all= to =nil=.) + +- The =embark-collect= command produces a buffer listing all the current + candidates, for you to peruse and run actions on at your leisure. + The candidates are displayed as a list showing additional + annotations. If any of the candidates contain newlines, then + horizontal lines are used to separate candidates. + + The Embark Collect buffer is somewhat "dired-like": you can select + and deselect candidates through =embark-select= (available as an + action in =embark-act=, bound to =SPC=; but you could also give it a + global key binding). In an Embark Collect buffer =embark-act= is bound + to =a= and =embark-act-all= is bound to =A=; =embark-act-all= will act on + all currently marked candidates if there any, and will act on all + candidates if none are marked. In particular, this means that =a SPC= + will toggle whether the candidate at point is selected, and =A SPC= + will select all candidates if none are selected, or deselect all + selected candidates if there are some. + +- The =embark-export= command tries to open a buffer in an appropriate + major mode for the set of candidates. If the candidates are files + export produces a Dired buffer; if they are buffers, you get an + Ibuffer buffer; and if they are packages you get a buffer in + package menu mode. + + If you use the grepping commands from the [[https://github.com/minad/consult/][Consult]] package, + =consult-grep=, =consult-git-grep= or =consult-ripgrep=, then you should + install the =embark-consult= package, which adds support for exporting a + list of grep results to an honest grep-mode buffer, on which you can + even use [[https://github.com/mhayashi1120/Emacs-wgrep][wgrep]] if you wish. + +When in doubt choosing between exporting and collecting, a good rule +of thumb is to always prefer =embark-export= since when an exporter to a +special major mode is available for a given type of target, it will be +more featureful than an Embark collect buffer, and if no such exporter +is configured the =embark-export= command falls back to the generic +=embark-collect=. + +These commands are always available as "actions" (although they do not +act on just the current target but on all candidates) for =embark-act= +and are bound to =A=, =S= (for "snapshot"), and =E=, respectively, in +=embark-general-map=. This means that you do not have to bind your own +key bindings for these (although you can, of course!), just a key +binding for =embark-act=. + +In Embark Collect or Embark Export buffers that were obtained by +running =embark-collect= or =embark-export= from within a minibuffer +completion session, =g= is bound to a command that restarts the +completion session, that is, the command that opened the minibuffer is +run again and the minibuffer contents restored. You can then interact +normally with the command, perhaps editing the minibuffer contents, +and, if you wish, you can rerun =embark-collect= or =embark-export= to get +an updated buffer. + +*** Selecting some targets to make an ad hoc candidate set + +The commands for working with sets of candidates just described, +namely =embark-act-all=, =embark-export= and =embark-collect= by default +work with all candidates defined in the current context. For example, +in the minibuffer they operate on all currently completion candidates, +or in a dired buffer they work on all marked files (or all files if +none are marked). Embark also has a notion of /selection/, where you can +accumulate an ad hoc list of targets for these commands to work on. + +The selection is controlled by using the =embark-select= action, bound +to =SPC= in =embark-general-map= so that it is always available (you can +also give =embark-select= a global key binding if you wish; when called +directly, not as an action for =embark-act=, it will select the first +target at point). Calling this action on a target toggles its +membership in the current buffer's Embark selection; that is, it adds +it to selection if not selected and removes it from the selection if +it was selected. Whenever the selection for a buffer is non-empty, the +commands =embark-act-all=, =embark-export= and =embark-collect= will act on +the selection. + +To deselect all selected targets, you can use the =embark-select= action +through =embark-act-all=, since this will run =embark-select= on each +member of the current selection. Similarly if no targets are selected +and you are in a minibuffer completion session, running =embark-select= +from =embark-act-all= will select all the current completion candidates. + +By default, whenever some targets are selected in the current buffer, +a count of selected targets appears in the mode line. This can be +turned off or customized through the =embark-selection-indicator= user +option. + +The selection functionality is supported in every buffer: + +- In the minibuffer this gives a convenient way to act on several + completion candidates that don't follow any simple pattern: just go + through the completions selecting the ones you want, then use + =embark-act-all=. For example, you could attach several files at once + to an email. +- For Embark Collect buffers this functionality enables a dired-like + workflow, in which you mark various candidates and apply an action + to all at once. (It supersedes a previous ad hoc dired-like + interface that was implemented only in Embark Collect buffers, with + a slightly different interface.) +- In a eww buffer you could use this to select various links you wish + to follow up on, and then collect them into a buffer. Similarly, + while reading Emacs's info manual you could select some symbols you + want to read more about and export them to an =apropos-mode= buffer. +- You can use selections in regular text or programming buffers to do + complex editing operations. For example, if you have three + paragraphs scattered over a file and you want to bring them + together, you can select each one, insert them all somewhere and + finally delete all of them (from their original locations). + +*** =embark-live= a live-updating variant of =embark-collect= + +Finally, there is also an =embark-live= variant of the =embark-collect= +command which automatically updates the collection after each change +in the source buffer. Users of a completion UI that automatically +updates and displays the candidate list (such as Vertico, Icomplete, +Fido-mode, or MCT) will probably not want to use +=embark-live= from the minibuffer as they will then have two live +updating displays of the completion candidates! + +A more likely use of =embark-live= is to be called from a regular buffer +to display a sort of live updating "table of contents" for the buffer. +This depends on having appropriate candidate collectors configured in +=embark-candidate-collectors=. There are not many in Embark's default +configuration, but you can try this experiment: open a dired buffer in +a directory that has very many files, mark a few, and run =embark-live=. +You'll get an Embark Collect buffer containing only the marked files, +which updates as you mark or unmark files in dired. To make +=embark-live= genuinely useful other candidate collectors are required. +The =embark-consult= package (documented near the end of this manual) +contains a few: one for imenu items and one for outline headings as +used by =outline-minor-mode=. Those collectors really do give +=embark-live= a table-of-contents feel. + +** Switching to a different command without losing what you've typed + +Embark also has the =embark-become= command which is useful for when +you run a command, start typing at the minibuffer and realize you +meant a different command. The most common case for me is that I run +=switch-to-buffer=, start typing a buffer name and realize I haven't +opened the file I had in mind yet! I'll use this situation as a +running example to illustrate =embark-become=. When this happens I can, +of course, press =C-g= and then run =find-file= and open the file, but +this requires retyping the portion of the file name you already +typed. This process can be streamlined with =embark-become=: while still +in the =switch-to-buffer= you can run =embark-become= and effectively +make the =switch-to-buffer= command become =find-file= for this run. + +You can bind =embark-become= to a key in =minibuffer-local-map=, but it is +also available as an action under the letter =B= (uppercase), so you +don't need a binding if you already have one for =embark-act=. So, +assuming I have =embark-act= bound to, say, =C-.=, once I realize I +haven't open the file I can type =C-. B C-x C-f= to have +=switch-to-buffer= become =find-file= without losing what I have already +typed in the minibuffer. + +But for even more convenience, =embark-become= offers shorter key +bindings for commands you are likely to want the current command to +become. When you use =embark-become= it looks for the current command in +all keymaps named in the list =embark-become-keymaps= and then activates +all keymaps that contain it. For example, the default value of +=embark-become-keymaps= contains a keymap =embark-become-file+buffer-map= +with bindings for several commands related to files and buffers, in +particular, it binds =switch-to-buffer= to =b= and =find-file= to =f=. So when +I accidentally try to switch to a buffer for a file I haven't opened +yet, =embark-become= finds that the command I ran, =switch-to-buffer=, is +in the keymap =embark-become-file+buffer-map=, so it activates that +keymap (and any others that also contain a binding for +=switch-to-buffer=). The end result is that I can type =C-. B f= to +switch to =find-file=. + +* Quick start + +The easiest way to install Embark is from GNU ELPA, just run =M-x +package-install RET embark RET=. (It is also available on MELPA.) It is +highly recommended to also install [[https://github.com/minad/marginalia][Marginalia]] (also available on GNU +ELPA), so that Embark can offer you preconfigured actions in more +contexts. For =use-package= users, the following is a very reasonable +starting configuration: + +#+begin_src emacs-lisp + (use-package marginalia + :ensure t + :config + (marginalia-mode)) + + (use-package embark + :ensure t + + :bind + (("C-." . embark-act) ;; pick some comfortable binding + ("C-;" . embark-dwim) ;; good alternative: M-. + ("C-h B" . embark-bindings)) ;; alternative for `describe-bindings' + + :init + + ;; Optionally replace the key help with a completing-read interface + (setq prefix-help-command #'embark-prefix-help-command) + + ;; Show the Embark target at point via Eldoc. You may adjust the + ;; Eldoc strategy, if you want to see the documentation from + ;; multiple providers. Beware that using this can be a little + ;; jarring since the message shown in the minibuffer can be more + ;; than one line, causing the modeline to move up and down: + + ;; (add-hook 'eldoc-documentation-functions #'embark-eldoc-first-target) + ;; (setq eldoc-documentation-strategy #'eldoc-documentation-compose-eagerly) + + :config + + ;; Hide the mode line of the Embark live/completions buffers + (add-to-list 'display-buffer-alist + '("\\`\\*Embark Collect \\(Live\\|Completions\\)\\*" + nil + (window-parameters (mode-line-format . none))))) + + ;; Consult users will also want the embark-consult package. + (use-package embark-consult + :ensure t ; only need to install it, embark loads it after consult if found + :hook + (embark-collect-mode . consult-preview-at-point-mode)) +#+end_src + +About the suggested key bindings for =embark-act= and =embark-dwim=: +- Those key bindings are unlikely to work in the terminal, but + terminal users are probably well aware of this and will know to + select different bindings. +- The suggested =C-.= binding is used by default in (at least some + installations of) GNOME to input emojis, and Emacs doesn't even get + a chance to respond to the binding. You can select a different key + binding for =embark-act= or use =ibus-setup= to change the shortcut for + emoji insertion (Emacs 29 will likely use =C-x 8 e e=, in case you + want to set the same one system-wide). +- The suggested alternative of =M-.= for =embark-dwim= is bound by default + to =xref-find-definitions=. That is a very useful command but + overwriting it with =embark-dwim= is sensible since in Embark's + default configuration, =embark-dwim= will also find the definition of + the identifier at point. (Note that =xref-find-definitions= with a + prefix argument prompts you for an identifier, =embark-dwim= does not + cover this case). + +Other Embark commands such as =embark-act-all=, =embark-become=, +=embark-collect=, and =embark-export= can be run through =embark-act= as +actions bound to =A=, =B=, =S= (for "snapshot"), and =E= respectively, and +thus don't really need a dedicated key binding, but feel free to bind +them directly if you so wish. If you do choose to bind them directly, +you'll probably want to bind them in =minibuffer-local-map=, since they +are most useful in the minibuffer (in fact, =embark-become= only works +in the minibuffer). + +The command =embark-dwim= executes the default action at point. Another good +keybinding for =embark-dwim= is =M-.= since =embark-dwim= acts like +=xref-find-definitions= on the symbol at point. =C-.= can be seen as a +right-click context menu at point and =M-.= acts like left-click. The +keybindings are mnemonic, both act at the point (=.=). + +Embark needs to know what your minibuffer completion system considers +to be the list of candidates and which one is the current candidate. +Embark works out of the box if you use Emacs's default tab completion, +the built-in =icomplete-mode= or =fido-mode=, or the third-party packages +[[https://github.com/minad/vertico][Vertico]] or [[https://github.com/abo-abo/swiper][Ivy]]. + +If you are a [[https://emacs-helm.github.io/helm/][Helm]] or [[https://github.com/abo-abo/swiper][Ivy]] user you are unlikely to want Embark since +those packages include comprehensive functionality for acting on +minibuffer completion candidates. (Embark does come with Ivy +integration despite this.) + +* Advanced configuration +** Showing information about available targets and actions + +By default, if you run =embark-act= and do not immediately select an +action, after a short delay Embark will pop up a buffer called =*Embark +Actions*= containing a list of available actions with their key +bindings. You can scroll that buffer with the mouse of with the usual +commands =scroll-other-window= and =scroll-other-window-down= (bound by +default to =C-M-v= and =C-M-S-v=). + +That functionality is provided by the =embark-mixed-indicator=, but +Embark has other indicators that can provide information about the +target and its type, what other targets you can cycle to, and which +actions have key bindings in the action map for the current type of +target. Any number of indicators can be active at once and the user +option =embark-indicators= should be set to a list of the desired +indicators. + +Embark comes with the following indicators: + +- =embark-minimal-indicator=: shows a messages in the echo area or + minibuffer prompt showing the current target and the types of all + targets starting with the current one. + +- =embark-highlight-indicator=: highlights the target at point; on by + default. + +- =embark-verbose-indicator=: displays a table of actions and their key + bindings in a buffer; this is not on by default, in favor of the + mixed indicator described next. + +- =embark-mixed-indicator=: starts out by behaving as the minimal + indicator but after a short delay acts as the verbose indicator; + this is on by default. + +- =embark-isearch-highlight-indicator=: this only does something when + the current target is the symbol at point, in which case it + lazily highlights all occurrences of that symbol in the current + buffer, like isearch; also on by default. + +Users of the popular [[https://github.com/justbur/emacs-which-key][which-key]] package may prefer to use the +=embark-which-key-indicator= from the [[https://github.com/oantolin/embark/wiki/Additional-Configuration#use-which-key-like-a-key-menu-prompt][Embark wiki]]. Just copy its +definition from the wiki into your configuration and customize the +=embark-indicators= user option to exclude the mixed and verbose +indicators and to include =embark-which-key-indicator=. + +If you use [[https://github.com/minad/vertico][Vertico]], there is an even easier way to get a +=which-key=-like display that also lets you use completion to narrow +down the list of alternatives, described at the end of the next +section. + +** Selecting commands via completions instead of key bindings + +As an alternative to reading the list of actions in the verbose or +mixed indicators (see the previous section for a description of +these), you can press the =embark-help-key=, which is =C-h= by default +(but you may prefer =?= to free up =C-h= for use as a prefix) after +running =embark-act=. Pressing the help key will prompt you for the name +of an action with completion (but feel free to enter a command that is +not among the offered candidates!), and will also remind you of the +key bindings. You can press =embark-keymap-prompter-key=, which is =@= by +default, at the prompt and then one of the key bindings to enter the +name of the corresponding action. + +You may think that with the =*Embark Actions*= buffer popping up to +remind you of the key bindings you'd never want to use completion to +select an action by name, but personally I find that typing a small +portion of the action name to narrow down the list of candidates feels +significantly faster than visually scanning the entire list of actions. + +If you find you prefer selecting actions that way, you can configure +embark to always prompt you for actions by setting the variable +=embark-prompter= to =embark-completing-read-prompter=. + +On the other hand, you may wish to continue using key bindings for the +actions you perform most often, and to use completion only to explore +what further actions are available or when you've forgotten a key +binding. In that case, you may prefer to use the minimal indicator, +which does not pop-up an =*Embark Actions*= buffer at all, and to use +the =embark-help-key= whenever you need help. This unobtrusive setup is +achieved with the following configuration: + +#+begin_src emacs-lisp + (setq embark-indicators + '(embark-minimal-indicator ; default is embark-mixed-indicator + embark-highlight-indicator + embark-isearch-highlight-indicator)) +#+end_src + +[[https://github.com/minad/vertico][Vertico]] users may wish to configure a grid display for the actions and +key-bindings, reminiscent of the popular package [[https://github.com/justbur/emacs-which-key][which-key]], but, of +course, enhanced by the use of completion to narrow the list of +commands. In order to get the grid display, put the following in your +Vertico configuration: + +#+begin_src emacs-lisp + (add-to-list 'vertico-multiform-categories '(embark-keybinding grid)) + (vertico-multiform-mode) +#+end_src + +This will make the available keys be shown in a compact grid like in +=which-key=. The =vertico-multiform-mode= also enables keys such as =M-V=, +=M-G=, =M-B=, and =M-U= for manually switching between layouts in Vertico +buffers. + +*** Selecting commands via completion outside of Embark + +If you like this completion interface for exploring key bindings for +Embark actions, you may want to use it elsewhere in Emacs. You can use +Embark's completion-based command prompter to list: + +- key bindings under a prefix, +- local key bindings, or +- all key bindings. + +To use it for key bindings under a prefix (you can use this to replace +the =which-key= package, for example), use this configuration: + +#+begin_src emacs-lisp + (setq prefix-help-command #'embark-prefix-help-command) +#+end_src + +Now, when you have started on a prefix sequence such as =C-x= or =C-c=, +pressing =C-h= will bring up the Embark version of the built-in +=prefix-help-command=, which will list the keys under that prefix and +their bindings, and lets you select the one you wanted with completion, +or by key binding if you press =embark-keymap-prompter-key=. + +To list local or global key bindings, use the command =embark-bindings=. +You can bind that to =C-h b=, which is the default key binding for the +built-in =describe-bindings= command, which this command can replace. By +default, =embark-bindings= lists local key bindings, typically those +bound in the major mode keymap; to get global bindings as well, call +it with a =C-u= prefix argument. + +** Quitting the minibuffer after an action + +By default, if you call =embark-act= from the minibuffer it quits the +minibuffer after performing the action. You can change this by setting +the user option =embark-quit-after-action= to =nil=. Having =embark-act= /not/ +quit the minibuffer can be useful to turn commands into little "thing +managers". For example, you can use =find-file= as a little file manager +or =describe-package= as a little package manager: you can run those +commands, perform a series of actions, and then quit the command. + +If you want to control the quitting behavior in a fine-grained manner +depending on the action, you can set =embark-quit-after-action= to an +alist, associating commands to either =t= for quitting or =nil= for not +quitting. When using an alist, you can use the special key =t= to +specify the default behavior. For example, to specify that by default +actions should not quit the minibuffer but that using =kill-buffer= as +an action should quit, you can use the following configuration: + +#+begin_src emacs-lisp + (setq embark-quit-after-action '((kill-buffer . t) (t . nil))) +#+end_src + +The variable =embark-quit-after-action= only specifies a default, that +is, it only controls whether or not =embark-act= quits the minibuffer +when you call it without a prefix argument, and you can select the +opposite behavior to what the variable says by calling =embark-act= with +=C-u=. Also note that both the variable =embark-quit-after-action= and =C-u= +have no effect when you call =embark-act= outside the minibuffer. + +If you find yourself using the quitting and non-quitting variants of +=embark-act= about equally often, independently of the action, you may +prefer to simply have separate commands for them instead of a single +command that you call with =C-u= half the time. You could, for example, +keep the default exiting behavior of =embark-act= and define a +non-quitting version as follows: + +#+begin_src emacs-lisp + (defun embark-act-noquit () + "Run action but don't quit the minibuffer afterwards." + (interactive) + (let ((embark-quit-after-action nil)) + (embark-act))) +#+end_src + +** Running some setup after injecting the target + +You can customize what happens after the target is inserted at the +minibuffer prompt of an action. There are +=embark-target-injection-hooks=, that are run by default after injecting +the target into the minibuffer. The variable +=embark-target-injection-hooks= is an alist associating commands to +their setup hooks. There are two special keys: if no setup hook is +specified for a given action, the hook associated to =t= is run; and the +hook associated to =:always= is run regardless of the action. (This +variable used to have the less explicit name of +=embark-setup-action-hooks=, so please update your configuration.) + +For example, consider using =shell-command= as an action during file +completion. It would be useful to insert a space before the target +file name and to leave the point at the beginning, so you can +immediately type the shell command to run on that file. That's why in +Embark's default configuration there is an entry in +=embark-target-injection-hooks= associating =shell-command= to a hook that +includes =embark--shell-prep=, a simple helper function that quotes all +the spaces in the file name, inserts an extra space at the beginning +of the line and leaves point to the left of it. + +Now, the preparation that =embark--shell-prep= does would be useless if +Embark did what it normally does after it inserts the target of the +action at the minibuffer prompt, which is to "press =RET=" for you, +accepting the target as is; if Embark did that for =shell-command= you +wouldn't get a chance to type in the command to execute! That is why +in Embark's default configuration the entry for =shell-command= in +=embark-target-injection-hooks= also contains the function +=embark--allow-edit=. + +Embark used to have a dedicated variable =embark-allow-edit-actions= to +which you could add commands for which Embark should forgo pressing +=RET= for you after inserting the target. Since its effect can also be +achieved via the general =embark-target-injection-hooks= mechanism, that +variable has been removed to simplify Embark. Be sure to update your +configuration; if you had something like: + +#+begin_src emacs-lisp + (add-to-list 'embark-allow-edit-actions 'my-command) +#+end_src + +you should replace it with: + +#+begin_src emacs-lisp + (push 'embark--allow-edit + (alist-get 'my-command embark-target-injection-hooks)) +#+end_src + + +Also note that while you could abuse =embark--allow-edit= so that you +have to confirm "dangerous" actions such as =delete-file=, it is better +to implement confirmation by adding the =embark--confirm= function to +the appropriate entry of a different hook alist, namely, +=embark-pre-action-hooks=. + +Besides =embark--allow-edit=, Embark comes with another function that is +of general utility in action setup hooks: =embark--ignore-target=. Use +it for commands that do prompt you in the minibuffer but for which +inserting the target would be inappropriate. This is not a common +situation but does occasionally arise. For example it is used by +default for =shell-command-on-region=: that command is used as an action +for region targets, and it prompts you for a shell command; you +typically do /not/ want the target, that is the contents of the region, +to be entered at that prompt! + +** Running hooks before, after or around an action + +Embark has three variables, =embark-pre-action-hooks=, +=embark-post-action-hooks= and =embark-around-action-hooks=, which are +alists associating commands to hooks that should run before or after +or as around advice for the command when used as an action. As with +=embark-target-injection-hooks=, there are two special keys for the +alists: =t= designates the default hook to run when no specific hook is +specified for a command; and the hook associated to =:always= runs +regardless. + +The default values of those variables are fairly extensive, adding +creature comforts to make running actions a smooth experience. Embark +comes with several functions intended to be added to these hooks, and +used in the default values of =embark-pre-action-hooks=, +=embark-post-action-hooks= and =embark-around-action-hooks=. + +For pre-action hooks: + +- =embark--confirm= :: Prompt the user for confirmation before executing + the action. This is used be default for commands deemed "dangerous", + or, more accurately, hard to undo, such as =delete-file= and + =kill-buffer=. + +- =embark--unmark-target= :: Unmark the active region. Use this for + commands you want to act on the region contents but without the + region being active. The default configuration uses this function as + a pre-action hook for =occur= and =query-replace=, for example, so that + you can use them as actions with region targets to search the whole + buffer for the text contained in the region. Without this pre-action + hook using =occur= as an action for a region target would be + pointless: it would search for the the region contents /in the + region/, (typically, due to the details of regexps) finding only one + match! + +- =embark--beginning-of-target= :: Move to the beginning of the target + (for targets that report bounds). This is used by default for + backward motion commands such as =backward-sexp=, so that they don't + accidentally leave you on the current target. + +- =embark--end-of-target= :: Move to the end of the target. This is used + similarly to the previous function, but also for commands that act + on the last s-expression like =eval-last-sexp=. This allow you to act + on an s-expression from anywhere inside it and still use + =eval-last-sexp= as an action. + +- =embark--xref-push-markers= :: Push the current location on the xref + marker stack. Use this for commands that take you somewhere and for + which you'd like to be able to come back to where you were using + =xref-pop-marker-stack=. This is used by default for =find-library=. + +For post-action hooks: + +- =embark--restart= :: Restart the command currently prompting in the + minibuffer, so that the list of completion candidates is updated. + This is useful as a post action hook for commands that delete or + rename a completion candidate; for example the default value of + =embark-post-action-hooks= uses it for =delete-file=, =kill-buffer=, + =rename-file=, =rename-buffer=, etc. + +For around-action hooks: + +- =embark--mark-target= :: Save existing mark and point location, mark + the target and run the action. Most targets at point outside the + minibuffer report which region of the buffer they correspond to + (this is the information used by =embark-highlight-indicator= to + know what portion of the buffer to highlight); this function marks + that region. It is useful as an around action hook for commands that + expect a region to be marked, for example, it is used by default for + =indent-region= so that it works on s-expression targets, or for + =fill-region= so that it works on paragraph targets. + +- =embark--cd= :: Run the action with =default-directory= set to the + directory associated to the current target. The target should be of + type =file=, =buffer=, =bookmark= or =library=, and the associated directory + is what you'd expect in each case. + +- =embark--narrow-to-target= :: Run the action with buffer narrowed to + current target. Use this as an around hook to localize the effect of + actions that don't already work on just the region. In the default + configuration it is used for =repunctuate-sentences=. + +- =embark--save-excursion= :: Run the action restoring point at the end. + The current default configuration doesn't use this but it is + available for users. + +** Creating your own keymaps + +All internal keymaps are defined with the standard helper macro +=defvar-keymap=. For example a simple version of the file action keymap +could be defined as follows: + +#+BEGIN_SRC emacs-lisp + (defvar-keymap embark-file-map + :doc "Example keymap with a few file actions" + :parent embark-general-map + "d" #'delete-file + "r" #'rename-file + "c" #'copy-file) +#+END_SRC + +These action keymaps are perfectly normal Emacs +keymaps. You may want to inherit from the =embark-general-map= if you +want to access the default Embark actions. Note that =embark-collect= +and =embark-export= are also made available via =embark-general-map=. + +** Defining actions for new categories of targets + +It is easy to configure Embark to provide actions for new types of +targets, either in the minibuffer or outside it. I present below two +very detailed examples of how to do this. At several points I'll +explain more than one way to proceed, typically with the easiest +option first. I include the alternative options since there will be +similar situations where the easiest option is not available. + +*** New minibuffer target example - tab-bar tabs + +As an example, take the new [[https://www.gnu.org/software/emacs/manual/html_node/emacs/Tab-Bars.html][tab bars]] from Emacs 27. I'll explain how +to configure Embark to offer tab-specific actions when you use the +tab-bar-mode commands that mention tabs by name. The configuration +explained here is now built-in to Embark (and Marginalia), but it's +still a good self-contained example. In order to setup up tab actions +you would need to: (1) make sure Embark knows those commands deal with +tabs, (2) define a keymap for tab actions and configure Embark so it +knows that's the keymap you want. + +**** Telling Embark about commands that prompt for tabs by name + +For step (1), it would be great if the =tab-bar-mode= commands reported +the completion category =tab= when asking you for a tab with +completion. (All built-in Emacs commands that prompt for file names, +for example, do have metadata indicating that they want a =file=.) They +do not, unfortunately, and I will describe a couple of ways to deal +with this. + +Maybe the easiest thing is to configure [[https://github.com/minad/marginalia][Marginalia]] to enhance those +commands. All of the =tab-bar-*-tab-by-name= commands have the words +"tab by name" in the minibuffer prompt, so you can use: + +#+begin_src emacs-lisp + (add-to-list 'marginalia-prompt-categories '("tab by name" . tab)) +#+end_src + +That's it! But in case you are ever in a situation where you don't +already have commands that prompt for the targets you want, I'll +describe how writing your own command with appropriate =category= +metadata looks: + +#+begin_src emacs-lisp + (defun my-select-tab-by-name (tab) + (interactive + (list + (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) + (tab-bar-tabs)) + (user-error "No tabs found")))) + (completing-read + "Tabs: " + (lambda (string predicate action) + (if (eq action 'metadata) + '(metadata (category . tab)) + (complete-with-action + action tab-list string predicate))))))) + (tab-bar-select-tab-by-name tab)) +#+end_src + +As you can see, the built-in support for setting the category +meta-datum is not very easy to use or pretty to look at. To help with +this I recommend the =consult--read= function from the excellent +[[https://github.com/minad/consult/][Consult]] package. With that function we can rewrite the command as +follows: + +#+begin_src emacs-lisp + (defun my-select-tab-by-name (tab) + (interactive + (list + (let ((tab-list (or (mapcar (lambda (tab) (cdr (assq 'name tab))) + (tab-bar-tabs)) + (user-error "No tabs found")))) + (consult--read tab-list + :prompt "Tabs: " + :category 'tab)))) + (tab-bar-select-tab-by-name tab)) +#+end_src + +Much nicer! No matter how you define the =my-select-tab-by-name= +command, the first approach with Marginalia and prompt detection has +the following advantages: you get the =tab= category for all the +=tab-bar-*-bar-by-name= commands at once, also, you enhance built-in +commands, instead of defining new ones. + +**** Defining and configuring a keymap for tab actions + + Let's say we want to offer select, rename and close actions for tabs + (in addition to Embark general actions, such as saving the tab name to + the kill-ring, which you get for free). Then this will do: + + #+begin_src emacs-lisp + (defvar-keymap embark-tab-actions + :doc "Keymap for actions for tab-bar tabs (when mentioned by name)." + :parent embark-general-map + "s" #'tab-bar-select-tab-by-name + "r" #'tab-bar-rename-tab-by-name + "k" #'tab-bar-close-tab-by-name) + + (add-to-list 'embark-keymap-alist '(tab . embark-tab-actions)) + #+end_src + + What if after using this for a while you feel closing the tab + without confirmation is dangerous? You have a couple of options: + + 1. You can keep using the =tab-bar-close-tab-by-name= command, but have + Embark ask you for confirmation: + #+begin_src emacs-lisp + (push #'embark--confirm + (alist-get 'tab-bar-close-tab-by-name + embark-pre-action-hooks)) + #+end_src + + 2. You can write your own command that prompts for confirmation and + use that instead of =tab-bar-close-tab-by-name= in the above keymap: + #+begin_src emacs-lisp + (defun my-confirm-close-tab-by-name (tab) + (interactive "sTab to close: ") + (when (y-or-n-p (format "Close tab '%s'? " tab)) + (tab-bar-close-tab-by-name tab))) + #+end_src + + Notice that this is a command you can also use directly from =M-x= + independently of Embark. Using it from =M-x= leaves something to be + desired, though, since you don't get completion for the tab names. + You can fix this if you wish as described in the previous section. + +*** New target example in regular buffers - short Wikipedia links + +Say you want to teach Embark to treat text of the form +=wikipedia:Garry_Kasparov= in any regular buffer as a link to Wikipedia, +with actions to open the Wikipedia page in eww or an external browser +or to save the URL of the page in the kill-ring. We can take advantage +of the actions that Embark has preconfigured for URLs, so all we need +to do is teach Embark that =wikipedia:Garry_Kasparov= stands for the URL +=https://en.wikipedia.org/wiki/Garry_Kasparov=. + +You can be as fancy as you want with the recognized syntax. Here, to +keep the example simple, I'll assume the link matches the regexp +=wikipedia:[[:alnum:]_]+=. We will write a function that looks for a +match surrounding point, and returns a dotted list of the form ='(url +URL-OF-THE-PAGE START . END)= where =START= and =END= are the buffer +positions bounding the target, and are used by Embark to highlight it +if you have =embark-highlight-indicator= included in the list +=embark-indicators=. (There are a couple of other options for the return +value of a target finder: the bounding positions are optional and a +single target finder is allowed to return multiple targets; see the +documentation for =embark-target-finders= for details.) + +#+begin_src emacs-lisp + (defun my-short-wikipedia-link () + "Target a link at point of the form wikipedia:Page_Name." + (save-excursion + (let* ((start (progn (skip-chars-backward "[:alnum:]_:") (point))) + (end (progn (skip-chars-forward "[:alnum:]_:") (point))) + (str (buffer-substring-no-properties start end))) + (save-match-data + (when (string-match "wikipedia:\\([[:alnum:]_]+\\)" str) + `(url + ,(format "https://en.wikipedia.org/wiki/%s" + (match-string 1 str)) + ,start . ,end)))))) + + (add-to-list 'embark-target-finders 'my-short-wikipedia-link) +#+end_src + +* How does Embark call the actions? + + Embark actions are normal Emacs commands, that is, functions with an + interactive specification. In order to execute an action, Embark + calls the command with =call-interactively=, so the command reads user + input exactly as if run directly by the user. For example the + command may open a minibuffer and read a string + (=read-from-minibuffer=) or open a completion interface + (=completing-read=). If this happens, Embark takes the target string + and inserts it automatically into the minibuffer, simulating user + input this way. After inserting the string, Embark exits the + minibuffer, submitting the input. (The immediate minibuffer exit can + be disabled for specific actions in order to allow editing the + input; this is done by adding the =embark--allow-edit= function to the + appropriate entry of =embark-target-injection-hooks=). Embark inserts + the target string at the first minibuffer opened by the action + command, and if the command happens to prompt the user for input + more than once, the user still interacts with the second and further + prompts in the normal fashion. Note that if a command does not + prompt the user for input in the minibuffer, Embark still allows you + to use it as an action, but of course, never inserts the target + anywhere. (There are plenty of examples in the default configuration + of commands that do not prompt the user bound to keys in the action + maps, most of the region actions, for instance.) + + This is how Embark manages to reuse normal commands as actions. The + mechanism allows you to use as Embark actions commands that were not + written with Embark in mind (and indeed almost all actions that are + bound by default in Embark's action keymaps are standard Emacs + commands). It also allows you to write new custom actions in such a + way that they are useful even without Embark. + + Staring from version 28.1, Emacs has a variable + =y-or-n-p-use-read-key=, which when set to =t= causes =y-or-n-p= to use + =read-key= instead of =read-from-minibuffer=. Setting + =y-or-n-p-use-read-key= to =t= is recommended for Embark users because + it keeps Embark from attempting to insert the target at a =y-or-n-p= + prompt, which would almost never be sensible. Also consider this as + a warning to structure your own action commands so that if they use + =y-or-n-p=, they do so only after the prompting for the target. + + Here is a simple example illustrating the various ways of reading + input from the user mentioned above. Bind the following commands to + the =embark-symbol-map= to be used as actions, then put the point on + some symbol and run them with =embark-act=: + + #+begin_src emacs-lisp + (defun example-action-command1 () + (interactive) + (message "The input was `%s'." (read-from-minibuffer "Input: "))) + + (defun example-action-command2 (arg input1 input2) + (interactive "P\nsInput 1: \nsInput 2: ") + (message "The first input %swas `%s', and the second was `%s'." + (if arg "truly " "") + input1 + input2)) + + (defun example-action-command3 () + (interactive) + (message "Your selection was `%s'." + (completing-read "Select: " '("E" "M" "B" "A" "R" "K")))) + + (defun example-action-command4 () + (interactive) + (message "I don't prompt you for input and thus ignore the target!")) + + (keymap-set embark-symbol-map "X 1" #'example-action-command1) + (keymap-set embark-symbol-map "X 2" #'example-action-command2) + (keymap-set embark-symbol-map "X 3" #'example-action-command3) + (keymap-set embark-symbol-map "X 4" #'example-action-command4) + #+end_src + + Also note that if you are using the key bindings to call actions, + you can pass prefix arguments to actions in the normal way. For + example, you can use =C-u X2= with the above demonstration actions to + make the message printed by =example-action-command2= more emphatic. + This ability to pass prefix arguments to actions is useful for some + actions in the default configuration, such as + =embark-shell-command-on-buffer=. + +** Non-interactive functions as actions + + Alternatively, Embark does support one other type of action: a + non-interactive function of a single argument. The target is passed + as argument to the function. For example: + + #+begin_src emacs-lisp + (defun example-action-function (target) + (message "The target was `%s'." target)) + + (keymap-set embark-symbol-map "X 4" #'example-action-function) + #+end_src + + Note that normally binding non-interactive functions in a keymap is + useless, since when attempting to run them using the key binding you + get an error message similar to "Wrong type argument: commandp, + example-action-function". In general it is more flexible to write + any new Embark actions as commands, that is, as interactive + functions, because that way you can also run them directly, without + Embark. But there are a couple of reasons to use non-interactive + functions as actions: + + 1. You may already have the function lying around, and it is + convenient to simply reuse it. + + 2. For command actions the targets can only be simple string, with + no text properties. For certain advanced uses you may want the + action to receive a string /with/ some text properties, or even a + non-string target. + +* Embark, Marginalia and Consult + +Embark cooperates well with the [[https://github.com/minad/marginalia][Marginalia]] and [[https://github.com/minad/consult][Consult]] packages. +Neither of those packages is a dependency of Embark, but both are +highly recommended companions to Embark, for opposite reasons: +Marginalia greatly enhances Embark's usefulness, while Embark can help +enhance Consult. + +In the remainder of this section I'll explain what exactly Marginalia +does for Embark, and what Embark can do for Consult. + +** Marginalia + +Embark comes with actions for symbols (commands, functions, variables +with actions such as finding the definition, looking up the +documentation, evaluating, etc.) in the =embark-symbol-map= keymap, and +for packages (actions like install, delete, browse url, etc.) in the +=embark-package-keymap=. + +Unfortunately Embark does not automatically offers you these keymaps +when relevant, because many built-in Emacs commands don't report +accurate category metadata. For example, a command like +=describe-package=, which reads a package name from the minibuffer, +does not have metadata indicating this fact. + +In an earlier Embark version, there were functions to supply this +missing metadata, but they have been moved to Marginalia, which +augments many Emacs command to report accurate category metadata. +Simply activating =marginalia-mode= allows Embark to offer you the +package and symbol actions when appropriate again. Candidate +annotations in the Embark collect buffer are also provided by the +Marginalia package: + +- If you install Marginalia and activate =marginalia-mode=, Embark + Collect buffers will use the Marginalia annotations automatically. + +- If you don't install Marginalia, you will see only the annotations + that come with Emacs (such as key bindings in =M-x=, or the unicode + characters in =C-x 8 RET=). + +** Consult + +The excellent Consult package provides many commands that use +minibuffer completion, via the =completing-read= function; plenty of its +commands can be considered enhanced versions of built-in Emacs +commands, and some are completely new functionality. One common +enhancement provided in all commands for which it makes sense is +preview functionality, for example =consult-buffer= will show you a +quick preview of a buffer before you actually switch to it. + +If you use both Consult and Embark you should install the +=embark-consult= package which provides integration between the two. It +provides exporters for several Consult commands and also tweaks the +behavior of many Consult commands when used as actions with =embark-act= +in subtle ways that you may not even notice, but make for a smoother +experience. You need only install it to get these benefits: Embark +will automatically load it after Consult if found. + +The =embark-consult= package provides the following exporters: + +- You can use =embark-export= from =consult-line=, =consult-outline=, or + =consult-mark= to obtain an =occur-mode= buffer. As with the built-in + =occur= command you use that buffer to jump to a match and after that, + you can then use =next-error= and =previous-error= to navigate to other + matches. You can also press =e= to activate =occur-edit-mode= and edit + the matches in place! + +- You can export from any of the Consult asynchronous search commands, + =consult-grep=, =consult-git-grep=, or =consult-ripgrep= to get a + =grep-mode= buffer. Here too you can use =next-error= and =previous-error= + to navigate among matches, and, if you install the [[http://github.com/mhayashi1120/Emacs-wgrep/raw/master/wgrep.el ][wgrep]] package, + you can use it to edit the matches in place. + +In both cases, pressing =g= will rerun the Consult command you had +exported from and re-enter the input you had typed (which is similar +to reverting but a little more flexible). You can then proceed to +re-export if that's what you want, but you can also edit the input +changing the search terms or simply cancel if you see you are done +with that search. + +The =embark-consult= also contains some candidates collectors that allow +you to run =embark-live= to get a live-updating table of contents for +your buffer: + +- =embark-consult-outline-candidates= produces the outline headings of + the current buffer, using =consult-outline=. +- =embark-consult-imenu-candidates= produces the imenu items of + the current buffer, using =consult-imenu=. +- =embark-consult-imenu-or-outline-candidates= is a simple combination + of the two previous functions: it produces imenu items in buffers + deriving from =prog-mode= and otherwise outline headings. + +The way to configure =embark-live= (or =embark-collect= and =embark-export= +for that matter) to use one of these function is to add it at the end +of the =embark-candidate-collectors= list. The =embark-consult= package by +default adds the last one, which seems to be the most sensible +default. + +Besides those exporters and candidate collectors, the =embark-consult= +package provides many subtle tweaks and small integrations between +Embark and Consult. Some examples are: + +- When used as actions, the asynchronous search commands will search + only the files associated to the targets: if the targets /are/ files, + it searches those files; for buffers it will search either the + associated file if there is one, else all files in the buffer's + =default-directory=; for bookmarks it will search the file they point + to, same for Emacs Lisp libraries. This is particularly powerful + when using =embark-act-all= to act on multiple files at once, for + example you can use =consult-find= to search among file /names/ and then + =embark-act-all= and =consult-grep= to search within the matching files. + + - For all other target types, those that do not have a sensible + notion of associated file, a Consult search command (asynchronous + or not) will search for the text of the target but leave the + minibuffer open so you can interact with the Consult command. + +- =consult-imenu= will search for the target and take you directly to + the location if it matches a unique imenu entry, otherwise it will + leave the minibuffer open so you can navigate among the matches. + +* Related Packages + +There are several packages that offer functionality similar +to Embark's. + +- Acting on minibuffer completion candidates :: The popular Ivy and + Helm packages have support for acting on the completion candidates + of commands written using their APIs, and there is an extensive + ecosystem of packages meant for Helm and for Ivy (the Ivy ones + usually have "counsel" in the name) providing commands and + appropriate actions. +- Acting on things at point :: The built-in =context-menu-mode= provides + a mouse-driven context-sensitive configurable menu. The =do-at-point= + package by Philip Kaludercic (available on GNU ELPA), on the other + hand is keyboard-driven. +- Collecting completion candidates into a buffer :: The Ivy package + has the command =ivy-occur= which is similar to =embark-collect=. As + with Ivy actions, =ivy-occur= only works for commands written using + the Ivy API. + +* Resources + +If you want to learn more about how others have used Embark here are +some links to read: + +- [[https://karthinks.com/software/fifteen-ways-to-use-embark/][Fifteen ways to use Embark]], a blog post by Karthik Chikmagalur. +- [[https://protesilaos.com/dotemacs/][Protesilaos Stavrou's dotemacs]], look for the section called + "Extended minibuffer actions and more (embark.el and + prot-embark.el)" + +And some videos to watch: + +- [[https://protesilaos.com/codelog/2021-01-09-emacs-embark-extras/][Embark and my extras]] by Protesilaos Stavrou. +- [[https://youtu.be/qpoQiiinCtY][Embark -- Key features and tweaks]] by Raoul Comninos on the + Emacs-Elements YouTube channel. +- [[https://youtu.be/WsxXr1ncukY][Livestreamed: Adding an Embark context action to send a stream + message]] by Sacha Chua. +- [[https://youtu.be/qk2Is_sC8Lk][System Crafters Live! - The Many Uses of Embark]] by David Wilson. +- [[https://youtu.be/5ffb2at2d7w][Using Emacs Episode 80 - Vertico, Marginalia, Consult and Embark]] by + Mike Zamansky. + +* Contributions + +Contributions to Embark are very welcome. There is a [[https://github.com/oantolin/embark/issues/95][wish list]] for +actions, target finders, candidate collectors and exporters. For other +ideas you have for Embark, feel free to open an issue on the [[https://github.com/oantolin/embark/issues][issue +tracker]]. Any neat configuration tricks you find might be a good fit +for the [[https://github.com/oantolin/embark/wiki][wiki]]. + +Code contributions are very welcome too, but since Embark is now on +GNU ELPA, copyright assignment to the FSF is required before you can +contribute code. + +* Acknowledgments + +While I, Omar Antolín Camarena, have written most of the Embark code +and remain very stubborn about some of the design decisions, Embark +has received substantial help from a number of other people which this +document has neglected to mention for far too long. In particular, +Daniel Mendler has been absolutely invaluable, implementing several +important features, and providing a lot of useful advice. + +Code contributions: + +- [[https://github.com/minad][Daniel Mendler]] +- [[https://github.com/clemera/][Clemens Radermacher]] +- [[https://codeberg.org/jao/][José Antonio Ortega Ruiz]] +- [[https://github.com/iyefrat][Itai Y. Efrat]] +- [[https://github.com/a13][a13]] +- [[https://github.com/jakanakaevangeli][jakanakaevangeli]] +- [[https://github.com/mihakam][mihakam]] +- [[https://github.com/leungbk][Brian Leung]] +- [[https://github.com/karthink][Karthik Chikmagalur]] +- [[https://github.com/roshanshariff][Roshan Shariff]] +- [[https://github.com/condy0919][condy0919]] +- [[https://github.com/DamienCassou][Damien Cassou]] +- [[https://github.com/JimDBh][JimDBh]] + +Advice and useful discussions: + +- [[https://github.com/minad][Daniel Mendler]] +- [[https://gitlab.com/protesilaos/][Protesilaos Stavrou]] +- [[https://github.com/clemera/][Clemens Radermacher]] +- [[https://github.com/hmelman/][Howard Melman]] +- [[https://github.com/astoff][Augusto Stoffel]] +- [[https://github.com/bdarcus][Bruce d'Arcus]] +- [[https://github.com/jdtsmith][JD Smith]] +- [[https://github.com/karthink][Karthik Chikmagalur]] +- [[https://github.com/jakanakaevangeli][jakanakaevangeli]] +- [[https://github.com/iyefrat][Itai Y. Efrat]] +- [[https://github.com/mohkale][Mohsin Kaleem]] blob - /dev/null blob + 8bc9ea9a4406cb2149b341c21c0a20aa31ce4cba (mode 644) --- /dev/null +++ elpa/embark-consult-1.1/embark-consult-autoloads.el @@ -0,0 +1,28 @@ +;;; embark-consult-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 embark-consult.el + +(register-definition-prefixes "embark-consult" '("embark-consult-")) + +;;; End of scraped data + +(provide 'embark-consult-autoloads) + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; no-native-compile: t +;; coding: utf-8-emacs-unix +;; End: + +;;; embark-consult-autoloads.el ends here blob - /dev/null blob + 943847c2ba4e2c70af821ac08ec0e3d034b2bdf6 (mode 644) --- /dev/null +++ elpa/embark-consult-1.1/embark-consult-pkg.el @@ -0,0 +1,2 @@ +;; Generated package description from embark-consult.el -*- no-byte-compile: t -*- +(define-package "embark-consult" "1.1" "Consult integration for Embark" '((emacs "27.1") (compat "29.1.4.0") (embark "1.0") (consult "1.0")) :commit "195add1f1ccd1059472c9df7334c97c4d155425e" :authors '(("Omar Antolín Camarena" . "omar@matem.unam.mx")) :maintainer '("Omar Antolín Camarena" . "omar@matem.unam.mx") :keywords '("convenience") :url "https://github.com/oantolin/embark") blob - /dev/null blob + 0bdbc728799f0597f3d1fa4d1300ee344b252a3e (mode 644) --- /dev/null +++ elpa/embark-consult-1.1/embark-consult.el @@ -0,0 +1,487 @@ +;;; embark-consult.el --- Consult integration for Embark -*- lexical-binding: t; -*- + +;; Copyright (C) 2021-2023 Free Software Foundation, Inc. + +;; Author: Omar Antolín Camarena +;; Maintainer: Omar Antolín Camarena +;; Keywords: convenience +;; Version: 1.1 +;; Homepage: https://github.com/oantolin/embark +;; Package-Requires: ((emacs "27.1") (compat "29.1.4.0") (embark "1.0") (consult "1.0")) + +;; 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: + +;; This package provides integration between Embark and Consult. The package +;; will be loaded automatically by Embark. + +;; Some of the functionality here was previously contained in Embark +;; itself: + +;; - Support for consult-buffer, so that you get the correct actions +;; for each type of entry in consult-buffer's list. + +;; - Support for consult-line, consult-outline, consult-mark and +;; consult-global-mark, so that the insert and save actions don't +;; include a weird unicode character at the start of the line, and so +;; you can export from them to an occur buffer (where occur-edit-mode +;; works!). + +;; Just load this package to get the above functionality, no further +;; configuration is necessary. + +;; Additionally this package contains some functionality that has +;; never been in Embark: access to Consult preview from auto-updating +;; Embark Collect buffer that is associated to an active minibuffer +;; for a Consult command. For information on Consult preview, see +;; Consult's info manual or its readme on GitHub. + +;; If you always want the minor mode enabled whenever it possible use: + +;; (add-hook 'embark-collect-mode-hook #'consult-preview-at-point-mode) + +;; If you don't want the minor mode automatically on and prefer to +;; trigger the consult previews manually use this instead: + +;; (keymap-set embark-collect-mode-map "C-j" +;; #'consult-preview-at-point) + +;;; Code: + +(require 'embark) +(require 'consult) + +(eval-when-compile + (require 'cl-lib)) + +;;; Consult preview from Embark Collect buffers + +(defun embark-consult--collect-candidate () + "Return candidate at point in collect buffer." + (cadr (embark-target-collect-candidate))) + +(add-hook 'consult--completion-candidate-hook #'embark-consult--collect-candidate) + +;;; Support for consult-location + +(defun embark-consult--strip (string) + "Strip substrings marked with the `consult-strip' property from STRING." + (if (text-property-not-all 0 (length string) 'consult-strip nil string) + (let ((end (length string)) (pos 0) (chunks)) + (while (< pos end) + (let ((next (next-single-property-change pos 'consult-strip string end))) + (unless (get-text-property pos 'consult-strip string) + (push (substring string pos next) chunks)) + (setq pos next))) + (apply #'concat (nreverse chunks))) + string)) + +(defun embark-consult--target-strip (type target) + "Remove the unicode suffix character from a TARGET of TYPE." + (cons type (embark-consult--strip target))) + +(setf (alist-get 'consult-location embark-transformer-alist) + #'embark-consult--target-strip) + +(defun embark-consult-goto-location (target) + "Jump to consult location TARGET." + (consult--jump (car (consult--get-location target))) + (pulse-momentary-highlight-one-line (point))) + +(setf (alist-get 'consult-location embark-default-action-overrides) + #'embark-consult-goto-location) + +(defun embark-consult-export-location-occur (lines) + "Create an occur mode buffer listing LINES. +The elements of LINES should be completion candidates with +category `consult-line'." + (let ((buf (generate-new-buffer "*Embark Export Occur*")) + (mouse-msg "mouse-2: go to this occurrence") + (inhibit-read-only t) + last-buf) + (with-current-buffer buf + (dolist (line lines) + (pcase-let* + ((`(,loc . ,num) (consult--get-location line)) + ;; the text properties added to the following strings are + ;; taken from occur-engine + (lineno (propertize + (format "%7d:" num) + 'occur-prefix t + ;; Allow insertion of text at the end + ;; of the prefix (for Occur Edit mode). + 'front-sticky t + 'rear-nonsticky t + 'read-only t + 'occur-target loc + 'follow-link t + 'help-echo mouse-msg + 'font-lock-face list-matching-lines-prefix-face + 'mouse-face 'highlight)) + (contents (propertize (embark-consult--strip line) + 'occur-target loc + 'occur-match t + 'follow-link t + 'help-echo mouse-msg + 'mouse-face 'highlight)) + (nl (propertize "\n" 'occur-target loc)) + (this-buf (marker-buffer loc))) + (unless (eq this-buf last-buf) + (insert (propertize + (format "lines from buffer: %s\n" this-buf) + 'face list-matching-lines-buffer-name-face + 'read-only t)) + (setq last-buf this-buf)) + (insert lineno contents nl))) + (goto-char (point-min)) + (occur-mode)) + (pop-to-buffer buf))) + +(defun embark-consult-export-location-grep (lines) + "Create a grep mode buffer listing LINES. +Any LINES that come from a buffer which is not visiting a file +will be excluded from the grep buffer, since grep mode only works +with files. The elements of LINES should be completion +candidates with category `consult-location'. No matches will be +highlighted in the exported buffer, since the `consult-location' +candidates do not carry that information." + (let (non-file-buffers) + (embark-consult--export-grep + :header "Exported line search results (file-backed buffers only):\n\n" + :lines lines + :insert + (lambda (lines) + (let ((count 0)) + (dolist (line lines) + (pcase-let* ((`(,loc . ,num) (consult--get-location line)) + (lineno (format "%d" num)) + (contents (embark-consult--strip line)) + (buffer (marker-buffer loc)) + (file (buffer-file-name buffer))) + (if (null file) + (cl-pushnew buffer non-file-buffers) + (insert (file-relative-name file) ":" lineno ":" contents "\n") + (cl-incf count)))) + count)) + :footer + (lambda () + (when non-file-buffers + (let ((start (goto-char (point-max)))) + (insert "\nSome results were in buffers with no associated file" + " and are missing\nfrom the exported result:\n") + (dolist (buf non-file-buffers) + (insert "- " (buffer-name buf) "\n")) + (insert "\nEither save the buffers or use the" + " `embark-consult-export-location-occur'\nexporter.") + (message "This exporter does not support non-file buffers: %s" + non-file-buffers) + (add-text-properties + start (point-max) + '(read-only t wgrep-footer t front-sticky t)))))))) + +(defun embark-consult--upgrade-markers () + "Upgrade consult-location cheap markers to real markers. +This function is meant to be added to `embark-collect-mode-hook'." + (when (eq embark--type 'consult-location) + (dolist (entry tabulated-list-entries) + (when (car entry) + (consult--get-location (car entry)))))) + +;; Set default `occur-mode' based exporter for consult-line, +;; consult-line-multi, consult-outline and alike Another option is +;; using grep-mode by using `embark-consult-export-location-grep' +(setf (alist-get 'consult-location embark-exporters-alist) + #'embark-consult-export-location-occur) +(cl-pushnew #'embark-consult--upgrade-markers embark-collect-mode-hook) + +;;; Support for consult-grep + +(defvar grep-mode-line-matches) +(defvar grep-num-matches-found) +(declare-function wgrep-setup "ext:wgrep") + +(defvar-keymap embark-consult-rerun-map + :doc "A keymap with a binding for `embark-rerun-collect-or-export'." + :parent nil + "g" #'embark-rerun-collect-or-export) + +(cl-defun embark-consult--export-grep (&key header lines insert footer) + "Create a grep mode buffer listing LINES. +The HEADER string is inserted at the top of the buffer. The +function INSERT is called to insert the LINES and should return a +count of the matches (there may be more than one match per line). +The function FOOTER is called to insert a footer." + (let ((buf (generate-new-buffer "*Embark Export Grep*"))) + (with-current-buffer buf + (insert (propertize header 'wgrep-header t 'front-sticky t)) + (let ((count (funcall insert lines))) + (funcall footer) + (goto-char (point-min)) + (grep-mode) + (setq-local grep-num-matches-found count + mode-line-process grep-mode-line-matches)) + ;; Make this buffer current for next/previous-error + (setq next-error-last-buffer buf) + ;; Set up keymap before possible wgrep-setup, so that wgrep + ;; restores our binding too when the user finishes editing. + (use-local-map (make-composed-keymap + embark-consult-rerun-map + (current-local-map))) + ;; TODO Wgrep 3.0 and development versions use different names for the + ;; parser variable. + (defvar wgrep-header/footer-parser) + (defvar wgrep-header&footer-parser) + (setq-local wgrep-header/footer-parser #'ignore + wgrep-header&footer-parser #'ignore) + (when (fboundp 'wgrep-setup) (wgrep-setup))) + (pop-to-buffer buf))) + +(defun embark-consult-export-grep (lines) + "Create a grep mode buffer listing LINES. +The elements of LINES should be completion candidates with +category `consult-grep'." + (embark-consult--export-grep + :header "Exported grep results:\n\n" + :lines lines + :insert + (lambda (lines) + (dolist (line lines) (insert line "\n")) + (goto-char (point-min)) + (let ((count 0) prop) + (while (setq prop (text-property-search-forward + 'face 'consult-highlight-match t)) + (cl-incf count) + (put-text-property (prop-match-beginning prop) + (prop-match-end prop) + 'font-lock-face + 'match)) + count)) + :footer #'ignore)) + +(defun embark-consult-goto-grep (location) + "Go to LOCATION, which should be a string with a grep match." + (consult--jump (consult--grep-position location)) + (pulse-momentary-highlight-one-line (point))) + +(setf (alist-get 'consult-grep embark-default-action-overrides) + #'embark-consult-goto-grep) +(setf (alist-get 'consult-grep embark-exporters-alist) + #'embark-consult-export-grep) + +;;; Support for consult-xref + +(declare-function xref--show-xref-buffer "ext:xref") +(declare-function consult-xref "ext:consult-xref") +(defvar xref-auto-jump-to-first-xref) +(defvar consult-xref--fetcher) + +(defun embark-consult-export-xref (items) + "Create an xref buffer listing ITEMS." + (cl-flet ((xref-items (items) + (mapcar (lambda (item) (get-text-property 0 'consult-xref item)) + items))) + (let ((fetcher consult-xref--fetcher) + (input (minibuffer-contents))) + (set-buffer + (xref--show-xref-buffer + (lambda () + (let ((candidates (funcall fetcher))) + (if (null (cdr candidates)) + candidates + (catch 'xref-items + (minibuffer-with-setup-hook + (lambda () + (insert input) + (add-hook + 'minibuffer-exit-hook + (lambda () + (throw 'xref-items + (xref-items + (or + (plist-get + (embark--maybe-transform-candidates) + :candidates) + (user-error "No candidates for export"))))) + nil t)) + (consult-xref fetcher)))))) + `((fetched-xrefs . ,(xref-items items)) + (window . ,(embark--target-window)) + (auto-jump . ,xref-auto-jump-to-first-xref) + (display-action))))))) + +(setf (alist-get 'consult-xref embark-exporters-alist) + #'embark-consult-export-xref) + +;;; Support for consult-find and consult-locate + +(setf (alist-get '(file . consult-find) embark-default-action-overrides + nil nil #'equal) + #'find-file) + +(setf (alist-get '(file . consult-locate) embark-default-action-overrides + nil nil #'equal) + #'find-file) + +;;; Support for consult-isearch-history + +(setf (alist-get 'consult-isearch-history embark-transformer-alist) + #'embark-consult--target-strip) + +;;; Support for consult-man and consult-info + +(defun embark-consult-man (cand) + "Default action override for `consult-man', open CAND man page." + (man (get-text-property 0 'consult-man cand))) + +(setf (alist-get 'consult-man embark-default-action-overrides) + #'embark-consult-man) + +(declare-function consult-info--action "ext:consult-info") + +(defun embark-consult-info (cand) + "Default action override for `consult-info', open CAND info manual." + (consult-info--action cand) + (pulse-momentary-highlight-one-line (point))) + +(setf (alist-get 'consult-info embark-default-action-overrides) + #'embark-consult-info) + +(setf (alist-get 'consult-info embark-transformer-alist) + #'embark-consult--target-strip) + +;;; Bindings for consult commands in embark keymaps + +(keymap-set embark-become-file+buffer-map "C b" #'consult-buffer) +(keymap-set embark-become-file+buffer-map "C 4 b" #'consult-buffer-other-window) + +;;; Support for Consult search commands + +(defvar-keymap embark-consult-sync-search-map + :doc "Keymap for Consult sync search commands" + :parent nil + "o" #'consult-outline + "i" 'consult-imenu + "I" 'consult-imenu-multi + "l" #'consult-line + "L" #'consult-line-multi) + +(defvar-keymap embark-consult-async-search-map + :doc "Keymap for Consult async search commands" + :parent nil + "g" #'consult-grep + "r" #'consult-ripgrep + "G" #'consult-git-grep + "f" #'consult-find + "F" #'consult-locate) + +(defvar embark-consult-search-map + (keymap-canonicalize + (make-composed-keymap embark-consult-sync-search-map + embark-consult-async-search-map)) + "Keymap for all Consult search commands.") + +(fset 'embark-consult-sync-search-map embark-consult-sync-search-map) +(keymap-set embark-become-match-map "C" 'embark-consult-sync-search-map) + +(cl-pushnew 'embark-consult-async-search-map embark-become-keymaps) + +(fset 'embark-consult-search-map embark-consult-search-map) +(keymap-set embark-general-map "C" 'embark-consult-search-map) + +(map-keymap + (lambda (_key cmd) + (cl-pushnew 'embark--unmark-target + (alist-get cmd embark-pre-action-hooks)) + (cl-pushnew 'embark--allow-edit + (alist-get cmd embark-target-injection-hooks))) + embark-consult-search-map) + +(defun embark-consult--unique-match (&rest _) + "If there is a unique matching candidate, accept it. +This is intended to be used in `embark-target-injection-hooks'." + (let ((candidates (cdr (embark-minibuffer-candidates)))) + (if (or (null candidates) (cdr candidates)) + (embark--allow-edit) + (delete-minibuffer-contents) + (insert (car candidates))))) + +(dolist (cmd '(consult-outline consult-imenu consult-imenu-multi)) + (setf (alist-get cmd embark-target-injection-hooks) + (remq 'embark--allow-edit + (alist-get cmd embark-target-injection-hooks))) + (cl-pushnew #'embark-consult--unique-match + (alist-get cmd embark-target-injection-hooks))) + +(cl-defun embark-consult--async-search-dwim + (&key action type target candidates &allow-other-keys) + "DWIM when using a Consult async search command as an ACTION. +If the TYPE of the target(s) has a notion of associated +file (files, buffers, libraries and some bookmarks do), then run +the ACTION with `consult-project-function' set to nil, and search +only the files associated to the TARGET or CANDIDATES. For other +types, run the ACTION with TARGET or CANDIDATES as initial input." + (if-let ((file-fn (cdr (assq type embark--associated-file-fn-alist)))) + (let (consult-project-function) + (funcall action + (delq nil (mapcar file-fn (or candidates (list target)))))) + (funcall action nil (or target (string-join candidates " "))))) + +(map-keymap + (lambda (_key cmd) + (unless (eq cmd #'consult-locate) + (cl-pushnew cmd embark-multitarget-actions) + (cl-pushnew #'embark-consult--async-search-dwim + (alist-get cmd embark-around-action-hooks)))) + embark-consult-async-search-map) + +;;; Tables of contents for buffers: imenu and outline candidate collectors + +(defun embark-consult-outline-candidates () + "Collect all outline headings in the current buffer." + (cons 'consult-location (consult--outline-candidates))) + +(autoload 'consult-imenu--items "consult-imenu") + +(defun embark-consult-imenu-candidates () + "Collect all imenu items in the current buffer." + (cons 'imenu (mapcar #'car (consult-imenu--items)))) + +(declare-function consult-imenu--group "ext:consult-imenu") + +(defun embark-consult--imenu-group-function (type prop) + "Return a suitable group-function for imenu. +TYPE is the completion category. +PROP is the metadata property. +Meant as :after-until advice for `embark-collect--metadatum'." + (when (and (eq type 'imenu) (eq prop 'group-function)) + (consult-imenu--group))) + +(advice-add #'embark-collect--metadatum :after-until + #'embark-consult--imenu-group-function) + +(defun embark-consult-imenu-or-outline-candidates () + "Collect imenu items in prog modes buffer or outline headings otherwise." + (if (derived-mode-p 'prog-mode) + (embark-consult-imenu-candidates) + (embark-consult-outline-candidates))) + +(setf (alist-get 'imenu embark-default-action-overrides) 'consult-imenu) + +(add-to-list 'embark-candidate-collectors + #'embark-consult-imenu-or-outline-candidates + 'append) + +(provide 'embark-consult) +;;; embark-consult.el ends here blob - 461858ed444ef6774cc72eb693ea2fa1df587006 (mode 644) blob + /dev/null --- elpa/embark-consult-1.0.signed +++ /dev/null @@ -1,2 +0,0 @@ -Good signature from 066DAFCB81E42C40 GNU ELPA Signing Agent (2019) (trust undefined) created at 2023-12-08T11:05:03+0100 using RSA -Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) (trust undefined) created at 2023-12-08T11:05:03+0100 using EDDSA \ No newline at end of file blob - /dev/null blob + 40cb3e05cfbf429e200afab12345618bacb03dd4 (mode 644) --- /dev/null +++ elpa/embark-consult-1.1.signed @@ -0,0 +1 @@ +Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) (trust undefined) created at 2024-04-19T11:05:04+0200 using EDDSA \ No newline at end of file blob - 34a1ee5cd90f0b187d47a151b89288eef588cbca (mode 644) blob + /dev/null --- elpa/macrostep-0.9.2/.elpaignore +++ /dev/null @@ -1,2 +0,0 @@ -.travis.yml -LICENSE blob - f8e6a17c8c62a7cb48c0c86628730178ef879ff3 (mode 644) blob + /dev/null --- elpa/macrostep-0.9.2/Makefile +++ /dev/null @@ -1,17 +0,0 @@ -EMACS ?= emacs - -all: macrostep.elc macrostep-c.elc - -clean: - rm -f *.elc - -test: clean all - $(EMACS) --batch -L . --load macrostep-test.el - -sandbox: all - $(EMACS) -Q -L . --load macrostep --load macrostep-c - -%.elc: %.el - $(EMACS) --batch -L . --funcall batch-byte-compile "$<" - -.PHONY: test all clean blob - b0608735e2326aacee547dfe00d631076fdbe2cd (mode 644) blob + /dev/null --- elpa/macrostep-0.9.2/README-elpa +++ /dev/null @@ -1,235 +0,0 @@ -1 macrostep: interactive macro-expander -═══════════════════════════════════════ - - `macrostep' is an Emacs minor mode for interactively stepping through - the expansion of macros in Emacs Lisp source code. It lets you see - exactly what happens at each step of the expansion process by - pretty-printing the expanded forms inline in the source buffer, which - is temporarily read-only while macro expansions are visible. You can - expand and collapse macro forms one step at a time, and evaluate or - instrument the expansions for debugging with Edebug as normal (but see - "Bugs and known limitations", below). Single-stepping through the - expansion is particularly useful for debugging macros that expand into - another macro form. These can be difficult to debug with Emacs' - built-in `macroexpand', which continues expansion until the top-level - form is no longer a macro call. - - Both globally-visible macros as defined by `defmacro' and local macros - bound by `(cl-)macrolet' or another macro-defining form can be - expanded. Within macro expansions, calls to macros and compiler - macros are fontified specially: macro forms using - `macrostep-macro-face', and functions with compiler macros using - `macrostep-compiler-macro-face'. Uninterned symbols (gensyms) are - fontified based on which step in the expansion created them, to - distinguish them both from normal symbols and from other gensyms with - the same print name. - - As of version 0.9, it is also possible to extend `macrostep' to work - with other languages with macro systems in addition to Emacs Lisp. An - extension for Common Lisp (via SLIME) is in the works; contributions - for other languages are welcome. See "Extending macrostep" below for - details. - - -1.1 Key-bindings and usage -────────────────────────── - - The standard keybindings in `macrostep-mode' are the following: - - e, =, RET - expand the macro form following point one step - c, u, DEL - collapse the form following point - q, C-c C-c - collapse all expanded forms and exit macrostep-mode - n, TAB - jump to the next macro form in the expansion - p, M-TAB - jump to the previous macro form in the expansion - - It's not very useful to enable and disable macrostep-mode directly. - Instead, bind `macrostep-expand' to a key in `emacs-lisp-mode-map', - for example C-c e: - - ┌──── - │ (define-key emacs-lisp-mode-map (kbd "C-c e") 'macrostep-expand) - └──── - - You can then enter macrostep-mode and expand a macro form completely - by typing `C-c e e e ...' as many times as necessary. - - Exit macrostep-mode by typing `q' or `C-c C-c', or by successively - typing `c' to collapse all surrounding expansions. - - -1.2 Customization options -───────────────────────── - - Type `M-x customize-group RET macrostep RET' to customize options and - faces. - - To display macro expansions in a separate window, instead of inline in - the source buffer, customize `macrostep-expand-in-separate-buffer' to - `t'. The default is `nil'. Whichever default behavior is selected, - the alternative behavior can be obtained temporarily by giving a - prefix argument to `macrostep-expand'. - - To have `macrostep' ignore compiler macros, customize - `macrostep-expand-compiler-macros' to `nil'. The default is `t'. - - Customize the faces `macrostep-macro-face', - `macrostep-compiler-macro-face', and `macrostep-gensym-1' through - `macrostep-gensym-5' to alter the appearance of macro expansions. - - -1.3 Locally-bound macros -──────────────────────── - - As of version 0.9, `macrostep' can expand calls to a locally-bound - macro, whether defined by a surrounding `(cl-)macrolet' form, or by - another macro-defining macro. In other words, it is possible to - expand the inner `local-macro' forms in both the following examples, - whether `local-macro' is defined by an enclosing `cl-macrolet' – - - ┌──── - │ (cl-macrolet ((local-macro (&rest args) - │ `(expansion of ,args))) - │ (local-macro (do-something))) - └──── - - – or by a macro which expands into `cl-macrolet', provided that its - definition of macro is evaluated prior to calling `macrostep-expand': - - ┌──── - │ (defmacro with-local-macro (&rest body) - │ `(cl-macrolet ((local-macro (&rest args) - │ `(expansion of ,args))) - │ ,@body)) - │ - │ (with-local-macro - │ (local-macro (do something (else))) - └──── - - See the `with-js' macro in Emacs's `js.el' for a real example of the - latter kind of macro. - - Expansion of locally-bound macros is implemented by instrumenting - Emacs Lisp's macro-expander to capture the environment at point. A - similar trick is used to detect macro- and compiler-macro calls within - expanded text so that they can be fontified accurately. - - -1.4 Expanding sub-forms -─────────────────────── - - By moving point around in the macro expansion using - `macrostep-next-macro' and `macrostep-prev-macro' (bound to the `n' - and `p' keys), it is possible to expand other macro calls within the - expansion before expanding the outermost form. This can sometimes be - useful, although it does not correspond to the real order of macro - expansion in Emacs Lisp, which proceeds by fully expanding the outer - form to a non-macro form before expanding sub-forms. - - The main reason to expand sub-forms out of order is to help with - debugging macros which programmatically expand their arguments in - order to rewrite them. Expanding the arguments of such a macro lets - you visualise what the macro definition would compute via - `macroexpand-all'. - - -1.5 Extending macrostep for other languages -─────────────────────────────────────────── - - Since version 0.9, it is possible to extend macrostep to work with - other languages besides Emacs Lisp. In typical Emacs fashion, this is - implemented by setting buffer-local variables to different function - values. Six buffer-local variables define the language-specific part - of the implementation: - - • `macrostep-sexp-bounds-function' - • `macrostep-sexp-at-point-function' - • `macrostep-environment-at-point-function' - • `macrostep-expand-1-function' - • `macrostep-print-function' - • `macrostep-macro-form-p-function' - - Typically, an implementation for another language would set these - variables in a major-mode hook. See the docstrings of each variable - for details on how each one is called and what it should return. At a - minimum, another language implementation needs to provide - `macrostep-sexp-at-point-function', `macrostep-expand-1-function', and - `macrostep-print-function'. Lisp-like languages may be able to reuse - the default `macrostep-sexp-bounds-function' if they provide another - implementation of `macrostep-macro-form-p-function'. Languages which - do not implement locally-defined macros can set - `macrostep-environment-at-point-function' to `ignore'. - - Note that the core `macrostep' machinery only interprets the return - value of `macrostep-sexp-bounds-function', so implementations for - other languages can use any internal representations of code and - environments which is convenient. Although the terminology is - Lisp-specific, there is no reason that implementations could not be - provided for non-Lisp languages with macro systems, provided there is - some way of identifying macro calls and calling the compiler / - preprocessor to obtain their expansions. - - -1.6 Bugs and known limitations -────────────────────────────── - - You can evaluate and edebug macro-expanded forms and step through the - macro-expanded version, but the form that `eval-defun' and friends - read from the buffer won't have the uninterned symbols of the real - macro expansion. This will probably work OK with CL-style gensyms, - but may cause problems with `make-symbol' symbols if they have the - same print name as another symbol in the expansion. It's possible that - using `print-circle' and `print-gensym' could get around this. - - Please send other bug reports and feature requests to the author. - - -1.7 Acknowledgements -──────────────────── - - Thanks to: - • John Wiegley for fixing a bug with the face definitions under Emacs - 24 & for plugging macrostep in his [EmacsConf presentation]! - • George Kettleborough for bug reports, and patches to highlight the - expanded region and properly handle backquotes. - • Nic Ferrier for suggesting support for local definitions within - macrolet forms - • Luís Oliveira for suggesting and implementing SLIME support - - `macrostep' was originally inspired by J. V. Toups's 'Deep Emacs Lisp' - articles ([part 1], [part 2], [screencast]). - - -[EmacsConf presentation] - -[part 1] - - -[part 2] - - -[screencast] - - - -1.8 Changelog -───────────── - - • v0.9, 2015-10-01: - • separate into Elisp-specific and generic components - • highlight and expand compiler macros - • improve local macro expansion and macro form identification by - instrumenting `macroexpand(-all)' - • v0.8, 2014-05-29: fix a bug with printing the first element of lists - • v0.7, 2014-05-11: expand locally-defined macros within - `(cl-)macrolet' forms - • v0.6, 2013-05-04: better handling of quote and backquote - • v0.5, 2013-04-16: highlight region, maintain cleaner buffer state - • v0.4, 2013-04-07: only enter macrostep-mode on successful - macro-expansion - • v0.3, 2012-10-30: print dotted lists correctly. autoload - definitions. blob - cdb7fe090808507c5ace00d7700447cec4e6e6c6 (mode 644) blob + /dev/null --- elpa/macrostep-0.9.2/README.org +++ /dev/null @@ -1,203 +0,0 @@ -* macrostep: interactive macro-expander - - =macrostep= is an Emacs minor mode for interactively stepping - through the expansion of macros in Emacs Lisp source code. It lets - you see exactly what happens at each step of the expansion process - by pretty-printing the expanded forms inline in the source buffer, - which is temporarily read-only while macro expansions are visible. - You can expand and collapse macro forms one step at a time, and - evaluate or instrument the expansions for debugging with Edebug as - normal (but see "Bugs and known limitations", below). - Single-stepping through the expansion is particularly useful for - debugging macros that expand into another macro form. These can be - difficult to debug with Emacs' built-in =macroexpand=, which - continues expansion until the top-level form is no longer a macro - call. - - Both globally-visible macros as defined by =defmacro= and local - macros bound by =(cl-)macrolet= or another macro-defining form can - be expanded. Within macro expansions, calls to macros and compiler - macros are fontified specially: macro forms using - =macrostep-macro-face=, and functions with compiler macros using - =macrostep-compiler-macro-face=. Uninterned symbols (gensyms) are - fontified based on which step in the expansion created them, to - distinguish them both from normal symbols and from other gensyms - with the same print name. - - As of version 0.9, it is also possible to extend =macrostep= to - work with other languages with macro systems in addition to Emacs - Lisp. An extension for Common Lisp (via SLIME) is in the works; - contributions for other languages are welcome. See "Extending - macrostep" below for details. - -** Key-bindings and usage - The standard keybindings in =macrostep-mode= are the following: - - - e, =, RET :: expand the macro form following point one step - - c, u, DEL :: collapse the form following point - - q, C-c C-c :: collapse all expanded forms and exit macrostep-mode - - n, TAB :: jump to the next macro form in the expansion - - p, M-TAB :: jump to the previous macro form in the expansion - - It's not very useful to enable and disable macrostep-mode - directly. Instead, bind =macrostep-expand= to a key in - =emacs-lisp-mode-map=, for example C-c e: - -#+BEGIN_SRC emacs-lisp - (define-key emacs-lisp-mode-map (kbd "C-c e") 'macrostep-expand) -#+END_SRC - - You can then enter macrostep-mode and expand a macro form - completely by typing =C-c e e e ...= as many times as necessary. - - Exit macrostep-mode by typing =q= or =C-c C-c=, or by successively - typing =c= to collapse all surrounding expansions. - -** Customization options - Type =M-x customize-group RET macrostep RET= to customize options - and faces. - - To display macro expansions in a separate window, instead of inline - in the source buffer, customize - =macrostep-expand-in-separate-buffer= to =t=. The default is - =nil=. Whichever default behavior is selected, the alternative - behavior can be obtained temporarily by giving a prefix argument to - =macrostep-expand=. - - To have =macrostep= ignore compiler macros, customize - =macrostep-expand-compiler-macros= to =nil=. The default is =t=. - - Customize the faces =macrostep-macro-face=, - =macrostep-compiler-macro-face=, and =macrostep-gensym-1= through - =macrostep-gensym-5= to alter the appearance of macro expansions. - -** Locally-bound macros - As of version 0.9, =macrostep= can expand calls to a locally-bound - macro, whether defined by a surrounding =(cl-)macrolet= form, or by - another macro-defining macro. In other words, it is possible to - expand the inner =local-macro= forms in both the following - examples, whether =local-macro= is defined by an enclosing - =cl-macrolet= -- - - #+BEGIN_SRC emacs-lisp - (cl-macrolet ((local-macro (&rest args) - `(expansion of ,args))) - (local-macro (do-something))) - #+END_SRC - - -- or by a macro which expands into =cl-macrolet=, provided that - its definition of macro is evaluated prior to calling - =macrostep-expand=: - - #+BEGIN_SRC emacs-lisp - (defmacro with-local-macro (&rest body) - `(cl-macrolet ((local-macro (&rest args) - `(expansion of ,args))) - ,@body)) - - (with-local-macro - (local-macro (do something (else))) - #+END_SRC - - See the =with-js= macro in Emacs's =js.el= for a real example of - the latter kind of macro. - - Expansion of locally-bound macros is implemented by instrumenting - Emacs Lisp's macro-expander to capture the environment at point. A - similar trick is used to detect macro- and compiler-macro calls - within expanded text so that they can be fontified accurately. - -** Expanding sub-forms - By moving point around in the macro expansion using - =macrostep-next-macro= and =macrostep-prev-macro= (bound to the =n= - and =p= keys), it is possible to expand other macro calls within - the expansion before expanding the outermost form. This can - sometimes be useful, although it does not correspond to the real - order of macro expansion in Emacs Lisp, which proceeds by fully - expanding the outer form to a non-macro form before expanding - sub-forms. - - The main reason to expand sub-forms out of order is to help with - debugging macros which programmatically expand their arguments in - order to rewrite them. Expanding the arguments of such a macro - lets you visualise what the macro definition would compute via - =macroexpand-all=. - -** Extending macrostep for other languages - Since version 0.9, it is possible to extend macrostep to work with - other languages besides Emacs Lisp. In typical Emacs fashion, this - is implemented by setting buffer-local variables to different - function values. Six buffer-local variables define the - language-specific part of the implementation: - - - =macrostep-sexp-bounds-function= - - =macrostep-sexp-at-point-function= - - =macrostep-environment-at-point-function= - - =macrostep-expand-1-function= - - =macrostep-print-function= - - =macrostep-macro-form-p-function= - - Typically, an implementation for another language would set these - variables in a major-mode hook. See the docstrings of each - variable for details on how each one is called and what it should - return. At a minimum, another language implementation needs to - provide =macrostep-sexp-at-point-function=, - =macrostep-expand-1-function=, and =macrostep-print-function=. - Lisp-like languages may be able to reuse the default - =macrostep-sexp-bounds-function= if they provide another - implementation of =macrostep-macro-form-p-function=. Languages - which do not implement locally-defined macros can set - =macrostep-environment-at-point-function= to =ignore=. - - Note that the core =macrostep= machinery only interprets the return - value of =macrostep-sexp-bounds-function=, so implementations for - other languages can use any internal representations of code and - environments which is convenient. Although the terminology is - Lisp-specific, there is no reason that implementations could not be - provided for non-Lisp languages with macro systems, provided there - is some way of identifying macro calls and calling the compiler / - preprocessor to obtain their expansions. - -** Bugs and known limitations - You can evaluate and edebug macro-expanded forms and step through - the macro-expanded version, but the form that =eval-defun= and - friends read from the buffer won't have the uninterned symbols of - the real macro expansion. This will probably work OK with CL-style - gensyms, but may cause problems with =make-symbol= symbols if they - have the same print name as another symbol in the expansion. It's - possible that using =print-circle= and =print-gensym= could get - around this. - - Please send other bug reports and feature requests to the author. - -** Acknowledgements - Thanks to: - - John Wiegley for fixing a bug with the face definitions under - Emacs 24 & for plugging macrostep in his [[http://youtu.be/RvPFZL6NJNQ][EmacsConf presentation]]! - - George Kettleborough for bug reports, and patches to highlight - the expanded region and properly handle backquotes. - - Nic Ferrier for suggesting support for local definitions within - macrolet forms - - Luís Oliveira for suggesting and implementing SLIME support - - =macrostep= was originally inspired by J. V. Toups's 'Deep Emacs - Lisp' articles ([[http://dorophone.blogspot.co.uk/2011/04/deep-emacs-part-1.html][part 1]], [[http://dorophone.blogspot.co.uk/2011/04/deep-emacs-lisp-part-2.html][part 2]], [[http://dorophone.blogspot.co.uk/2011/05/monadic-parser-combinators-in-elisp.html][screencast]]). - -** Changelog - - v0.9, 2015-10-01: - - separate into Elisp-specific and generic components - - highlight and expand compiler macros - - improve local macro expansion and macro form identification by - instrumenting =macroexpand(-all)= - - v0.8, 2014-05-29: fix a bug with printing the first element of - lists - - v0.7, 2014-05-11: expand locally-defined macros within - =(cl-)macrolet= forms - - v0.6, 2013-05-04: better handling of quote and backquote - - v0.5, 2013-04-16: highlight region, maintain cleaner buffer state - - v0.4, 2013-04-07: only enter macrostep-mode on successful - macro-expansion - - v0.3, 2012-10-30: print dotted lists correctly. autoload - definitions. - -#+OPTIONS: author:nil email:nil toc:nil timestamp:nil blob - eeda2c28537b1f803fa9ad1d51cae239d363243b (mode 644) blob + /dev/null --- elpa/macrostep-0.9.2/macrostep-autoloads.el +++ /dev/null @@ -1,73 +0,0 @@ -;;; macrostep-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 (file-name-directory load-file-name)) (car load-path))) - - - -;;; Generated autoloads from macrostep.el - -(autoload 'macrostep-mode "macrostep" "\ -Minor mode for inline expansion of macros in Emacs Lisp source buffers. - -\\Progressively expand macro forms with \\[macrostep-expand], collapse them with \\[macrostep-collapse], -and move back and forth with \\[macrostep-next-macro] and \\[macrostep-prev-macro]. Use \\[macrostep-collapse-all] or collapse all -visible expansions to quit and return to normal editing. - -\\{macrostep-mode-map} - -This is a minor mode. If called interactively, toggle the -`Macrostep 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 `macrostep-mode'. - -The mode's hook is called both when the mode is enabled and when -it is disabled. - -(fn &optional ARG)" t) -(autoload 'macrostep-expand "macrostep" "\ -Expand the macro form following point by one step. - -Enters `macrostep-mode' if it is not already active, making the -buffer temporarily read-only. If `macrostep-mode' is active and the -form following point is not a macro form, search forward in the -buffer and expand the next macro form found, if any. - -With a prefix argument, the expansion is displayed in a separate -buffer instead of inline in the current buffer. Setting -`macrostep-expand-in-separate-buffer' to non-nil swaps these two -behaviors. - -(fn &optional TOGGLE-SEPARATE-BUFFER)" t) -(register-definition-prefixes "macrostep" '("macrostep-")) - - -;;; Generated autoloads from macrostep-c.el - -(autoload 'macrostep-c-mode-hook "macrostep-c") -(add-hook 'c-mode-hook #'macrostep-c-mode-hook) -(register-definition-prefixes "macrostep-c" '("macrostep-c-")) - -;;; End of scraped data - -(provide 'macrostep-autoloads) - -;; Local Variables: -;; version-control: never -;; no-byte-compile: t -;; no-update-autoloads: t -;; no-native-compile: t -;; coding: utf-8-emacs-unix -;; End: - -;;; macrostep-autoloads.el ends here blob - 226ecce2ac19e32f574682e77bdd3dc97fed4c7d (mode 644) blob + /dev/null --- elpa/macrostep-0.9.2/macrostep-c.el +++ /dev/null @@ -1,183 +0,0 @@ -;;; macrostep-c.el --- macrostep interface to C preprocessor -*- lexical-binding: t; -*- - -;; Copyright (C) 2015 Jon Oddie - -;; Author: Jon Oddie -;; Url: https://github.com/emacsorphanage/macrostep -;; Keywords: c, languages, macro, debugging - -;; SPDX-License-Identifier: GPL-3.0-or-later - -;; This file 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 file 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 file. If not, see . - -;;; Commentary: - -;; A thin wrapper around Emacs's built-in `cmacexp' library to provide -;; basic support for expanding C macros using the `macrostep' user -;; interface. To use, position point on a macro use in a C buffer and -;; type `M-x macrostep-expand'. The variables `c-macro-preprocessor' -;; and especially `c-macro-cppflags' may need to be set correctly for -;; accurate expansion. - -;; This is fairly basic compared to the Emacs Lisp `macrostep'. In -;; particular, there is no step-by-step expansion, since C macros are -;; expanded in a single "cpp" pass, and no pretty-printing. - -;; To hide the buffer containing "cpp" warnings (not recommended), you -;; could do something like: -;; -;; (push `(,(regexp-quote macrostep-c-warning-buffer) -;; (display-buffer-no-window)) -;; display-buffer-alist) - -;;; Code: - -(require 'macrostep) -(require 'cmacexp) -(require 'cl-lib) - -(require 'subr-x nil t) -(defalias 'macrostep-c-string-trim - (if (fboundp 'string-trim) - #'string-trim - (lambda (string) - (when (string-match "\\`[ \t\n\r]+" string) - (setq string (replace-match "" t t string))) - (when (string-match "[ \t\n\r]+\\'" string) - (setq string (replace-match "" t t string))) - string))) - -(put 'macrostep-c-non-macro 'error-conditions - '(macrostep-c-non-macro error)) -(put 'macrostep-c-non-macro 'error-message - "Text around point is not a macro call.") - -(put 'macrostep-c-expansion-failed 'error-conditions - '(macrostep-c-expansion-failed error)) -(put 'macrostep-c-expansion-failed 'error-message - "Macro-expansion failed.") - -(defvar macrostep-c-warning-buffer "*Macroexpansion Warnings*") - -;;;###autoload -(defun macrostep-c-mode-hook () - (setq macrostep-sexp-bounds-function - #'macrostep-c-sexp-bounds) - (setq macrostep-sexp-at-point-function - #'macrostep-c-sexp-at-point) - (setq macrostep-environment-at-point-function - #'ignore) - (setq macrostep-expand-1-function - #'macrostep-c-expand-1) - (setq macrostep-print-function - #'macrostep-c-print-function) - (add-hook 'macrostep-mode-off-hook - #'macrostep-c-mode-off nil t)) - -(defun macrostep-c-mode-off (&rest _ignore) - (when (derived-mode-p 'c-mode) - (let ((warning-window - (get-buffer-window macrostep-c-warning-buffer))) - (when warning-window - (quit-window nil warning-window))))) - -;;;###autoload -(add-hook 'c-mode-hook #'macrostep-c-mode-hook) - -(defun macrostep-c-sexp-bounds () - (save-excursion - (cl-loop - (let ((region (macrostep-c-sexp-bounds-1))) - (cond - ((null region) - (signal 'macrostep-c-non-macro nil)) - ((macrostep-c-expandable-p region) - (cl-return region)) - (t - (condition-case nil - (progn - (backward-up-list) - (skip-syntax-backward "-")) - (scan-error - (signal 'macrostep-c-non-macro nil))))))))) - -(defun macrostep-c-sexp-bounds-1 () - (let ((region (bounds-of-thing-at-point 'symbol))) - (when region - (cl-destructuring-bind (symbol-start . symbol-end) region - (save-excursion - (goto-char symbol-end) - (if (looking-at "[[:space:]]*(") - (cons symbol-start (scan-sexps symbol-end 1)) - region)))))) - -(defun macrostep-c-expandable-p (region) - (cl-destructuring-bind (start . end) region - (condition-case nil - (cl-destructuring-bind (expansion _warnings) - (macrostep-c-expand-region start end) - (and (cl-plusp (length expansion)) - (not (string= expansion (buffer-substring start end))))) - (macrostep-c-expansion-failed nil)))) - -(defun macrostep-c-sexp-at-point (start end) - (cons start end)) - -(defun macrostep-c-expand-1 (region _ignore) - (cl-destructuring-bind (start . end) region - (cl-destructuring-bind (expansion warnings) - (macrostep-c-expand-region start end) - (when (cl-plusp (length warnings)) - (with-current-buffer - (get-buffer-create macrostep-c-warning-buffer) - (let ((inhibit-read-only t)) - (erase-buffer) - (insert warnings) - (goto-char (point-min))) - (special-mode) - (display-buffer (current-buffer) - '(display-buffer-pop-up-window - (inhibit-same-window . t) - (allow-no-window . t))))) - expansion))) - -(defun macrostep-c-expand-region (start end) - (let ((expansion - (condition-case nil - (c-macro-expansion start end - (concat c-macro-preprocessor " " - c-macro-cppflags)) - (search-failed - (signal 'macrostep-c-expansion-failed nil))))) - (with-temp-buffer - (save-excursion - (insert expansion)) - (when (looking-at (regexp-quote "/*")) - (search-forward "*/")) - (let ((warnings (buffer-substring (point-min) (point))) - (expansion (buffer-substring (point) (point-max)))) - (mapcar #'macrostep-c-string-trim (list expansion warnings)))))) - -(defun macrostep-c-print-function (expansion &rest _ignore) - (with-temp-buffer - (insert expansion) - (let ((exit-code - (shell-command-on-region (point-min) (point-max) "indent" nil t))) - (when (zerop exit-code) - (setq expansion (macrostep-c-string-trim (buffer-string)))))) - (insert expansion)) - -(provide 'macrostep-c) - -;;; macrostep-c.el ends here blob - 22b0071fd1b14f0e6f7d71fee44850763327d77a (mode 644) blob + /dev/null --- elpa/macrostep-0.9.2/macrostep-pkg.el +++ /dev/null @@ -1,2 +0,0 @@ -;; Generated package description from macrostep.el -*- no-byte-compile: t -*- -(define-package "macrostep" "0.9.2" "Interactive macro expander" '((cl-lib "0.5")) :commit "633586421e7fc14072cc1ca1655c1103b81a9093" :authors '(("Jon Oddie" . "j.j.oddie@gmail.com")) :maintainer '("Jon Oddie" . "j.j.oddie@gmail.com") :keywords '("lisp" "languages" "macro" "debugging") :url "https://github.com/emacsorphanage/macrostep") blob - 4a00a603f7a0cce838a1a6ce20d622d8854c90e4 (mode 644) blob + /dev/null --- elpa/macrostep-0.9.2/macrostep.el +++ /dev/null @@ -1,1125 +0,0 @@ -;;; macrostep.el --- Interactive macro expander -*- lexical-binding: t; -*- - -;; Copyright (C) 2012-2015 Jon Oddie -;; Copyright (C) 2020-2023 Free Software Foundation, Inc. - -;; Author: Jon Oddie -;; Url: https://github.com/emacsorphanage/macrostep -;; Keywords: lisp, languages, macro, debugging - -;; Package-Version: 0.9.2 -;; Package-Requires: ((cl-lib "0.5")) - -;; SPDX-License-Identifier: GPL-3.0-or-later - -;; This file 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 file 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 file. If not, see . - -;;; Commentary: - -;; `macrostep' is an Emacs minor mode for interactively stepping through -;; the expansion of macros in Emacs Lisp source code. It lets you see -;; exactly what happens at each step of the expansion process by -;; pretty-printing the expanded forms inline in the source buffer, which is -;; temporarily read-only while macro expansions are visible. You can -;; expand and collapse macro forms one step at a time, and evaluate or -;; instrument the expansions for debugging with Edebug as normal (but see -;; "Bugs and known limitations", below). Single-stepping through the -;; expansion is particularly useful for debugging macros that expand into -;; another macro form. These can be difficult to debug with Emacs' -;; built-in `macroexpand', which continues expansion until the top-level -;; form is no longer a macro call. - -;; Both globally-visible macros as defined by `defmacro' and local macros -;; bound by `(cl-)macrolet' or another macro-defining form can be expanded. -;; Within macro expansions, calls to macros and compiler macros are -;; fontified specially: macro forms using `macrostep-macro-face', and -;; functions with compiler macros using `macrostep-compiler-macro-face'. -;; Uninterned symbols (gensyms) are fontified based on which step in the -;; expansion created them, to distinguish them both from normal symbols and -;; from other gensyms with the same print name. - -;; As of version 0.9, it is also possible to extend `macrostep' to work -;; with other languages with macro systems in addition to Emacs Lisp. An -;; extension for Common Lisp (via SLIME) is in the works; contributions for -;; other languages are welcome. See "Extending macrostep" below for -;; details. - - -;; 1 Key-bindings and usage -;; ======================== - -;; The standard keybindings in `macrostep-mode' are the following: - -;; e, =, RET : expand the macro form following point one step -;; c, u, DEL : collapse the form following point -;; q, C-c C-c: collapse all expanded forms and exit macrostep-mode -;; n, TAB : jump to the next macro form in the expansion -;; p, M-TAB : jump to the previous macro form in the expansion - -;; It's not very useful to enable and disable macrostep-mode directly. -;; Instead, bind `macrostep-expand' to a key in `emacs-lisp-mode-map', -;; for example C-c e: - -;; ,---- -;; | (define-key emacs-lisp-mode-map (kbd "C-c e") 'macrostep-expand) -;; `---- - -;; You can then enter macrostep-mode and expand a macro form completely -;; by typing `C-c e e e ...' as many times as necessary. - -;; Exit macrostep-mode by typing `q' or `C-c C-c', or by successively -;; typing `c' to collapse all surrounding expansions. - - -;; 2 Customization options -;; ======================= - -;; Type `M-x customize-group RET macrostep RET' to customize options and -;; faces. - -;; To display macro expansions in a separate window, instead of inline in -;; the source buffer, customize `macrostep-expand-in-separate-buffer' to -;; `t'. The default is `nil'. Whichever default behavior is selected, -;; the alternative behavior can be obtained temporarily by giving a -;; prefix argument to `macrostep-expand'. - -;; To have `macrostep' ignore compiler macros, customize -;; `macrostep-expand-compiler-macros' to `nil'. The default is `t'. - -;; Customize the faces `macrostep-macro-face', -;; `macrostep-compiler-macro-face', and `macrostep-gensym-1' through -;; `macrostep-gensym-5' to alter the appearance of macro expansions. - - -;; 3 Locally-bound macros -;; ====================== - -;; As of version 0.9, `macrostep' can expand calls to a locally-bound -;; macro, whether defined by a surrounding `(cl-)macrolet' form, or by -;; another macro-defining macro. In other words, it is possible to -;; expand the inner `local-macro' forms in both the following examples, -;; whether `local-macro' is defined by an enclosing `cl-macrolet' -- - -;; ,---- -;; | (cl-macrolet ((local-macro (&rest args) -;; | `(expansion of ,args))) -;; | (local-macro (do-something))) -;; `---- - -;; -- or by a macro which expands into `cl-macrolet', provided that its -;; definition of macro is evaluated prior to calling `macrostep-expand': - -;; ,---- -;; | (defmacro with-local-macro (&rest body) -;; | `(cl-macrolet ((local-macro (&rest args) -;; | `(expansion of ,args))) -;; | ,@body)) -;; | -;; | (with-local-macro -;; | (local-macro (do something (else))) -;; `---- - -;; See the `with-js' macro in Emacs's `js.el' for a real example of the -;; latter kind of macro. - -;; Expansion of locally-bound macros is implemented by instrumenting -;; Emacs Lisp's macro-expander to capture the environment at point. A -;; similar trick is used to detect macro- and compiler-macro calls within -;; expanded text so that they can be fontified accurately. - - -;; 4 Expanding sub-forms -;; ===================== - -;; By moving point around in the macro expansion using -;; `macrostep-next-macro' and `macrostep-prev-macro' (bound to the `n' -;; and `p' keys), it is possible to expand other macro calls within the -;; expansion before expanding the outermost form. This can sometimes be -;; useful, although it does not correspond to the real order of macro -;; expansion in Emacs Lisp, which proceeds by fully expanding the outer -;; form to a non-macro form before expanding sub-forms. - -;; The main reason to expand sub-forms out of order is to help with -;; debugging macros which programmatically expand their arguments in -;; order to rewrite them. Expanding the arguments of such a macro lets -;; you visualise what the macro definition would compute via -;; `macroexpand-all'. - - -;; 5 Extending macrostep for other languages -;; ========================================= - -;; Since version 0.9, it is possible to extend macrostep to work with -;; other languages besides Emacs Lisp. In typical Emacs fashion, this is -;; implemented by setting buffer-local variables to different function -;; values. Six buffer-local variables define the language-specific part -;; of the implementation: - -;; - `macrostep-sexp-bounds-function' -;; - `macrostep-sexp-at-point-function' -;; - `macrostep-environment-at-point-function' -;; - `macrostep-expand-1-function' -;; - `macrostep-print-function' -;; - `macrostep-macro-form-p-function' - -;; Typically, an implementation for another language would set these -;; variables in a major-mode hook. See the docstrings of each variable -;; for details on how each one is called and what it should return. At a -;; minimum, another language implementation needs to provide -;; `macrostep-sexp-at-point-function', `macrostep-expand-1-function', and -;; `macrostep-print-function'. Lisp-like languages may be able to reuse -;; the default `macrostep-sexp-bounds-function' if they provide another -;; implementation of `macrostep-macro-form-p-function'. Languages which -;; do not implement locally-defined macros can set -;; `macrostep-environment-at-point-function' to `ignore'. - -;; Note that the core `macrostep' machinery only interprets the return -;; value of `macrostep-sexp-bounds-function', so implementations for -;; other languages can use any internal representations of code and -;; environments which is convenient. Although the terminology is -;; Lisp-specific, there is no reason that implementations could not be -;; provided for non-Lisp languages with macro systems, provided there is -;; some way of identifying macro calls and calling the compiler / -;; preprocessor to obtain their expansions. - - -;; 6 Bugs and known limitations -;; ============================ - -;; You can evaluate and edebug macro-expanded forms and step through the -;; macro-expanded version, but the form that `eval-defun' and friends -;; read from the buffer won't have the uninterned symbols of the real -;; macro expansion. This will probably work OK with CL-style gensyms, -;; but may cause problems with `make-symbol' symbols if they have the -;; same print name as another symbol in the expansion. It's possible that -;; using `print-circle' and `print-gensym' could get around this. - -;; Please send other bug reports and feature requests to the author. - - -;; 7 Acknowledgements -;; ================== - -;; Thanks to: -;; - John Wiegley for fixing a bug with the face definitions under Emacs -;; 24 & for plugging macrostep in his [EmacsConf presentation]! -;; - George Kettleborough for bug reports, and patches to highlight the -;; expanded region and properly handle backquotes. -;; - Nic Ferrier for suggesting support for local definitions within -;; macrolet forms -;; - Luís Oliveira for suggesting and implementing SLIME support - -;; `macrostep' was originally inspired by J. V. Toups's 'Deep Emacs Lisp' -;; articles ([part 1], [part 2], [screencast]). - -;; [EmacsConf presentation] http://youtu.be/RvPFZL6NJNQ - -;; [part 1] -;; http://dorophone.blogspot.co.uk/2011/04/deep-emacs-part-1.html - -;; [part 2] -;; http://dorophone.blogspot.co.uk/2011/04/deep-emacs-lisp-part-2.html - -;; [screencast] -;; http://dorophone.blogspot.co.uk/2011/05/monadic-parser-combinators-in-elisp.html - - -;; 8 Changelog -;; =========== - -;; - v0.9.2, 2023-05-12: -;; - name the keymap macrostep-mode-map, fixing a regression in v0.9.1 -;; - v0.9.1, 2023-03-12: -;; - bug fixes, cleanup and modernization -;; - v0.9, 2015-10-01: -;; - separate into Elisp-specific and generic components -;; - highlight and expand compiler macros -;; - improve local macro expansion and macro form identification by -;; instrumenting `macroexpand(-all)' -;; - v0.8, 2014-05-29: fix a bug with printing the first element of lists -;; - v0.7, 2014-05-11: expand locally-defined macros within -;; `(cl-)macrolet' forms -;; - v0.6, 2013-05-04: better handling of quote and backquote -;; - v0.5, 2013-04-16: highlight region, maintain cleaner buffer state -;; - v0.4, 2013-04-07: only enter macrostep-mode on successful -;; macro-expansion -;; - v0.3, 2012-10-30: print dotted lists correctly. autoload -;; definitions. - -;;; Code: - -(require 'pp) -(require 'ring) -(require 'cl-lib) - - -;;; Constants and dynamically bound variables -(defvar macrostep-overlays nil - "List of all macro stepper overlays in the current buffer.") -(make-variable-buffer-local 'macrostep-overlays) - -(defvar macrostep-gensym-depth nil - "Number of macro expansion levels that have introduced gensyms so far.") -(make-variable-buffer-local 'macrostep-gensym-depth) - -(defvar macrostep-gensyms-this-level nil - "t if gensyms have been encountered during current level of macro expansion.") -(make-variable-buffer-local 'macrostep-gensyms-this-level) - -(defvar macrostep-saved-undo-list nil - "Saved value of `buffer-undo-list' upon entering macrostep mode.") -(make-variable-buffer-local 'macrostep-saved-undo-list) - -(defvar macrostep-saved-read-only nil - "Saved value of `buffer-read-only' upon entering macrostep mode.") -(make-variable-buffer-local 'macrostep-saved-read-only) - -(defvar macrostep-expansion-buffer nil - "Non-nil if the current buffer is a macro-expansion buffer.") -(make-variable-buffer-local 'macrostep-expansion-buffer) - -(defvar macrostep-outer-environment nil - "Outermost macro-expansion environment to use in macro-expansion buffers. - -This variable is used to save information about any enclosing -`cl-macrolet' context when a macro form is expanded in a separate -buffer.") -(make-variable-buffer-local 'macrostep-outer-environment) - -;;; Customization options and faces -(defgroup macrostep nil - "Interactive macro stepper for Emacs Lisp." - :group 'lisp - :link '(emacs-commentary-link :tag "commentary" "macrostep.el") - :link '(emacs-library-link :tag "lisp file" "macrostep.el") - :link '(url-link :tag "web page" "https://github.com/joddie/macrostep")) - -(defface macrostep-gensym-1 - '((((min-colors 16581375)) :foreground "#8080c0" :box t :bold t) - (((min-colors 8)) :background "cyan") - (t :inverse-video t)) - "Face for gensyms created in the first level of macro expansion.") - -(defface macrostep-gensym-2 - '((((min-colors 16581375)) :foreground "#8fbc8f" :box t :bold t) - (((min-colors 8)) :background "#00cd00") - (t :inverse-video t)) - "Face for gensyms created in the second level of macro expansion.") - -(defface macrostep-gensym-3 - '((((min-colors 16581375)) :foreground "#daa520" :box t :bold t) - (((min-colors 8)) :background "yellow") - (t :inverse-video t)) - "Face for gensyms created in the third level of macro expansion.") - -(defface macrostep-gensym-4 - '((((min-colors 16581375)) :foreground "#cd5c5c" :box t :bold t) - (((min-colors 8)) :background "red") - (t :inverse-video t)) - "Face for gensyms created in the fourth level of macro expansion.") - -(defface macrostep-gensym-5 - '((((min-colors 16581375)) :foreground "#da70d6" :box t :bold t) - (((min-colors 8)) :background "magenta") - (t :inverse-video t)) - "Face for gensyms created in the fifth level of macro expansion.") - -(defface macrostep-expansion-highlight-face - `((((min-colors 16581375) (background light)) - ,@(and (>= emacs-major-version 27) '(:extend t)) - :background "#eee8d5") - (((min-colors 16581375) (background dark)) - ,@(and (>= emacs-major-version 27) '(:extend t)) - :background "#222222")) - "Face for macro-expansion highlight.") - -(defface macrostep-macro-face - '((t :underline t)) - "Face for macros in macro-expanded code.") - -(defface macrostep-compiler-macro-face - '((t :slant italic)) - "Face for compiler macros in macro-expanded code.") - -(defcustom macrostep-expand-in-separate-buffer nil - "When non-nil, show expansions in a separate buffer instead of inline." - :type 'boolean) - -(defcustom macrostep-expand-compiler-macros t - "When non-nil, also expand compiler macros." - :type 'boolean) - -;; Need the following for making the ring of faces -(defun macrostep-make-ring (&rest items) - "Make a ring containing all of ITEMS with no empty slots." - (let ((ring (make-ring (length items)))) - (mapc (lambda (item) (ring-insert ring item)) (reverse items)) - ring)) - -(defvar macrostep-gensym-faces - (macrostep-make-ring - 'macrostep-gensym-1 'macrostep-gensym-2 'macrostep-gensym-3 - 'macrostep-gensym-4 'macrostep-gensym-5) - "Ring of all macrostepper faces for fontifying gensyms.") - -;; Other modes can enable macrostep by redefining these functions to -;; language-specific versions. -(defvar macrostep-sexp-bounds-function - #'macrostep-sexp-bounds - "Function to return the bounds of the macro form nearest point. - -It will be called with no arguments and should return a cons of -buffer positions, (START . END). It should use `save-excursion' -to avoid changing the position of point. - -The default value, `macrostep-sexp-bounds', implements this for -Emacs Lisp, and may be suitable for other Lisp-like languages.") -(make-variable-buffer-local 'macrostep-sexp-bounds-function) - -(defvar macrostep-sexp-at-point-function - #'macrostep-sexp-at-point - "Function to return the macro form at point for expansion. - -It will be called with two arguments, the values of START and END -returned by `macrostep-sexp-bounds-function', and with point -positioned at START. It should return a value suitable for -passing as the first argument to `macrostep-expand-1-function'. - -The default value, `macrostep-sexp-at-point', implements this for -Emacs Lisp, and may be suitable for other Lisp-like languages.") -(make-variable-buffer-local 'macrostep-sexp-at-point-function) - -(defvar macrostep-environment-at-point-function - #'macrostep-environment-at-point - "Function to return the local macro-expansion environment at point. - -It will be called with no arguments, and should return a value -suitable for passing as the second argument to -`macrostep-expand-1-function'. - -The default value, `macrostep-environment-at-point', is specific -to Emacs Lisp. For languages which do not implement local -macro-expansion environments, this should be set to `ignore' -or `(lambda () nil)'.") -(make-variable-buffer-local 'macrostep-environment-at-point-function) - -(defvar macrostep-expand-1-function - #'macrostep-expand-1 - "Function to perform one step of macro-expansion. - -It will be called with two arguments, FORM and ENVIRONMENT, the -return values of `macrostep-sexp-at-point-function' and -`macrostep-environment-at-point-function' respectively. It -should return the result of expanding FORM by one step as a value -which is suitable for passing as the argument to -`macrostep-print-function'. - -The default value, `macrostep-expand-1', is specific to Emacs Lisp.") -(make-variable-buffer-local 'macrostep-expand-1-function) - -(defvar macrostep-print-function - #'macrostep-pp - "Function to pretty-print macro expansions. - -It will be called with two arguments, FORM and ENVIRONMENT, the -return values of `macrostep-sexp-at-point-function' and -`macrostep-environment-at-point-function' respectively. It -should insert a pretty-printed representation at point in the -current buffer, leaving point just after the inserted -representation, without altering any other text in the current -buffer. - -The default value, `macrostep-pp', is specific to Emacs Lisp.") -(make-variable-buffer-local 'macrostep-print-function) - -(defvar macrostep-macro-form-p-function - #'macrostep-macro-form-p - "Function to check whether a form is a macro call. - -It will be called with two arguments, FORM and ENVIRONMENT -- the -return values of `macrostep-sexp-at-point-function' and -`macrostep-environment-at-point-function' respectively -- and -should return non-nil if FORM would undergo macro-expansion in -ENVIRONMENT. - -This is called only from `macrostep-sexp-bounds', so it need not -be provided if a different value is used for -`macrostep-sexp-bounds-function'. - -The default value, `macrostep-macro-form-p', is specific to Emacs Lisp.") -(make-variable-buffer-local 'macrostep-macro-form-p-function) - - -;;; Define keymap and minor mode -(define-obsolete-variable-alias 'macrostep-mode-keymap 'macrostep-mode-map "2023") -(define-obsolete-variable-alias 'macrostep-keymap 'macrostep-mode-map "2022") -(defvar macrostep-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "RET") #'macrostep-expand) - (define-key map "=" #'macrostep-expand) - (define-key map "e" #'macrostep-expand) - - (define-key map (kbd "DEL") #'macrostep-collapse) - (define-key map "u" #'macrostep-collapse) - (define-key map "c" #'macrostep-collapse) - - (define-key map (kbd "TAB") #'macrostep-next-macro) - (define-key map "n" #'macrostep-next-macro) - (define-key map (kbd "M-TAB") #'macrostep-prev-macro) - (define-key map "p" #'macrostep-prev-macro) - - (define-key map "q" #'macrostep-collapse-all) - (define-key map (kbd "C-c C-c") #'macrostep-collapse-all) - map) - "Keymap for `macrostep-mode'.") - -;;;###autoload -(define-minor-mode macrostep-mode - "Minor mode for inline expansion of macros in Emacs Lisp source buffers. - -\\Progressively expand macro forms with \ -\\[macrostep-expand], collapse them with \\[macrostep-collapse], -and move back and forth with \\[macrostep-next-macro] and \ -\\[macrostep-prev-macro]. Use \\[macrostep-collapse-all] or collapse all -visible expansions to quit and return to normal editing. - -\\{macrostep-mode-map}" - :lighter " Macro-Stepper" - :group 'macrostep - (if macrostep-mode - (progn - ;; Disable recording of undo information - (setq macrostep-saved-undo-list buffer-undo-list - buffer-undo-list t) - ;; Remember whether buffer was read-only - (setq macrostep-saved-read-only buffer-read-only - buffer-read-only t) - ;; Set up post-command hook to bail out on leaving read-only - (add-hook 'post-command-hook #'macrostep-command-hook nil t) - (message (substitute-command-keys "\ -\\Entering macro stepper mode. \ -Use \\[macrostep-expand] to expand, \\[macrostep-collapse] to collapse, \ -\\[macrostep-collapse-all] to exit."))) - - ;; Exiting mode - (if macrostep-expansion-buffer - ;; Kill dedicated expansion buffers - (quit-window t) - ;; Collapse any remaining overlays - (when macrostep-overlays (macrostep-collapse-all)) - ;; Restore undo info & read-only state - (setq buffer-undo-list macrostep-saved-undo-list - buffer-read-only macrostep-saved-read-only - macrostep-saved-undo-list nil) - ;; Remove our post-command hook - (remove-hook 'post-command-hook #'macrostep-command-hook t)))) - -;; Post-command hook: bail out of macrostep-mode if the user types C-x -;; C-q to make the buffer writable again. -(defun macrostep-command-hook () - (if (not buffer-read-only) - (macrostep-mode 0))) - - -;;; Interactive functions -;;;###autoload -(defun macrostep-expand (&optional toggle-separate-buffer) - "Expand the macro form following point by one step. - -Enters `macrostep-mode' if it is not already active, making the -buffer temporarily read-only. If `macrostep-mode' is active and the -form following point is not a macro form, search forward in the -buffer and expand the next macro form found, if any. - -With a prefix argument, the expansion is displayed in a separate -buffer instead of inline in the current buffer. Setting -`macrostep-expand-in-separate-buffer' to non-nil swaps these two -behaviors." - (interactive "P") - (cl-destructuring-bind (start . end) - (funcall macrostep-sexp-bounds-function) - (goto-char start) - (let* ((sexp (funcall macrostep-sexp-at-point-function start end)) - (end (copy-marker end)) - (text (buffer-substring start end)) - (env (funcall macrostep-environment-at-point-function)) - (expansion (funcall macrostep-expand-1-function sexp env))) - - ;; Create a dedicated macro-expansion buffer and copy the text to - ;; be expanded into it, if required - (let ((separate-buffer-p - (if toggle-separate-buffer - (not macrostep-expand-in-separate-buffer) - macrostep-expand-in-separate-buffer))) - (when (and separate-buffer-p (not macrostep-expansion-buffer)) - (let ((mode major-mode) - (buffer - (get-buffer-create (generate-new-buffer-name "*macro expansion*")))) - (set-buffer buffer) - (funcall mode) - (setq macrostep-expansion-buffer t) - (setq macrostep-outer-environment env) - (save-excursion - (setq start (point)) - (insert text) - (setq end (point-marker))) - (pop-to-buffer buffer)))) - - (unless macrostep-mode (macrostep-mode t)) - (let ((existing-overlay (macrostep-overlay-at-point)) - (macrostep-gensym-depth macrostep-gensym-depth) - (macrostep-gensyms-this-level nil) - priority) - (if existing-overlay - (progn ; Expanding part of a previous macro-expansion - (setq priority (1+ (overlay-get existing-overlay 'priority))) - (setq macrostep-gensym-depth - (overlay-get existing-overlay 'macrostep-gensym-depth))) - ;; Expanding source buffer text - (setq priority 1) - (setq macrostep-gensym-depth -1)) - - (with-silent-modifications - (atomic-change-group - (let ((inhibit-read-only t)) - (save-excursion - ;; Insert expansion - (funcall macrostep-print-function expansion env) - ;; Delete the original form - (macrostep-collapse-overlays-in (point) end) - (delete-region (point) end) - ;; Create a new overlay - (let* ((overlay - (make-overlay start - (if (looking-at "\n") - (1+ (point)) - (point)))) - (highlight-overlay (unless macrostep-expansion-buffer - (copy-overlay overlay)))) - (unless macrostep-expansion-buffer - ;; Highlight the overlay in original source buffers only - (overlay-put highlight-overlay 'face 'macrostep-expansion-highlight-face) - (overlay-put highlight-overlay 'priority -1) - (overlay-put overlay 'macrostep-highlight-overlay highlight-overlay)) - (overlay-put overlay 'priority priority) - (overlay-put overlay 'macrostep-original-text text) - (overlay-put overlay 'macrostep-gensym-depth macrostep-gensym-depth) - (push overlay macrostep-overlays)))))))))) - -(defun macrostep-collapse () - "Collapse the innermost macro expansion near point to its source text. - -If no more macro expansions are visible after this, exit -`macrostep-mode'." - (interactive) - (let ((overlay (macrostep-overlay-at-point))) - (when (not overlay) (error "No macro expansion at point")) - (let ((inhibit-read-only t)) - (with-silent-modifications - (atomic-change-group - (macrostep-collapse-overlay overlay))))) - (if (not macrostep-overlays) - (macrostep-mode 0))) - -(defun macrostep-collapse-all () - "Collapse all visible macro expansions and exit `macrostep-mode'." - (interactive) - (let ((inhibit-read-only t)) - (with-silent-modifications - (dolist (overlay macrostep-overlays) - (let ((outermost (= (overlay-get overlay 'priority) 1))) - ;; We only need restore the original text for the outermost - ;; overlays - (macrostep-collapse-overlay overlay (not outermost)))))) - (setq macrostep-overlays nil) - (macrostep-mode 0)) - -(defun macrostep-next-macro () - "Move point forward to the next macro form in macro-expanded text." - (interactive) - (let* ((start (if (get-text-property (point) 'macrostep-macro-start) - (1+ (point)) - (point))) - (next (next-single-property-change start 'macrostep-macro-start))) - (if next - (goto-char next) - (error "No more macro forms found")))) - -(defun macrostep-prev-macro () - "Move point back to the previous macro form in macro-expanded text." - (interactive) - (let (prev) - (save-excursion - (while - (progn - (setq prev (previous-single-property-change - (point) 'macrostep-macro-start)) - (if (or (not prev) - (get-text-property (1- prev) 'macrostep-macro-start)) - nil - (prog1 t (goto-char prev)))))) - (if prev - (goto-char (1- prev)) - (error "No previous macro form found")))) - - -;;; Utility functions (not language-specific) - -(defun macrostep-overlay-at-point () - "Return the innermost macro stepper overlay at point." - (cdr (get-char-property-and-overlay (point) 'macrostep-original-text))) - -(defun macrostep-collapse-overlay (overlay &optional no-restore-p) - "Collapse a macro-expansion overlay and restore the unexpanded source text. - -As a minor optimization, does not restore the original source -text if NO-RESTORE-P is non-nil. This is safe to do when -collapsing all the sub-expansions of an outer overlay, since the -outer overlay will restore the original source itself. - -Also removes the overlay from `macrostep-overlays'." - (with-current-buffer (overlay-buffer overlay) - ;; If we're cleaning up we don't need to bother restoring text - ;; or checking for inner overlays to delete - (unless no-restore-p - (let* ((start (overlay-start overlay)) - (end (overlay-end overlay)) - (text (overlay-get overlay 'macrostep-original-text)) - (sexp-end - (copy-marker - (if (equal (char-before end) ?\n) (1- end) end)))) - (macrostep-collapse-overlays-in start end) - (goto-char (overlay-start overlay)) - (save-excursion - (insert text) - (delete-region (point) sexp-end)))) - ;; Remove overlay from the list and delete it - (setq macrostep-overlays - (delq overlay macrostep-overlays)) - (let ((highlight-overlay (overlay-get overlay 'macrostep-highlight-overlay))) - (when highlight-overlay (delete-overlay highlight-overlay))) - (delete-overlay overlay))) - -(defun macrostep-collapse-overlays-in (start end) - "Collapse all macrostepper overlays that are strictly between START and END. - -Will not collapse overlays that begin at START and end at END." - (dolist (ol (overlays-in start end)) - (when (and (overlay-buffer ol) ; collapsing may delete other overlays - (> (overlay-start ol) start) - (< (overlay-end ol) end) - (overlay-get ol 'macrostep-original-text)) - (macrostep-collapse-overlay ol t)))) - - -;;; Emacs Lisp implementation - -(defun macrostep-sexp-bounds () - "Find the bounds of the macro form nearest point. - -If point is not before an open-paren, moves up to the nearest -enclosing list. If the form at point is not a macro call, -attempts to move forward to the next macro form as determined by -`macrostep-macro-form-p-function'. - -Returns a cons of buffer positions, (START . END)." - (save-excursion - (if (not (looking-at "[(`]")) - (backward-up-list 1)) - (if (equal (char-before) ?`) - (backward-char)) - (let ((sexp (funcall macrostep-sexp-at-point-function)) - (env (funcall macrostep-environment-at-point-function))) - ;; If this isn't a macro form, try to find the next one in the buffer - (unless (funcall macrostep-macro-form-p-function sexp env) - (condition-case nil - (macrostep-next-macro) - (error - (if (consp sexp) - (error "(%s ...) is not a macro form" (car sexp)) - (error "Text at point is not a macro form")))))) - (cons (point) (scan-sexps (point) 1)))) - -(defun macrostep-sexp-at-point (&rest _ignore) - "Return the sexp near point for purposes of macro-stepper expansion. - -If the sexp near point is part of a macro expansion, returns the -saved text of the macro expansion, and does not read from the -buffer. This preserves uninterned symbols in the macro -expansion, so that they can be fontified consistently. (See -`macrostep-print-sexp'.)" - (or (get-text-property (point) 'macrostep-expanded-text) - (sexp-at-point))) - -(defun macrostep-macro-form-p (form environment) - "Return non-nil if FORM would be evaluated via macro expansion. - -If FORM is an invocation of a macro defined by `defmacro' or an -enclosing `cl-macrolet' form, return the symbol `macro'. - -If `macrostep-expand-compiler-macros' is non-nil and FORM is a -call to a function with a compiler macro, return the symbol -`compiler-macro'. - -Otherwise, return nil." - (car (macrostep--macro-form-info form environment t))) - -(defun macrostep--macro-form-info (form environment &optional inhibit-autoload) - "Return information about macro definitions that apply to FORM. - -If no macros are involved in the evaluation of FORM within -ENVIRONMENT, returns nil. Otherwise, returns a cons (TYPE -. DEFINITION). - -If FORM would be evaluated by a macro defined by `defmacro', -`cl-macrolet', etc., TYPE is the symbol `macro' and DEFINITION is -the macro definition, as a function. - -If `macrostep-expand-compiler-macros' is non-nil and FORM would -be compiled using a compiler macro, TYPE is the symbol -`compiler-macro' and DEFINITION is the function that implements -the compiler macro. - -If FORM is an invocation of an autoloaded macro, the behavior -depends on the value of INHIBIT-AUTOLOAD. If INHIBIT-AUTOLOAD is -nil, the file containing the macro definition will be loaded -using `load-library' and the macro definition returned as normal. -If INHIBIT-AUTOLOAD is non-nil, no files will be loaded, and the -value of DEFINITION in the result will be nil." - (if (not (and (consp form) - (symbolp (car form)))) - `(nil . nil) - (let* ((head (car form)) - (local-definition (assoc-default head environment #'eq))) - (if local-definition - `(macro . ,local-definition) - (let ((compiler-macro-definition - (and macrostep-expand-compiler-macros - (or (get head 'compiler-macro) - (get head 'cl-compiler-macro))))) - (if (and compiler-macro-definition - (not (eq form - (apply compiler-macro-definition form (cdr form))))) - `(compiler-macro . ,compiler-macro-definition) - (condition-case nil - (let ((fun (indirect-function head))) - (cl-case (car-safe fun) - ((macro) - `(macro . ,(cdr fun))) - ((autoload) - (when (memq (nth 4 fun) '(macro t)) - (if inhibit-autoload - `(macro . nil) - (load-library (nth 1 fun)) - (macrostep--macro-form-info form nil)))) - (t - `(nil . nil)))) - (void-function nil)))))))) - -(defun macrostep-expand-1 (form environment) - "Return result of macro-expanding the top level of FORM by exactly one step. -Unlike `macroexpand', this function does not continue macro -expansion until a non-macro-call results." - (cl-destructuring-bind (type . definition) - (macrostep--macro-form-info form environment) - (cl-ecase type - ((nil) - form) - ((macro) - (apply definition (cdr form))) - ((compiler-macro) - (let ((expansion (apply definition form (cdr form)))) - (if (equal form expansion) - (error "Form left unchanged by compiler macro") - expansion)))))) - -(put 'macrostep-grab-environment-failed 'error-conditions - '(macrostep-grab-environment-failed error)) - -(defun macrostep-environment-at-point () - "Return the local macro-expansion environment at point, if any. - -The local environment includes macros declared by any `macrolet' -or `cl-macrolet' forms surrounding point, as well as by any macro -forms which expand into a `macrolet'. - -The return value is an alist of elements (NAME . FUNCTION), where -NAME is the symbol locally bound to the macro and FUNCTION is the -lambda expression that returns its expansion." - ;; If point is on a macro form within an expansion inserted by - ;; `macrostep-print-sexp', a local environment may have been - ;; previously saved as a text property. - (let ((saved-environment - (get-text-property (point) 'macrostep-environment))) - (if saved-environment - saved-environment - ;; Otherwise, we (ab)use the macro-expander to return the - ;; environment at point. If point is not at an evaluated - ;; position in the containing form, - ;; `macrostep-environment-at-point-1' will raise an error, and - ;; we back up progressively through the containing forms until - ;; it succeeds. - (save-excursion - (catch 'done - (while t - (condition-case nil - (throw 'done (macrostep-environment-at-point-1)) - (macrostep-grab-environment-failed - (condition-case nil - (backward-sexp) - (scan-error (backward-up-list))))))))))) - -(defun macrostep-environment-at-point-1 () - "Attempt to extract the macro environment that would be active at point. - -If point is not at an evaluated position within the containing -form, raise an error." - ;; Macro environments are extracted using Emacs Lisp's builtin - ;; macro-expansion machinery. The form containing point is copied - ;; to a temporary buffer, and a call to - ;; `--macrostep-grab-environment--' is inserted at point. This - ;; altered form is then fully macro-expanded, in an environment - ;; where `--macrostep-grab-environment--' is defined as a macro - ;; which throws the environment to a uniquely-generated tag. - (let* ((point-at-top-level - (save-excursion - (while (ignore-errors (backward-up-list) t)) - (point))) - (enclosing-form - (buffer-substring point-at-top-level - (scan-sexps point-at-top-level 1))) - (position (- (point) point-at-top-level)) - (tag (make-symbol "macrostep-grab-environment-tag")) - (grab-environment '--macrostep-grab-environment--)) - (if (= position 0) - nil - (with-temp-buffer - (emacs-lisp-mode) - (insert enclosing-form) - (goto-char (+ (point-min) position)) - (prin1 `(,grab-environment) (current-buffer)) - (let ((form (read (copy-marker (point-min))))) - (catch tag - (cl-letf (((symbol-function #'message) (symbol-function #'format))) - (with-no-warnings - (ignore-errors - (macroexpand-all - `(cl-macrolet ((,grab-environment (&environment env) - (throw ',tag env))) - ,form))))) - (signal 'macrostep-grab-environment-failed nil))))))) - -(defun macrostep-collect-macro-forms (form &optional environment) - "Identify sub-forms of FORM which undergo macro-expansion. - -FORM is an Emacs Lisp form. ENVIRONMENT is a local environment of -macro definitions. - -The return value is a list of two elements, (MACRO-FORM-ALIST -COMPILER-MACRO-FORMS). - -MACRO-FORM-ALIST is an alist of elements of the form (SUBFORM -. ENVIRONMENT), where SUBFORM is a form which undergoes -macro-expansion in the course of expanding FORM, and ENVIRONMENT -is the local macro environment in force when it is expanded. - -COMPILER-MACRO-FORMS is a list of subforms which would be -compiled using a compiler macro. Since there is no standard way -to provide a local compiler-macro definition in Emacs Lisp, no -corresponding local environments are collected for these. - -Forms and environments are extracted from FORM by instrumenting -Emacs's builtin `macroexpand' function and calling -`macroexpand-all'." - (let ((real-macroexpand (indirect-function #'macroexpand)) - (macro-form-alist '()) - (compiler-macro-forms '())) - (cl-letf - (((symbol-function #'macroexpand) - (lambda (form environment &rest args) - (let ((expansion - (apply real-macroexpand form environment args))) - (cond ((not (eq expansion form)) - (setq macro-form-alist - (cons (cons form environment) - macro-form-alist))) - ((and (consp form) - (symbolp (car form)) - macrostep-expand-compiler-macros - (not (eq form - (cl-compiler-macroexpand form)))) - (setq compiler-macro-forms - (cons form compiler-macro-forms)))) - expansion)))) - (ignore-errors - (macroexpand-all form environment))) - (list macro-form-alist compiler-macro-forms))) - -(defvar macrostep-collected-macro-form-alist nil - "An alist of macro forms and environments. -Controls the printing of sub-forms in `macrostep-print-sexp'.") - -(defvar macrostep-collected-compiler-macro-forms nil - "A list of compiler-macro forms to be highlighted in `macrostep-print-sexp'.") - -(defun macrostep-pp (sexp environment) - "Pretty-print SEXP, fontifying macro forms and uninterned symbols." - (cl-destructuring-bind - (macrostep-collected-macro-form-alist - macrostep-collected-compiler-macro-forms) - (macrostep-collect-macro-forms sexp environment) - (let ((print-quoted t)) - (macrostep-print-sexp sexp) - ;; Point is now after the expanded form; pretty-print it - (save-restriction - (narrow-to-region (scan-sexps (point) -1) (point)) - (save-excursion - (pp-buffer) - ;; Remove the extra newline inserted by pp-buffer - (goto-char (point-max)) - (delete-region - (point) - (save-excursion (skip-chars-backward " \t\n") (point)))) - ;; Indent the newly-inserted form in context - (widen) - (save-excursion - (backward-sexp) - (indent-sexp)))))) - -;; This must be defined before `macrostep-print-sexp': -(defmacro macrostep-propertize (form &rest plist) - "Evaluate FORM, applying syntax properties in PLIST to any inserted text." - (declare (indent 1) - (debug (&rest form))) - (let ((start (make-symbol "start"))) - `(let ((,start (point))) - (prog1 - ,form - ,@(cl-loop for (key value) on plist by #'cddr - collect `(put-text-property ,start (point) - ,key ,value)))))) - -(defun macrostep-print-sexp (sexp) - "Insert SEXP like `print', fontifying macro forms and uninterned symbols. - -Fontifies uninterned symbols and macro forms using -`font-lock-face' property, and saves the actual text of SEXP's -sub-forms as the `macrostep-expanded-text' text property so that -any uninterned symbols can be reused in macro expansions of the -sub-forms. See also `macrostep-sexp-at-point'. - -Macro and compiler-macro forms within SEXP are identified by -comparison with the `macrostep-collected-macro-form-alist' and -`macrostep-collected-compiler-macro-forms' variables, which -should be dynamically let-bound around calls to this function." - (cond - ((symbolp sexp) - ;; Fontify gensyms - (if (not (eq sexp (intern-soft (symbol-name sexp)))) - (macrostep-propertize - (prin1 sexp (current-buffer)) - 'font-lock-face (macrostep-get-gensym-face sexp)) - ;; Print other symbols as normal - (prin1 sexp (current-buffer)))) - - ((listp sexp) - ;; Print quoted and quasiquoted forms nicely. - (let ((head (car sexp))) - (cond ((and (eq head 'quote) ; quote - (= (length sexp) 2)) - (insert "'") - (macrostep-print-sexp (cadr sexp))) - - ((and (eq head '\`) ; backquote - (= (length sexp) 2)) - (if (assq sexp macrostep-collected-macro-form-alist) - (macrostep-propertize - (insert "`") - 'macrostep-expanded-text sexp - 'macrostep-macro-start t - 'font-lock-face 'macrostep-macro-face) - (insert "`")) - (macrostep-print-sexp (cadr sexp))) - - ((and (memq head '(\, \,@)) ; unquote - (= (length sexp) 2)) - (princ head (current-buffer)) - (macrostep-print-sexp (cadr sexp))) - - (t ; other list form - (cl-destructuring-bind (macro? . environment) - (or (assq sexp macrostep-collected-macro-form-alist) - '(nil . nil)) - (let - ((compiler-macro? - (memq sexp macrostep-collected-compiler-macro-forms))) - (if (or macro? compiler-macro?) - (progn - ;; Save the real expansion as a text property on the - ;; opening paren - (macrostep-propertize - (insert "(") - 'macrostep-macro-start t - 'macrostep-expanded-text sexp - 'macrostep-environment environment) - ;; Fontify the head of the macro - (macrostep-propertize - (macrostep-print-sexp head) - 'font-lock-face - (if macro? - 'macrostep-macro-face - 'macrostep-compiler-macro-face))) - ;; Not a macro form - (insert "(") - (macrostep-print-sexp head)))) - - ;; Print remaining list elements - (setq sexp (cdr sexp)) - (when sexp (insert " ")) - (while sexp - (if (listp sexp) - (progn - (macrostep-print-sexp (car sexp)) - (when (cdr sexp) (insert " ")) - (setq sexp (cdr sexp))) - ;; Print tail of dotted list - (insert ". ") - (macrostep-print-sexp sexp) - (setq sexp nil))) - (insert ")"))))) - - ;; Print everything except symbols and lists as normal - (t (prin1 sexp (current-buffer))))) - -(defun macrostep-get-gensym-face (symbol) - "Return the face to use in fontifying SYMBOL in printed macro expansions. - -All symbols introduced in the same level of macro expansion are -fontified using the same face (modulo the number of faces; see -`macrostep-gensym-faces')." - (or (get symbol 'macrostep-gensym-face) - (progn - (if (not macrostep-gensyms-this-level) - (setq macrostep-gensym-depth (1+ macrostep-gensym-depth) - macrostep-gensyms-this-level t)) - (let ((face (ring-ref macrostep-gensym-faces macrostep-gensym-depth))) - (put symbol 'macrostep-gensym-face face) - face)))) - - -(provide 'macrostep) -;; Local Variables: -;; indent-tabs-mode: nil -;; End: -;;; macrostep.el ends here blob - /dev/null blob + 34a1ee5cd90f0b187d47a151b89288eef588cbca (mode 644) --- /dev/null +++ elpa/macrostep-0.9.4/.elpaignore @@ -0,0 +1,2 @@ +.travis.yml +LICENSE blob - /dev/null blob + f8e6a17c8c62a7cb48c0c86628730178ef879ff3 (mode 644) --- /dev/null +++ elpa/macrostep-0.9.4/Makefile @@ -0,0 +1,17 @@ +EMACS ?= emacs + +all: macrostep.elc macrostep-c.elc + +clean: + rm -f *.elc + +test: clean all + $(EMACS) --batch -L . --load macrostep-test.el + +sandbox: all + $(EMACS) -Q -L . --load macrostep --load macrostep-c + +%.elc: %.el + $(EMACS) --batch -L . --funcall batch-byte-compile "$<" + +.PHONY: test all clean blob - /dev/null blob + b0608735e2326aacee547dfe00d631076fdbe2cd (mode 644) --- /dev/null +++ elpa/macrostep-0.9.4/README-elpa @@ -0,0 +1,235 @@ +1 macrostep: interactive macro-expander +═══════════════════════════════════════ + + `macrostep' is an Emacs minor mode for interactively stepping through + the expansion of macros in Emacs Lisp source code. It lets you see + exactly what happens at each step of the expansion process by + pretty-printing the expanded forms inline in the source buffer, which + is temporarily read-only while macro expansions are visible. You can + expand and collapse macro forms one step at a time, and evaluate or + instrument the expansions for debugging with Edebug as normal (but see + "Bugs and known limitations", below). Single-stepping through the + expansion is particularly useful for debugging macros that expand into + another macro form. These can be difficult to debug with Emacs' + built-in `macroexpand', which continues expansion until the top-level + form is no longer a macro call. + + Both globally-visible macros as defined by `defmacro' and local macros + bound by `(cl-)macrolet' or another macro-defining form can be + expanded. Within macro expansions, calls to macros and compiler + macros are fontified specially: macro forms using + `macrostep-macro-face', and functions with compiler macros using + `macrostep-compiler-macro-face'. Uninterned symbols (gensyms) are + fontified based on which step in the expansion created them, to + distinguish them both from normal symbols and from other gensyms with + the same print name. + + As of version 0.9, it is also possible to extend `macrostep' to work + with other languages with macro systems in addition to Emacs Lisp. An + extension for Common Lisp (via SLIME) is in the works; contributions + for other languages are welcome. See "Extending macrostep" below for + details. + + +1.1 Key-bindings and usage +────────────────────────── + + The standard keybindings in `macrostep-mode' are the following: + + e, =, RET + expand the macro form following point one step + c, u, DEL + collapse the form following point + q, C-c C-c + collapse all expanded forms and exit macrostep-mode + n, TAB + jump to the next macro form in the expansion + p, M-TAB + jump to the previous macro form in the expansion + + It's not very useful to enable and disable macrostep-mode directly. + Instead, bind `macrostep-expand' to a key in `emacs-lisp-mode-map', + for example C-c e: + + ┌──── + │ (define-key emacs-lisp-mode-map (kbd "C-c e") 'macrostep-expand) + └──── + + You can then enter macrostep-mode and expand a macro form completely + by typing `C-c e e e ...' as many times as necessary. + + Exit macrostep-mode by typing `q' or `C-c C-c', or by successively + typing `c' to collapse all surrounding expansions. + + +1.2 Customization options +───────────────────────── + + Type `M-x customize-group RET macrostep RET' to customize options and + faces. + + To display macro expansions in a separate window, instead of inline in + the source buffer, customize `macrostep-expand-in-separate-buffer' to + `t'. The default is `nil'. Whichever default behavior is selected, + the alternative behavior can be obtained temporarily by giving a + prefix argument to `macrostep-expand'. + + To have `macrostep' ignore compiler macros, customize + `macrostep-expand-compiler-macros' to `nil'. The default is `t'. + + Customize the faces `macrostep-macro-face', + `macrostep-compiler-macro-face', and `macrostep-gensym-1' through + `macrostep-gensym-5' to alter the appearance of macro expansions. + + +1.3 Locally-bound macros +──────────────────────── + + As of version 0.9, `macrostep' can expand calls to a locally-bound + macro, whether defined by a surrounding `(cl-)macrolet' form, or by + another macro-defining macro. In other words, it is possible to + expand the inner `local-macro' forms in both the following examples, + whether `local-macro' is defined by an enclosing `cl-macrolet' – + + ┌──── + │ (cl-macrolet ((local-macro (&rest args) + │ `(expansion of ,args))) + │ (local-macro (do-something))) + └──── + + – or by a macro which expands into `cl-macrolet', provided that its + definition of macro is evaluated prior to calling `macrostep-expand': + + ┌──── + │ (defmacro with-local-macro (&rest body) + │ `(cl-macrolet ((local-macro (&rest args) + │ `(expansion of ,args))) + │ ,@body)) + │ + │ (with-local-macro + │ (local-macro (do something (else))) + └──── + + See the `with-js' macro in Emacs's `js.el' for a real example of the + latter kind of macro. + + Expansion of locally-bound macros is implemented by instrumenting + Emacs Lisp's macro-expander to capture the environment at point. A + similar trick is used to detect macro- and compiler-macro calls within + expanded text so that they can be fontified accurately. + + +1.4 Expanding sub-forms +─────────────────────── + + By moving point around in the macro expansion using + `macrostep-next-macro' and `macrostep-prev-macro' (bound to the `n' + and `p' keys), it is possible to expand other macro calls within the + expansion before expanding the outermost form. This can sometimes be + useful, although it does not correspond to the real order of macro + expansion in Emacs Lisp, which proceeds by fully expanding the outer + form to a non-macro form before expanding sub-forms. + + The main reason to expand sub-forms out of order is to help with + debugging macros which programmatically expand their arguments in + order to rewrite them. Expanding the arguments of such a macro lets + you visualise what the macro definition would compute via + `macroexpand-all'. + + +1.5 Extending macrostep for other languages +─────────────────────────────────────────── + + Since version 0.9, it is possible to extend macrostep to work with + other languages besides Emacs Lisp. In typical Emacs fashion, this is + implemented by setting buffer-local variables to different function + values. Six buffer-local variables define the language-specific part + of the implementation: + + • `macrostep-sexp-bounds-function' + • `macrostep-sexp-at-point-function' + • `macrostep-environment-at-point-function' + • `macrostep-expand-1-function' + • `macrostep-print-function' + • `macrostep-macro-form-p-function' + + Typically, an implementation for another language would set these + variables in a major-mode hook. See the docstrings of each variable + for details on how each one is called and what it should return. At a + minimum, another language implementation needs to provide + `macrostep-sexp-at-point-function', `macrostep-expand-1-function', and + `macrostep-print-function'. Lisp-like languages may be able to reuse + the default `macrostep-sexp-bounds-function' if they provide another + implementation of `macrostep-macro-form-p-function'. Languages which + do not implement locally-defined macros can set + `macrostep-environment-at-point-function' to `ignore'. + + Note that the core `macrostep' machinery only interprets the return + value of `macrostep-sexp-bounds-function', so implementations for + other languages can use any internal representations of code and + environments which is convenient. Although the terminology is + Lisp-specific, there is no reason that implementations could not be + provided for non-Lisp languages with macro systems, provided there is + some way of identifying macro calls and calling the compiler / + preprocessor to obtain their expansions. + + +1.6 Bugs and known limitations +────────────────────────────── + + You can evaluate and edebug macro-expanded forms and step through the + macro-expanded version, but the form that `eval-defun' and friends + read from the buffer won't have the uninterned symbols of the real + macro expansion. This will probably work OK with CL-style gensyms, + but may cause problems with `make-symbol' symbols if they have the + same print name as another symbol in the expansion. It's possible that + using `print-circle' and `print-gensym' could get around this. + + Please send other bug reports and feature requests to the author. + + +1.7 Acknowledgements +──────────────────── + + Thanks to: + • John Wiegley for fixing a bug with the face definitions under Emacs + 24 & for plugging macrostep in his [EmacsConf presentation]! + • George Kettleborough for bug reports, and patches to highlight the + expanded region and properly handle backquotes. + • Nic Ferrier for suggesting support for local definitions within + macrolet forms + • Luís Oliveira for suggesting and implementing SLIME support + + `macrostep' was originally inspired by J. V. Toups's 'Deep Emacs Lisp' + articles ([part 1], [part 2], [screencast]). + + +[EmacsConf presentation] + +[part 1] + + +[part 2] + + +[screencast] + + + +1.8 Changelog +───────────── + + • v0.9, 2015-10-01: + • separate into Elisp-specific and generic components + • highlight and expand compiler macros + • improve local macro expansion and macro form identification by + instrumenting `macroexpand(-all)' + • v0.8, 2014-05-29: fix a bug with printing the first element of lists + • v0.7, 2014-05-11: expand locally-defined macros within + `(cl-)macrolet' forms + • v0.6, 2013-05-04: better handling of quote and backquote + • v0.5, 2013-04-16: highlight region, maintain cleaner buffer state + • v0.4, 2013-04-07: only enter macrostep-mode on successful + macro-expansion + • v0.3, 2012-10-30: print dotted lists correctly. autoload + definitions. blob - /dev/null blob + cdb7fe090808507c5ace00d7700447cec4e6e6c6 (mode 644) --- /dev/null +++ elpa/macrostep-0.9.4/README.org @@ -0,0 +1,203 @@ +* macrostep: interactive macro-expander + + =macrostep= is an Emacs minor mode for interactively stepping + through the expansion of macros in Emacs Lisp source code. It lets + you see exactly what happens at each step of the expansion process + by pretty-printing the expanded forms inline in the source buffer, + which is temporarily read-only while macro expansions are visible. + You can expand and collapse macro forms one step at a time, and + evaluate or instrument the expansions for debugging with Edebug as + normal (but see "Bugs and known limitations", below). + Single-stepping through the expansion is particularly useful for + debugging macros that expand into another macro form. These can be + difficult to debug with Emacs' built-in =macroexpand=, which + continues expansion until the top-level form is no longer a macro + call. + + Both globally-visible macros as defined by =defmacro= and local + macros bound by =(cl-)macrolet= or another macro-defining form can + be expanded. Within macro expansions, calls to macros and compiler + macros are fontified specially: macro forms using + =macrostep-macro-face=, and functions with compiler macros using + =macrostep-compiler-macro-face=. Uninterned symbols (gensyms) are + fontified based on which step in the expansion created them, to + distinguish them both from normal symbols and from other gensyms + with the same print name. + + As of version 0.9, it is also possible to extend =macrostep= to + work with other languages with macro systems in addition to Emacs + Lisp. An extension for Common Lisp (via SLIME) is in the works; + contributions for other languages are welcome. See "Extending + macrostep" below for details. + +** Key-bindings and usage + The standard keybindings in =macrostep-mode= are the following: + + - e, =, RET :: expand the macro form following point one step + - c, u, DEL :: collapse the form following point + - q, C-c C-c :: collapse all expanded forms and exit macrostep-mode + - n, TAB :: jump to the next macro form in the expansion + - p, M-TAB :: jump to the previous macro form in the expansion + + It's not very useful to enable and disable macrostep-mode + directly. Instead, bind =macrostep-expand= to a key in + =emacs-lisp-mode-map=, for example C-c e: + +#+BEGIN_SRC emacs-lisp + (define-key emacs-lisp-mode-map (kbd "C-c e") 'macrostep-expand) +#+END_SRC + + You can then enter macrostep-mode and expand a macro form + completely by typing =C-c e e e ...= as many times as necessary. + + Exit macrostep-mode by typing =q= or =C-c C-c=, or by successively + typing =c= to collapse all surrounding expansions. + +** Customization options + Type =M-x customize-group RET macrostep RET= to customize options + and faces. + + To display macro expansions in a separate window, instead of inline + in the source buffer, customize + =macrostep-expand-in-separate-buffer= to =t=. The default is + =nil=. Whichever default behavior is selected, the alternative + behavior can be obtained temporarily by giving a prefix argument to + =macrostep-expand=. + + To have =macrostep= ignore compiler macros, customize + =macrostep-expand-compiler-macros= to =nil=. The default is =t=. + + Customize the faces =macrostep-macro-face=, + =macrostep-compiler-macro-face=, and =macrostep-gensym-1= through + =macrostep-gensym-5= to alter the appearance of macro expansions. + +** Locally-bound macros + As of version 0.9, =macrostep= can expand calls to a locally-bound + macro, whether defined by a surrounding =(cl-)macrolet= form, or by + another macro-defining macro. In other words, it is possible to + expand the inner =local-macro= forms in both the following + examples, whether =local-macro= is defined by an enclosing + =cl-macrolet= -- + + #+BEGIN_SRC emacs-lisp + (cl-macrolet ((local-macro (&rest args) + `(expansion of ,args))) + (local-macro (do-something))) + #+END_SRC + + -- or by a macro which expands into =cl-macrolet=, provided that + its definition of macro is evaluated prior to calling + =macrostep-expand=: + + #+BEGIN_SRC emacs-lisp + (defmacro with-local-macro (&rest body) + `(cl-macrolet ((local-macro (&rest args) + `(expansion of ,args))) + ,@body)) + + (with-local-macro + (local-macro (do something (else))) + #+END_SRC + + See the =with-js= macro in Emacs's =js.el= for a real example of + the latter kind of macro. + + Expansion of locally-bound macros is implemented by instrumenting + Emacs Lisp's macro-expander to capture the environment at point. A + similar trick is used to detect macro- and compiler-macro calls + within expanded text so that they can be fontified accurately. + +** Expanding sub-forms + By moving point around in the macro expansion using + =macrostep-next-macro= and =macrostep-prev-macro= (bound to the =n= + and =p= keys), it is possible to expand other macro calls within + the expansion before expanding the outermost form. This can + sometimes be useful, although it does not correspond to the real + order of macro expansion in Emacs Lisp, which proceeds by fully + expanding the outer form to a non-macro form before expanding + sub-forms. + + The main reason to expand sub-forms out of order is to help with + debugging macros which programmatically expand their arguments in + order to rewrite them. Expanding the arguments of such a macro + lets you visualise what the macro definition would compute via + =macroexpand-all=. + +** Extending macrostep for other languages + Since version 0.9, it is possible to extend macrostep to work with + other languages besides Emacs Lisp. In typical Emacs fashion, this + is implemented by setting buffer-local variables to different + function values. Six buffer-local variables define the + language-specific part of the implementation: + + - =macrostep-sexp-bounds-function= + - =macrostep-sexp-at-point-function= + - =macrostep-environment-at-point-function= + - =macrostep-expand-1-function= + - =macrostep-print-function= + - =macrostep-macro-form-p-function= + + Typically, an implementation for another language would set these + variables in a major-mode hook. See the docstrings of each + variable for details on how each one is called and what it should + return. At a minimum, another language implementation needs to + provide =macrostep-sexp-at-point-function=, + =macrostep-expand-1-function=, and =macrostep-print-function=. + Lisp-like languages may be able to reuse the default + =macrostep-sexp-bounds-function= if they provide another + implementation of =macrostep-macro-form-p-function=. Languages + which do not implement locally-defined macros can set + =macrostep-environment-at-point-function= to =ignore=. + + Note that the core =macrostep= machinery only interprets the return + value of =macrostep-sexp-bounds-function=, so implementations for + other languages can use any internal representations of code and + environments which is convenient. Although the terminology is + Lisp-specific, there is no reason that implementations could not be + provided for non-Lisp languages with macro systems, provided there + is some way of identifying macro calls and calling the compiler / + preprocessor to obtain their expansions. + +** Bugs and known limitations + You can evaluate and edebug macro-expanded forms and step through + the macro-expanded version, but the form that =eval-defun= and + friends read from the buffer won't have the uninterned symbols of + the real macro expansion. This will probably work OK with CL-style + gensyms, but may cause problems with =make-symbol= symbols if they + have the same print name as another symbol in the expansion. It's + possible that using =print-circle= and =print-gensym= could get + around this. + + Please send other bug reports and feature requests to the author. + +** Acknowledgements + Thanks to: + - John Wiegley for fixing a bug with the face definitions under + Emacs 24 & for plugging macrostep in his [[http://youtu.be/RvPFZL6NJNQ][EmacsConf presentation]]! + - George Kettleborough for bug reports, and patches to highlight + the expanded region and properly handle backquotes. + - Nic Ferrier for suggesting support for local definitions within + macrolet forms + - Luís Oliveira for suggesting and implementing SLIME support + + =macrostep= was originally inspired by J. V. Toups's 'Deep Emacs + Lisp' articles ([[http://dorophone.blogspot.co.uk/2011/04/deep-emacs-part-1.html][part 1]], [[http://dorophone.blogspot.co.uk/2011/04/deep-emacs-lisp-part-2.html][part 2]], [[http://dorophone.blogspot.co.uk/2011/05/monadic-parser-combinators-in-elisp.html][screencast]]). + +** Changelog + - v0.9, 2015-10-01: + - separate into Elisp-specific and generic components + - highlight and expand compiler macros + - improve local macro expansion and macro form identification by + instrumenting =macroexpand(-all)= + - v0.8, 2014-05-29: fix a bug with printing the first element of + lists + - v0.7, 2014-05-11: expand locally-defined macros within + =(cl-)macrolet= forms + - v0.6, 2013-05-04: better handling of quote and backquote + - v0.5, 2013-04-16: highlight region, maintain cleaner buffer state + - v0.4, 2013-04-07: only enter macrostep-mode on successful + macro-expansion + - v0.3, 2012-10-30: print dotted lists correctly. autoload + definitions. + +#+OPTIONS: author:nil email:nil toc:nil timestamp:nil blob - /dev/null blob + aad9191a283b80df6f987bb57c5934427b671d1f (mode 644) --- /dev/null +++ elpa/macrostep-0.9.4/macrostep-autoloads.el @@ -0,0 +1,74 @@ +;;; macrostep-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 macrostep.el + +(autoload 'macrostep-mode "macrostep" "\ +Minor mode for inline expansion of macros in Emacs Lisp source buffers. + +\\Progressively expand macro forms with \\[macrostep-expand], collapse them with \\[macrostep-collapse], +and move back and forth with \\[macrostep-next-macro] and \\[macrostep-prev-macro]. Use \\[macrostep-collapse-all] or collapse all +visible expansions to quit and return to normal editing. + +\\{macrostep-mode-map} + +This is a minor mode. If called interactively, toggle the +`Macrostep 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 `macrostep-mode'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +(fn &optional ARG)" t) +(autoload 'macrostep-expand "macrostep" "\ +Expand the macro form following point by one step. + +Enters `macrostep-mode' if it is not already active, making the +buffer temporarily read-only. If `macrostep-mode' is active and +the form following point is not a macro form, search forward in +the buffer and expand the next macro form found, if any. + +If optional argument TOGGLE-SEPARATE-BUFFER is non-nil (or set + with a prefix argument), the expansion is displayed in a + separate buffer instead of inline in the current buffer. + Setting `macrostep-expand-in-separate-buffer' to non-nil swaps + these two behaviors. + +(fn &optional TOGGLE-SEPARATE-BUFFER)" t) +(register-definition-prefixes "macrostep" '("macrostep-")) + + +;;; Generated autoloads from macrostep-c.el + +(autoload 'macrostep-c-mode-hook "macrostep-c") +(add-hook 'c-mode-hook #'macrostep-c-mode-hook) +(register-definition-prefixes "macrostep-c" '("macrostep-c-")) + +;;; End of scraped data + +(provide 'macrostep-autoloads) + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; no-native-compile: t +;; coding: utf-8-emacs-unix +;; End: + +;;; macrostep-autoloads.el ends here blob - /dev/null blob + 226ecce2ac19e32f574682e77bdd3dc97fed4c7d (mode 644) --- /dev/null +++ elpa/macrostep-0.9.4/macrostep-c.el @@ -0,0 +1,183 @@ +;;; macrostep-c.el --- macrostep interface to C preprocessor -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 Jon Oddie + +;; Author: Jon Oddie +;; Url: https://github.com/emacsorphanage/macrostep +;; Keywords: c, languages, macro, debugging + +;; SPDX-License-Identifier: GPL-3.0-or-later + +;; This file 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 file 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 file. If not, see . + +;;; Commentary: + +;; A thin wrapper around Emacs's built-in `cmacexp' library to provide +;; basic support for expanding C macros using the `macrostep' user +;; interface. To use, position point on a macro use in a C buffer and +;; type `M-x macrostep-expand'. The variables `c-macro-preprocessor' +;; and especially `c-macro-cppflags' may need to be set correctly for +;; accurate expansion. + +;; This is fairly basic compared to the Emacs Lisp `macrostep'. In +;; particular, there is no step-by-step expansion, since C macros are +;; expanded in a single "cpp" pass, and no pretty-printing. + +;; To hide the buffer containing "cpp" warnings (not recommended), you +;; could do something like: +;; +;; (push `(,(regexp-quote macrostep-c-warning-buffer) +;; (display-buffer-no-window)) +;; display-buffer-alist) + +;;; Code: + +(require 'macrostep) +(require 'cmacexp) +(require 'cl-lib) + +(require 'subr-x nil t) +(defalias 'macrostep-c-string-trim + (if (fboundp 'string-trim) + #'string-trim + (lambda (string) + (when (string-match "\\`[ \t\n\r]+" string) + (setq string (replace-match "" t t string))) + (when (string-match "[ \t\n\r]+\\'" string) + (setq string (replace-match "" t t string))) + string))) + +(put 'macrostep-c-non-macro 'error-conditions + '(macrostep-c-non-macro error)) +(put 'macrostep-c-non-macro 'error-message + "Text around point is not a macro call.") + +(put 'macrostep-c-expansion-failed 'error-conditions + '(macrostep-c-expansion-failed error)) +(put 'macrostep-c-expansion-failed 'error-message + "Macro-expansion failed.") + +(defvar macrostep-c-warning-buffer "*Macroexpansion Warnings*") + +;;;###autoload +(defun macrostep-c-mode-hook () + (setq macrostep-sexp-bounds-function + #'macrostep-c-sexp-bounds) + (setq macrostep-sexp-at-point-function + #'macrostep-c-sexp-at-point) + (setq macrostep-environment-at-point-function + #'ignore) + (setq macrostep-expand-1-function + #'macrostep-c-expand-1) + (setq macrostep-print-function + #'macrostep-c-print-function) + (add-hook 'macrostep-mode-off-hook + #'macrostep-c-mode-off nil t)) + +(defun macrostep-c-mode-off (&rest _ignore) + (when (derived-mode-p 'c-mode) + (let ((warning-window + (get-buffer-window macrostep-c-warning-buffer))) + (when warning-window + (quit-window nil warning-window))))) + +;;;###autoload +(add-hook 'c-mode-hook #'macrostep-c-mode-hook) + +(defun macrostep-c-sexp-bounds () + (save-excursion + (cl-loop + (let ((region (macrostep-c-sexp-bounds-1))) + (cond + ((null region) + (signal 'macrostep-c-non-macro nil)) + ((macrostep-c-expandable-p region) + (cl-return region)) + (t + (condition-case nil + (progn + (backward-up-list) + (skip-syntax-backward "-")) + (scan-error + (signal 'macrostep-c-non-macro nil))))))))) + +(defun macrostep-c-sexp-bounds-1 () + (let ((region (bounds-of-thing-at-point 'symbol))) + (when region + (cl-destructuring-bind (symbol-start . symbol-end) region + (save-excursion + (goto-char symbol-end) + (if (looking-at "[[:space:]]*(") + (cons symbol-start (scan-sexps symbol-end 1)) + region)))))) + +(defun macrostep-c-expandable-p (region) + (cl-destructuring-bind (start . end) region + (condition-case nil + (cl-destructuring-bind (expansion _warnings) + (macrostep-c-expand-region start end) + (and (cl-plusp (length expansion)) + (not (string= expansion (buffer-substring start end))))) + (macrostep-c-expansion-failed nil)))) + +(defun macrostep-c-sexp-at-point (start end) + (cons start end)) + +(defun macrostep-c-expand-1 (region _ignore) + (cl-destructuring-bind (start . end) region + (cl-destructuring-bind (expansion warnings) + (macrostep-c-expand-region start end) + (when (cl-plusp (length warnings)) + (with-current-buffer + (get-buffer-create macrostep-c-warning-buffer) + (let ((inhibit-read-only t)) + (erase-buffer) + (insert warnings) + (goto-char (point-min))) + (special-mode) + (display-buffer (current-buffer) + '(display-buffer-pop-up-window + (inhibit-same-window . t) + (allow-no-window . t))))) + expansion))) + +(defun macrostep-c-expand-region (start end) + (let ((expansion + (condition-case nil + (c-macro-expansion start end + (concat c-macro-preprocessor " " + c-macro-cppflags)) + (search-failed + (signal 'macrostep-c-expansion-failed nil))))) + (with-temp-buffer + (save-excursion + (insert expansion)) + (when (looking-at (regexp-quote "/*")) + (search-forward "*/")) + (let ((warnings (buffer-substring (point-min) (point))) + (expansion (buffer-substring (point) (point-max)))) + (mapcar #'macrostep-c-string-trim (list expansion warnings)))))) + +(defun macrostep-c-print-function (expansion &rest _ignore) + (with-temp-buffer + (insert expansion) + (let ((exit-code + (shell-command-on-region (point-min) (point-max) "indent" nil t))) + (when (zerop exit-code) + (setq expansion (macrostep-c-string-trim (buffer-string)))))) + (insert expansion)) + +(provide 'macrostep-c) + +;;; macrostep-c.el ends here blob - /dev/null blob + 71a7fe3e303361c5554ac0140eac7063e7c5ba28 (mode 644) --- /dev/null +++ elpa/macrostep-0.9.4/macrostep-pkg.el @@ -0,0 +1,2 @@ +;; Generated package description from macrostep.el -*- no-byte-compile: t -*- +(define-package "macrostep" "0.9.4" "Interactive macro expander" '((cl-lib "0.5") (compat "29")) :commit "31d4adcca4f08cfd7a45f85e691aaa7a9316b355" :authors '(("Jon Oddie" . "j.j.oddie@gmail.com")) :maintainer '("Jon Oddie" . "j.j.oddie@gmail.com") :keywords '("lisp" "languages" "macro" "debugging") :url "https://github.com/emacsorphanage/macrostep") blob - /dev/null blob + 767f9ebba90a724928e7110b9a03caab265b5ae1 (mode 644) --- /dev/null +++ elpa/macrostep-0.9.4/macrostep.el @@ -0,0 +1,1130 @@ +;;; macrostep.el --- Interactive macro expander -*- lexical-binding: t; -*- + +;; Copyright (C) 2012-2015 Jon Oddie +;; Copyright (C) 2020-2024 Free Software Foundation, Inc. + +;; Author: Jon Oddie +;; Url: https://github.com/emacsorphanage/macrostep +;; Keywords: lisp, languages, macro, debugging + +;; Package-Version: 0.9.4 +;; Package-Requires: ((cl-lib "0.5") (compat "29")) + +;; SPDX-License-Identifier: GPL-3.0-or-later + +;; This file 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 file 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 file. If not, see . + +;;; Commentary: + +;; `macrostep' is an Emacs minor mode for interactively stepping through +;; the expansion of macros in Emacs Lisp source code. It lets you see +;; exactly what happens at each step of the expansion process by +;; pretty-printing the expanded forms inline in the source buffer, which is +;; temporarily read-only while macro expansions are visible. You can +;; expand and collapse macro forms one step at a time, and evaluate or +;; instrument the expansions for debugging with Edebug as normal (but see +;; "Bugs and known limitations", below). Single-stepping through the +;; expansion is particularly useful for debugging macros that expand into +;; another macro form. These can be difficult to debug with Emacs' +;; built-in `macroexpand', which continues expansion until the top-level +;; form is no longer a macro call. + +;; Both globally-visible macros as defined by `defmacro' and local macros +;; bound by `(cl-)macrolet' or another macro-defining form can be expanded. +;; Within macro expansions, calls to macros and compiler macros are +;; fontified specially: macro forms using `macrostep-macro-face', and +;; functions with compiler macros using `macrostep-compiler-macro-face'. +;; Uninterned symbols (gensyms) are fontified based on which step in the +;; expansion created them, to distinguish them both from normal symbols and +;; from other gensyms with the same print name. + +;; As of version 0.9, it is also possible to extend `macrostep' to work +;; with other languages with macro systems in addition to Emacs Lisp. An +;; extension for Common Lisp (via SLIME) is in the works; contributions for +;; other languages are welcome. See "Extending macrostep" below for +;; details. + + +;; 1 Key-bindings and usage +;; ======================== + +;; The standard keybindings in `macrostep-mode' are the following: + +;; e, =, RET : expand the macro form following point one step +;; c, u, DEL : collapse the form following point +;; q, C-c C-c: collapse all expanded forms and exit macrostep-mode +;; n, TAB : jump to the next macro form in the expansion +;; p, M-TAB : jump to the previous macro form in the expansion + +;; It's not very useful to enable and disable macrostep-mode directly. +;; Instead, bind `macrostep-expand' to a key in `emacs-lisp-mode-map', +;; for example C-c e: + +;; ,---- +;; | (define-key emacs-lisp-mode-map (kbd "C-c e") 'macrostep-expand) +;; `---- + +;; You can then enter macrostep-mode and expand a macro form completely +;; by typing `C-c e e e ...' as many times as necessary. + +;; Exit macrostep-mode by typing `q' or `C-c C-c', or by successively +;; typing `c' to collapse all surrounding expansions. + + +;; 2 Customization options +;; ======================= + +;; Type `M-x customize-group RET macrostep RET' to customize options and +;; faces. + +;; To display macro expansions in a separate window, instead of inline in +;; the source buffer, customize `macrostep-expand-in-separate-buffer' to +;; `t'. The default is `nil'. Whichever default behavior is selected, +;; the alternative behavior can be obtained temporarily by giving a +;; prefix argument to `macrostep-expand'. + +;; To have `macrostep' ignore compiler macros, customize +;; `macrostep-expand-compiler-macros' to `nil'. The default is `t'. + +;; Customize the faces `macrostep-macro-face', +;; `macrostep-compiler-macro-face', and `macrostep-gensym-1' through +;; `macrostep-gensym-5' to alter the appearance of macro expansions. + + +;; 3 Locally-bound macros +;; ====================== + +;; As of version 0.9, `macrostep' can expand calls to a locally-bound +;; macro, whether defined by a surrounding `(cl-)macrolet' form, or by +;; another macro-defining macro. In other words, it is possible to +;; expand the inner `local-macro' forms in both the following examples, +;; whether `local-macro' is defined by an enclosing `cl-macrolet' -- + +;; ,---- +;; | (cl-macrolet ((local-macro (&rest args) +;; | `(expansion of ,args))) +;; | (local-macro (do-something))) +;; `---- + +;; -- or by a macro which expands into `cl-macrolet', provided that its +;; definition of macro is evaluated prior to calling `macrostep-expand': + +;; ,---- +;; | (defmacro with-local-macro (&rest body) +;; | `(cl-macrolet ((local-macro (&rest args) +;; | `(expansion of ,args))) +;; | ,@body)) +;; | +;; | (with-local-macro +;; | (local-macro (do something (else))) +;; `---- + +;; See the `with-js' macro in Emacs's `js.el' for a real example of the +;; latter kind of macro. + +;; Expansion of locally-bound macros is implemented by instrumenting +;; Emacs Lisp's macro-expander to capture the environment at point. A +;; similar trick is used to detect macro- and compiler-macro calls within +;; expanded text so that they can be fontified accurately. + + +;; 4 Expanding sub-forms +;; ===================== + +;; By moving point around in the macro expansion using +;; `macrostep-next-macro' and `macrostep-prev-macro' (bound to the `n' +;; and `p' keys), it is possible to expand other macro calls within the +;; expansion before expanding the outermost form. This can sometimes be +;; useful, although it does not correspond to the real order of macro +;; expansion in Emacs Lisp, which proceeds by fully expanding the outer +;; form to a non-macro form before expanding sub-forms. + +;; The main reason to expand sub-forms out of order is to help with +;; debugging macros which programmatically expand their arguments in +;; order to rewrite them. Expanding the arguments of such a macro lets +;; you visualise what the macro definition would compute via +;; `macroexpand-all'. + + +;; 5 Extending macrostep for other languages +;; ========================================= + +;; Since version 0.9, it is possible to extend macrostep to work with +;; other languages besides Emacs Lisp. In typical Emacs fashion, this is +;; implemented by setting buffer-local variables to different function +;; values. Six buffer-local variables define the language-specific part +;; of the implementation: + +;; - `macrostep-sexp-bounds-function' +;; - `macrostep-sexp-at-point-function' +;; - `macrostep-environment-at-point-function' +;; - `macrostep-expand-1-function' +;; - `macrostep-print-function' +;; - `macrostep-macro-form-p-function' + +;; Typically, an implementation for another language would set these +;; variables in a major-mode hook. See the docstrings of each variable +;; for details on how each one is called and what it should return. At a +;; minimum, another language implementation needs to provide +;; `macrostep-sexp-at-point-function', `macrostep-expand-1-function', and +;; `macrostep-print-function'. Lisp-like languages may be able to reuse +;; the default `macrostep-sexp-bounds-function' if they provide another +;; implementation of `macrostep-macro-form-p-function'. Languages which +;; do not implement locally-defined macros can set +;; `macrostep-environment-at-point-function' to `ignore'. + +;; Note that the core `macrostep' machinery only interprets the return +;; value of `macrostep-sexp-bounds-function', so implementations for +;; other languages can use any internal representations of code and +;; environments which is convenient. Although the terminology is +;; Lisp-specific, there is no reason that implementations could not be +;; provided for non-Lisp languages with macro systems, provided there is +;; some way of identifying macro calls and calling the compiler / +;; preprocessor to obtain their expansions. + + +;; 6 Bugs and known limitations +;; ============================ + +;; You can evaluate and edebug macro-expanded forms and step through the +;; macro-expanded version, but the form that `eval-defun' and friends +;; read from the buffer won't have the uninterned symbols of the real +;; macro expansion. This will probably work OK with CL-style gensyms, +;; but may cause problems with `make-symbol' symbols if they have the +;; same print name as another symbol in the expansion. It's possible that +;; using `print-circle' and `print-gensym' could get around this. + +;; Please send other bug reports and feature requests to the author. + + +;; 7 Acknowledgements +;; ================== + +;; Thanks to: +;; - John Wiegley for fixing a bug with the face definitions under Emacs +;; 24 & for plugging macrostep in his [EmacsConf presentation]! +;; - George Kettleborough for bug reports, and patches to highlight the +;; expanded region and properly handle backquotes. +;; - Nic Ferrier for suggesting support for local definitions within +;; macrolet forms +;; - Luís Oliveira for suggesting and implementing SLIME support + +;; `macrostep' was originally inspired by J. V. Toups's 'Deep Emacs Lisp' +;; articles ([part 1], [part 2], [screencast]). + +;; [EmacsConf presentation] http://youtu.be/RvPFZL6NJNQ + +;; [part 1] +;; http://dorophone.blogspot.co.uk/2011/04/deep-emacs-part-1.html + +;; [part 2] +;; http://dorophone.blogspot.co.uk/2011/04/deep-emacs-lisp-part-2.html + +;; [screencast] +;; http://dorophone.blogspot.co.uk/2011/05/monadic-parser-combinators-in-elisp.html + + +;;; News: + +;; - v0.9.2, 2023-05-12: +;; - name the keymap macrostep-mode-map, fixing a regression in v0.9.1 +;; - v0.9.1, 2023-03-12: +;; - bug fixes, cleanup and modernization +;; - v0.9, 2015-10-01: +;; - separate into Elisp-specific and generic components +;; - highlight and expand compiler macros +;; - improve local macro expansion and macro form identification by +;; instrumenting `macroexpand(-all)' +;; - v0.8, 2014-05-29: fix a bug with printing the first element of lists +;; - v0.7, 2014-05-11: expand locally-defined macros within +;; `(cl-)macrolet' forms +;; - v0.6, 2013-05-04: better handling of quote and backquote +;; - v0.5, 2013-04-16: highlight region, maintain cleaner buffer state +;; - v0.4, 2013-04-07: only enter macrostep-mode on successful +;; macro-expansion +;; - v0.3, 2012-10-30: print dotted lists correctly. autoload +;; definitions. + +;;; Code: + +(require 'compat) + +(require 'pp) +(require 'ring) +(require 'cl-lib) + + +;;; Constants and dynamically bound variables +(defvar-local macrostep-overlays nil + "List of all macro stepper overlays in the current buffer.") + +(defvar-local macrostep-gensym-depth nil + "Number of macro expansion levels that have introduced gensyms so far.") + +(defvar-local macrostep-gensyms-this-level nil + "Non-nil if gensyms have been encountered during current level of macro expansion.") + +(defvar-local macrostep-saved-undo-list nil + "Saved value of `buffer-undo-list' upon entering macrostep mode.") + +(defvar-local macrostep-saved-read-only nil + "Saved value of `buffer-read-only' upon entering macrostep mode.") + +(defvar-local macrostep-expansion-buffer nil + "Non-nil if the current buffer is a macro-expansion buffer.") + +(defvar-local macrostep-outer-environment nil + "Outermost macro-expansion environment to use in macro-expansion buffers. + +This variable is used to save information about any enclosing +`cl-macrolet' context when a macro form is expanded in a separate +buffer.") + +;;; Customization options and faces +(defgroup macrostep nil + "Interactive macro stepper for Emacs Lisp." + :group 'lisp + :link '(emacs-commentary-link :tag "commentary" "macrostep.el") + :link '(emacs-library-link :tag "lisp file" "macrostep.el") + :link '(url-link :tag "web page" "https://github.com/emacsorphanage/macrostep")) + +(defface macrostep-gensym-1 + '((((min-colors 16581375)) :foreground "#8080c0" :box t :bold t) + (((min-colors 8)) :background "cyan") + (t :inverse-video t)) + "Face for gensyms created in the first level of macro expansion." + :version "1.0") + +(defface macrostep-gensym-2 + '((((min-colors 16581375)) :foreground "#8fbc8f" :box t :bold t) + (((min-colors 8)) :background "#00cd00") + (t :inverse-video t)) + "Face for gensyms created in the second level of macro expansion." + :version "1.0") + +(defface macrostep-gensym-3 + '((((min-colors 16581375)) :foreground "#daa520" :box t :bold t) + (((min-colors 8)) :background "yellow") + (t :inverse-video t)) + "Face for gensyms created in the third level of macro expansion." + :version "1.0") + +(defface macrostep-gensym-4 + '((((min-colors 16581375)) :foreground "#cd5c5c" :box t :bold t) + (((min-colors 8)) :background "red") + (t :inverse-video t)) + "Face for gensyms created in the fourth level of macro expansion." + :version "1.0") + +(defface macrostep-gensym-5 + '((((min-colors 16581375)) :foreground "#da70d6" :box t :bold t) + (((min-colors 8)) :background "magenta") + (t :inverse-video t)) + "Face for gensyms created in the fifth level of macro expansion." + :version "1.0") + +(defface macrostep-expansion-highlight-face + `((((min-colors 16581375) (background light)) + ,@(and (>= emacs-major-version 27) '(:extend t)) + :background "#eee8d5") + (((min-colors 16581375) (background dark)) + ,@(and (>= emacs-major-version 27) '(:extend t)) + :background "#222222")) + "Face for macro-expansion highlight." + :version "1.0") + +(defface macrostep-macro-face + '((t :underline t)) + "Face for macros in macro-expanded code." + :version "1.0") + +(defface macrostep-compiler-macro-face + '((t :slant italic)) + "Face for compiler macros in macro-expanded code." + :version "1.0") + +(defcustom macrostep-expand-in-separate-buffer nil + "When non-nil, show expansions in a separate buffer instead of inline." + :type 'boolean + :version "1.0") + +(defcustom macrostep-expand-compiler-macros t + "When non-nil, also expand compiler macros." + :type 'boolean + :version "1.0") + +(defvar macrostep-gensym-faces + (ring-convert-sequence-to-ring + (list 'macrostep-gensym-1 'macrostep-gensym-2 'macrostep-gensym-3 + 'macrostep-gensym-4 'macrostep-gensym-5)) + "Ring of all macrostepper faces for fontifying gensyms.") + +;; Other modes can enable macrostep by redefining these functions to +;; language-specific versions. +(defvar macrostep-sexp-bounds-function + #'macrostep-sexp-bounds + "Function to return the bounds of the macro form nearest point. + +It will be called with no arguments and should return a cons of +buffer positions, (START . END). It should use `save-excursion' +to avoid changing the position of point. + +The default value, `macrostep-sexp-bounds', implements this for +Emacs Lisp, and may be suitable for other Lisp-like languages.") +(make-variable-buffer-local 'macrostep-sexp-bounds-function) + +(defvar macrostep-sexp-at-point-function + #'macrostep-sexp-at-point + "Function to return the macro form at point for expansion. + +It will be called with two arguments, the values of START and END +returned by `macrostep-sexp-bounds-function', and with point +positioned at START. It should return a value suitable for +passing as the first argument to `macrostep-expand-1-function'. + +The default value, `macrostep-sexp-at-point', implements this for +Emacs Lisp, and may be suitable for other Lisp-like languages.") +(make-variable-buffer-local 'macrostep-sexp-at-point-function) + +(defvar macrostep-environment-at-point-function + #'macrostep-environment-at-point + "Function to return the local macro-expansion environment at point. + +It will be called with no arguments, and should return a value +suitable for passing as the second argument to +`macrostep-expand-1-function'. + +The default value, `macrostep-environment-at-point', is specific +to Emacs Lisp. For languages which do not implement local +macro-expansion environments, this should be set to `ignore' +or `(lambda () nil)'.") +(make-variable-buffer-local 'macrostep-environment-at-point-function) + +(defvar macrostep-expand-1-function + #'macrostep-expand-1 + "Function to perform one step of macro-expansion. + +It will be called with two arguments, FORM and ENVIRONMENT, the +return values of `macrostep-sexp-at-point-function' and +`macrostep-environment-at-point-function' respectively. It +should return the result of expanding FORM by one step as a value +which is suitable for passing as the argument to +`macrostep-print-function'. + +The default value, `macrostep-expand-1', is specific to Emacs Lisp.") +(make-variable-buffer-local 'macrostep-expand-1-function) + +(defvar macrostep-print-function + #'macrostep-pp + "Function to pretty-print macro expansions. + +It will be called with two arguments, FORM and ENVIRONMENT, the +return values of `macrostep-sexp-at-point-function' and +`macrostep-environment-at-point-function' respectively. It +should insert a pretty-printed representation at point in the +current buffer, leaving point just after the inserted +representation, without altering any other text in the current +buffer. + +The default value, `macrostep-pp', is specific to Emacs Lisp.") +(make-variable-buffer-local 'macrostep-print-function) + +(defvar macrostep-macro-form-p-function + #'macrostep-macro-form-p + "Function to check whether a form is a macro call. + +It will be called with two arguments, FORM and ENVIRONMENT -- the +return values of `macrostep-sexp-at-point-function' and +`macrostep-environment-at-point-function' respectively -- and +should return non-nil if FORM would undergo macro-expansion in +ENVIRONMENT. + +This is called only from `macrostep-sexp-bounds', so it need not +be provided if a different value is used for +`macrostep-sexp-bounds-function'. + +The default value, `macrostep-macro-form-p', is specific to Emacs Lisp.") +(make-variable-buffer-local 'macrostep-macro-form-p-function) + + +;;; Define keymap and minor mode +(define-obsolete-variable-alias 'macrostep-mode-keymap 'macrostep-mode-map "2023") +(define-obsolete-variable-alias 'macrostep-keymap 'macrostep-mode-map "2022") +(defvar-keymap macrostep-mode-map + :doc "Keymap for `macrostep-mode'." + "RET" #'macrostep-expand + "=" #'macrostep-expand + "e" #'macrostep-expand + + "DEL" #'macrostep-collapse + "u" #'macrostep-collapse + "c" #'macrostep-collapse + + "TAB" #'macrostep-next-macro + "n" #'macrostep-next-macro + "M-TAB" #'macrostep-prev-macro + "p" #'macrostep-prev-macro + + "q" #'macrostep-collapse-all + "C-c C-c" #'macrostep-collapse-all) + +;;;###autoload +(define-minor-mode macrostep-mode + "Minor mode for inline expansion of macros in Emacs Lisp source buffers. + +\\Progressively expand macro forms with \ +\\[macrostep-expand], collapse them with \\[macrostep-collapse], +and move back and forth with \\[macrostep-next-macro] and \ +\\[macrostep-prev-macro]. Use \\[macrostep-collapse-all] or collapse all +visible expansions to quit and return to normal editing. + +\\{macrostep-mode-map}" + :lighter " Macro-Stepper" + :group 'macrostep + (if macrostep-mode + (progn + ;; Disable recording of undo information + (setq macrostep-saved-undo-list buffer-undo-list + buffer-undo-list t) + ;; Remember whether buffer was read-only + (setq macrostep-saved-read-only buffer-read-only + buffer-read-only t) + ;; Set up post-command hook to bail out on leaving read-only + (add-hook 'post-command-hook #'macrostep-command-hook nil t) + (message (substitute-command-keys "\ +\\Entering macro stepper mode. \ +Use \\[macrostep-expand] to expand, \\[macrostep-collapse] to collapse, \ +\\[macrostep-collapse-all] to exit."))) + + ;; Exiting mode + (if macrostep-expansion-buffer + ;; Kill dedicated expansion buffers + (quit-window t) + ;; Collapse any remaining overlays + (when macrostep-overlays (macrostep-collapse-all)) + ;; Restore undo info & read-only state + (setq buffer-undo-list macrostep-saved-undo-list + buffer-read-only macrostep-saved-read-only + macrostep-saved-undo-list nil) + ;; Remove our post-command hook + (remove-hook 'post-command-hook #'macrostep-command-hook t)))) + +(defun macrostep-command-hook () + "Hook function for use by `post-command hook'. +Bail out of `macrostep-mode' if the user types +`\\[read-only-mode]' to make the buffer writable again." + (if (not buffer-read-only) + (macrostep-mode 0))) + + +;;; Interactive functions +;;;###autoload +(defun macrostep-expand (&optional toggle-separate-buffer) + "Expand the macro form following point by one step. + +Enters `macrostep-mode' if it is not already active, making the +buffer temporarily read-only. If `macrostep-mode' is active and +the form following point is not a macro form, search forward in +the buffer and expand the next macro form found, if any. + +If optional argument TOGGLE-SEPARATE-BUFFER is non-nil (or set + with a prefix argument), the expansion is displayed in a + separate buffer instead of inline in the current buffer. + Setting `macrostep-expand-in-separate-buffer' to non-nil swaps + these two behaviors." + (interactive "P") + (cl-destructuring-bind (start . end) + (funcall macrostep-sexp-bounds-function) + (goto-char start) + (let* ((sexp (funcall macrostep-sexp-at-point-function start end)) + (end (copy-marker end)) + (text (buffer-substring start end)) + (env (funcall macrostep-environment-at-point-function)) + (expansion (funcall macrostep-expand-1-function sexp env))) + + ;; Create a dedicated macro-expansion buffer and copy the text to + ;; be expanded into it, if required + (let ((separate-buffer-p + (if toggle-separate-buffer + (not macrostep-expand-in-separate-buffer) + macrostep-expand-in-separate-buffer))) + (when (and separate-buffer-p (not macrostep-expansion-buffer)) + (let ((mode major-mode) + (buffer + (get-buffer-create (generate-new-buffer-name "*macro expansion*")))) + (set-buffer buffer) + (funcall mode) + (setq macrostep-expansion-buffer t) + (setq macrostep-outer-environment env) + (save-excursion + (setq start (point)) + (insert text) + (setq end (point-marker))) + (pop-to-buffer buffer)))) + + (unless macrostep-mode (macrostep-mode t)) + (let ((existing-overlay (macrostep-overlay-at-point)) + (macrostep-gensym-depth macrostep-gensym-depth) + (macrostep-gensyms-this-level nil) + priority) + (if existing-overlay ; Expanding part of a previous macro-expansion + (setq priority (1+ (overlay-get existing-overlay 'priority)) + macrostep-gensym-depth + (overlay-get existing-overlay 'macrostep-gensym-depth)) + ;; Expanding source buffer text + (setq priority 1 + macrostep-gensym-depth -1)) + + (with-silent-modifications + (atomic-change-group + (let ((inhibit-read-only t)) + (save-excursion + ;; Insert expansion + (funcall macrostep-print-function expansion env) + ;; Delete the original form + (macrostep-collapse-overlays-in (point) end) + (delete-region (point) end) + ;; Create a new overlay + (let* ((overlay + (make-overlay start + (if (looking-at "\n") + (1+ (point)) + (point)))) + (highlight-overlay (unless macrostep-expansion-buffer + (copy-overlay overlay)))) + (unless macrostep-expansion-buffer + ;; Highlight the overlay in original source buffers only + (overlay-put highlight-overlay 'face 'macrostep-expansion-highlight-face) + (overlay-put highlight-overlay 'priority -1) + (overlay-put overlay 'macrostep-highlight-overlay highlight-overlay)) + (overlay-put overlay 'priority priority) + (overlay-put overlay 'macrostep-original-text text) + (overlay-put overlay 'macrostep-gensym-depth macrostep-gensym-depth) + (push overlay macrostep-overlays)))))))))) + +(defun macrostep-collapse () + "Collapse the innermost macro expansion near point to its source text. + +If no more macro expansions are visible after this, exit +`macrostep-mode'." + (interactive) + (let ((overlay (macrostep-overlay-at-point))) + (when (not overlay) (error "No macro expansion at point")) + (let ((inhibit-read-only t)) + (with-silent-modifications + (atomic-change-group + (macrostep-collapse-overlay overlay))))) + (unless macrostep-overlays + (macrostep-mode 0))) + +(defun macrostep-collapse-all () + "Collapse all visible macro expansions and exit `macrostep-mode'." + (interactive) + (let ((inhibit-read-only t)) + (with-silent-modifications + (dolist (overlay macrostep-overlays) + (let ((outermost (= (overlay-get overlay 'priority) 1))) + ;; We only need restore the original text for the outermost + ;; overlays + (macrostep-collapse-overlay overlay (not outermost)))))) + (setq macrostep-overlays nil) + (macrostep-mode 0)) + +(defun macrostep-next-macro () + "Move point forward to the next macro form in macro-expanded text." + (interactive) + (let* ((start (if (get-text-property (point) 'macrostep-macro-start) + (1+ (point)) + (point))) + (next (next-single-property-change start 'macrostep-macro-start))) + (if next + (goto-char next) + (error "No more macro forms found")))) + +(defun macrostep-prev-macro () + "Move point back to the previous macro form in macro-expanded text." + (interactive) + (let (prev) + (save-excursion + (while + (progn + (setq prev (previous-single-property-change + (point) 'macrostep-macro-start)) + (if (or (not prev) + (get-text-property (1- prev) 'macrostep-macro-start)) + nil + (prog1 t (goto-char prev)))))) + (if prev + (goto-char (1- prev)) + (error "No previous macro form found")))) + + +;;; Utility functions (not language-specific) + +(defun macrostep-overlay-at-point () + "Return the innermost macro stepper overlay at point." + (cdr (get-char-property-and-overlay (point) 'macrostep-original-text))) + +(defun macrostep-collapse-overlay (overlay &optional no-restore-p) + "Collapse macro-expansion buffer OVERLAY and restore the unexpanded source text. + +As a minor optimization, does not restore the original source +text if NO-RESTORE-P is non-nil. This is safe to do when +collapsing all the sub-expansions of an outer overlay, since the +outer overlay will restore the original source itself. + +Also removes the overlay from `macrostep-overlays'." + (with-current-buffer (overlay-buffer overlay) + ;; If we're cleaning up we don't need to bother restoring text + ;; or checking for inner overlays to delete + (unless no-restore-p + (let* ((start (overlay-start overlay)) + (end (overlay-end overlay)) + (text (overlay-get overlay 'macrostep-original-text)) + (sexp-end + (copy-marker + (if (equal (char-before end) ?\n) (1- end) end)))) + (macrostep-collapse-overlays-in start end) + (goto-char (overlay-start overlay)) + (save-excursion + (insert text) + (delete-region (point) sexp-end)))) + ;; Remove overlay from the list and delete it + (setq macrostep-overlays + (delq overlay macrostep-overlays)) + (let ((highlight-overlay (overlay-get overlay 'macrostep-highlight-overlay))) + (when highlight-overlay (delete-overlay highlight-overlay))) + (delete-overlay overlay))) + +(defun macrostep-collapse-overlays-in (start end) + "Collapse all macrostepper overlays that are strictly between START and END. + +Will not collapse overlays that begin at START and end at END." + (dolist (ol (overlays-in start end)) + (when (and (overlay-buffer ol) ; collapsing may delete other overlays + (> (overlay-start ol) start) + (< (overlay-end ol) end) + (overlay-get ol 'macrostep-original-text)) + (macrostep-collapse-overlay ol t)))) + + +;;; Emacs Lisp implementation + +(defun macrostep-sexp-bounds () + "Find the bounds of the macro form nearest point. + +If point is not before an open-paren, moves up to the nearest +enclosing list. If the form at point is not a macro call, +attempts to move forward to the next macro form as determined by +`macrostep-macro-form-p-function'. + +Returns a cons of buffer positions, (START . END)." + (save-excursion + (if (not (looking-at "[(`]")) + (backward-up-list 1)) + (if (equal (char-before) ?`) + (backward-char)) + (let ((sexp (funcall macrostep-sexp-at-point-function)) + (env (funcall macrostep-environment-at-point-function))) + ;; If this isn't a macro form, try to find the next one in the buffer + (unless (funcall macrostep-macro-form-p-function sexp env) + (condition-case nil + (macrostep-next-macro) + (error + (if (consp sexp) + (error "(%s ...) is not a macro form" (car sexp)) + (error "Text at point is not a macro form")))))) + (cons (point) (scan-sexps (point) 1)))) + +(defun macrostep-sexp-at-point (&rest _ignore) + "Return the sexp near point for purposes of macro-stepper expansion. + +If the sexp near point is part of a macro expansion, returns the +saved text of the macro expansion, and does not read from the +buffer. This preserves uninterned symbols in the macro +expansion, so that they can be fontified consistently. (See +`macrostep-print-sexp'.)" + (or (get-text-property (point) 'macrostep-expanded-text) + (sexp-at-point))) + +(defun macrostep-macro-form-p (form environment) + "Return non-nil if FORM would be evaluated via macro expansion. +This is considered within ENVIRONMENT. + +If FORM is an invocation of a macro defined by `defmacro' or an +enclosing `cl-macrolet' form, return the symbol `macro'. + +If `macrostep-expand-compiler-macros' is non-nil and FORM is a +call to a function with a compiler macro, return the symbol +`compiler-macro'. + +Otherwise, return nil." + (car (macrostep--macro-form-info form environment t))) + +(defun macrostep--macro-form-info (form environment &optional inhibit-autoload) + "Return information about macro definitions that apply to FORM. + +If no macros are involved in the evaluation of FORM within +ENVIRONMENT, returns nil. Otherwise, returns a cons (TYPE +. DEFINITION). + +If FORM would be evaluated by a macro defined by `defmacro', +`cl-macrolet', etc., TYPE is the symbol `macro' and DEFINITION is +the macro definition, as a function. + +If `macrostep-expand-compiler-macros' is non-nil and FORM would +be compiled using a compiler macro, TYPE is the symbol +`compiler-macro' and DEFINITION is the function that implements +the compiler macro. + +If FORM is an invocation of an autoloaded macro, the behavior +depends on the value of INHIBIT-AUTOLOAD. If INHIBIT-AUTOLOAD is +nil, the file containing the macro definition will be loaded +using `load-library' and the macro definition returned as normal. +If INHIBIT-AUTOLOAD is non-nil, no files will be loaded, and the +value of DEFINITION in the result will be nil." + (if (not (and (consp form) + (symbolp (car form)))) + `(nil . nil) + (let* ((head (car form)) + (local-definition (assoc-default head environment #'eq))) + (if local-definition + `(macro . ,local-definition) + (let ((compiler-macro-definition + (and macrostep-expand-compiler-macros + (or (get head 'compiler-macro) + (get head 'cl-compiler-macro))))) + (if (and compiler-macro-definition + (not (eq form + (apply compiler-macro-definition form (cdr form))))) + `(compiler-macro . ,compiler-macro-definition) + (condition-case nil + (let ((fun (indirect-function head))) + (cl-case (car-safe fun) + ((macro) + `(macro . ,(cdr fun))) + ((autoload) + (when (memq (nth 4 fun) '(macro t)) + (if inhibit-autoload + `(macro . nil) + (load-library (nth 1 fun)) + (macrostep--macro-form-info form nil)))) + (t + `(nil . nil)))) + (void-function nil)))))))) + +(defun macrostep-expand-1 (form environment) + "Return result of macro-expanding by exactly one step the top level of FORM. +This is done within ENVIRONMENT. + +Unlike `macroexpand', this function does not continue macro +expansion until a non-macro-call results." + (cl-destructuring-bind (type . definition) + (macrostep--macro-form-info form environment) + (cl-ecase type + ((nil) + form) + ((macro) + (apply definition (cdr form))) + ((compiler-macro) + (let ((expansion (apply definition form (cdr form)))) + (if (equal form expansion) + (error "Form left unchanged by compiler macro") + expansion)))))) + +(put 'macrostep-grab-environment-failed 'error-conditions + '(macrostep-grab-environment-failed error)) + +(defun macrostep-environment-at-point () + "Return the local macro-expansion environment at point, if any. + +The local environment includes macros declared by any `macrolet' +or `cl-macrolet' forms surrounding point, as well as by any macro +forms which expand into a `macrolet'. + +The return value is an alist of elements (NAME . FUNCTION), where +NAME is the symbol locally bound to the macro and FUNCTION is the +lambda expression that returns its expansion." + ;; If point is on a macro form within an expansion inserted by + ;; `macrostep-print-sexp', a local environment may have been + ;; previously saved as a text property. + (let ((saved-environment + (get-text-property (point) 'macrostep-environment))) + (if saved-environment + saved-environment + ;; Otherwise, we (ab)use the macro-expander to return the + ;; environment at point. If point is not at an evaluated + ;; position in the containing form, + ;; `macrostep-environment-at-point-1' will raise an error, and + ;; we back up progressively through the containing forms until + ;; it succeeds. + (save-excursion + (catch 'done + (while t + (condition-case nil + (throw 'done (macrostep-environment-at-point-1)) + (macrostep-grab-environment-failed + (condition-case nil + (backward-sexp) + (scan-error (backward-up-list))))))))))) + +(defun macrostep-environment-at-point-1 () + "Attempt to extract the macro environment that would be active at point. + +If point is not at an evaluated position within the containing +form, raise an error." + ;; Macro environments are extracted using Emacs Lisp's builtin + ;; macro-expansion machinery. The form containing point is copied + ;; to a temporary buffer, and a call to + ;; `--macrostep-grab-environment--' is inserted at point. This + ;; altered form is then fully macro-expanded, in an environment + ;; where `--macrostep-grab-environment--' is defined as a macro + ;; which throws the environment to a uniquely-generated tag. + (let* ((point-at-top-level + (save-excursion + (while (ignore-errors (backward-up-list) t)) + (point))) + (enclosing-form + (buffer-substring point-at-top-level + (scan-sexps point-at-top-level 1))) + (position (- (point) point-at-top-level)) + (tag (make-symbol "macrostep-grab-environment-tag")) + (grab-environment '--macrostep-grab-environment--)) + (if (= position 0) + nil + (with-temp-buffer + (emacs-lisp-mode) + (insert enclosing-form) + (goto-char (+ (point-min) position)) + (prin1 `(,grab-environment) (current-buffer)) + (let ((form (read (copy-marker (point-min))))) + (catch tag + (cl-letf (((symbol-function #'message) (symbol-function #'format))) + (with-no-warnings + (ignore-errors + (macroexpand-all + `(cl-macrolet ((,grab-environment (&environment env) + (throw ',tag env))) + ,form))))) + (signal 'macrostep-grab-environment-failed nil))))))) + +(defun macrostep-collect-macro-forms (form &optional environment) + "Identify sub-forms of FORM which undergo macro-expansion. + +FORM is an Emacs Lisp form. ENVIRONMENT is a local environment of +macro definitions. + +The return value is a list of two elements, (MACRO-FORM-ALIST +COMPILER-MACRO-FORMS). + +MACRO-FORM-ALIST is an alist of elements of the form (SUBFORM +. ENVIRONMENT), where SUBFORM is a form which undergoes +macro-expansion in the course of expanding FORM, and ENVIRONMENT +is the local macro environment in force when it is expanded. + +COMPILER-MACRO-FORMS is a list of subforms which would be +compiled using a compiler macro. Since there is no standard way +to provide a local compiler-macro definition in Emacs Lisp, no +corresponding local environments are collected for these. + +Forms and environments are extracted from FORM by instrumenting +Emacs's builtin `macroexpand' function and calling +`macroexpand-all'." + (let* ((macro-form-alist '()) + (compiler-macro-forms '()) + (override (lambda (real-macroexpand form environment &rest args) + (let ((expansion + (apply real-macroexpand form environment args))) + (cond ((not (eq expansion form)) + (setq macro-form-alist + (cons (cons form environment) + macro-form-alist))) + ((and (consp form) + (symbolp (car form)) + macrostep-expand-compiler-macros + (not (eq form + (cl-compiler-macroexpand form)))) + (setq compiler-macro-forms + (cons form compiler-macro-forms)))) + expansion)))) + (cl-macrolet ((with-override (fn &rest body) + `(cl-letf (((symbol-function ,fn) + (apply-partially override (indirect-function ,fn)))) + ,@body)) + (with-macroexpand-1 (&rest body) + (if (< emacs-major-version 30) + `(progn ,@body) `(with-override #'macroexpand-1 ,@body))) + (with-macroexpand (&rest body) + `(with-override #'macroexpand ,@body))) + (with-macroexpand-1 + (with-macroexpand + (ignore-errors + (macroexpand-all form environment))))) + (list macro-form-alist compiler-macro-forms))) + +(defvar macrostep-collected-macro-form-alist nil + "An alist of macro forms and environments. +Controls the printing of sub-forms in `macrostep-print-sexp'.") + +(defvar macrostep-collected-compiler-macro-forms nil + "A list of compiler-macro forms to be highlighted in `macrostep-print-sexp'.") + +(defun macrostep-pp (sexp environment) + "Pretty-print SEXP, fontifying macro forms and uninterned symbols. +This is done within ENVIRONMENT." + (cl-destructuring-bind + (macrostep-collected-macro-form-alist + macrostep-collected-compiler-macro-forms) + (macrostep-collect-macro-forms sexp environment) + (let ((print-quoted t)) + (macrostep-print-sexp sexp) + ;; Point is now after the expanded form; pretty-print it + (save-restriction + (narrow-to-region (scan-sexps (point) -1) (point)) + (save-excursion + (pp-buffer) + ;; Remove the extra newline inserted by pp-buffer + (goto-char (point-max)) + (delete-region + (point) + (save-excursion (skip-chars-backward " \t\n") (point)))) + ;; Indent the newly-inserted form in context + (widen) + (save-excursion + (backward-sexp) + (indent-sexp)))))) + +;; This must be defined before `macrostep-print-sexp': +(defmacro macrostep-propertize (form &rest plist) + "Evaluate FORM, applying syntax properties in PLIST to any inserted text." + (declare (indent 1) + (debug (&rest form))) + (let ((start (make-symbol "start"))) + `(let ((,start (point))) + (prog1 + ,form + ,@(cl-loop for (key value) on plist by #'cddr + collect `(put-text-property ,start (point) + ,key ,value)))))) + +(defun macrostep-print-sexp (sexp) + "Insert SEXP like `print', fontifying macro forms and uninterned symbols. + +Fontifies uninterned symbols and macro forms using +`font-lock-face' property, and saves the actual text of SEXP's +sub-forms as the `macrostep-expanded-text' text property so that +any uninterned symbols can be reused in macro expansions of the +sub-forms. See also `macrostep-sexp-at-point'. + +Macro and compiler-macro forms within SEXP are identified by +comparison with the `macrostep-collected-macro-form-alist' and +`macrostep-collected-compiler-macro-forms' variables, which +should be dynamically let-bound around calls to this function." + (cond + ((symbolp sexp) + ;; Fontify gensyms + (if (not (eq sexp (intern-soft (symbol-name sexp)))) + (macrostep-propertize + (prin1 sexp (current-buffer)) + 'font-lock-face (macrostep-get-gensym-face sexp)) + ;; Print other symbols as normal + (prin1 sexp (current-buffer)))) + + ((listp sexp) + ;; Print quoted and quasiquoted forms nicely. + (let ((head (car sexp))) + (cond ((and (eq head 'quote) ; quote + (= (length sexp) 2)) + (insert "'") + (macrostep-print-sexp (cadr sexp))) + + ((and (eq head '\`) ; backquote + (= (length sexp) 2)) + (if (assq sexp macrostep-collected-macro-form-alist) + (macrostep-propertize + (insert "`") + 'macrostep-expanded-text sexp + 'macrostep-macro-start t + 'font-lock-face 'macrostep-macro-face) + (insert "`")) + (macrostep-print-sexp (cadr sexp))) + + ((and (memq head '(\, \,@)) ; unquote + (= (length sexp) 2)) + (princ head (current-buffer)) + (macrostep-print-sexp (cadr sexp))) + + (t ; other list form + (cl-destructuring-bind (macro? . environment) + (or (assq sexp macrostep-collected-macro-form-alist) + '(nil . nil)) + (let + ((compiler-macro? + (memq sexp macrostep-collected-compiler-macro-forms))) + (if (or macro? compiler-macro?) + (progn + ;; Save the real expansion as a text property on the + ;; opening paren + (macrostep-propertize + (insert "(") + 'macrostep-macro-start t + 'macrostep-expanded-text sexp + 'macrostep-environment environment) + ;; Fontify the head of the macro + (macrostep-propertize + (macrostep-print-sexp head) + 'font-lock-face + (if macro? + 'macrostep-macro-face + 'macrostep-compiler-macro-face))) + ;; Not a macro form + (insert "(") + (macrostep-print-sexp head)))) + + ;; Print remaining list elements + (setq sexp (cdr sexp)) + (when sexp (insert " ")) + (while sexp + (if (listp sexp) + (progn + (macrostep-print-sexp (car sexp)) + (when (cdr sexp) (insert " ")) + (setq sexp (cdr sexp))) + ;; Print tail of dotted list + (insert ". ") + (macrostep-print-sexp sexp) + (setq sexp nil))) + (insert ")"))))) + + ;; Print everything except symbols and lists as normal + (t (prin1 sexp (current-buffer))))) + +(defun macrostep-get-gensym-face (symbol) + "Return the face to use in fontifying SYMBOL in printed macro expansions. + +All symbols introduced in the same level of macro expansion are +fontified using the same face (modulo the number of faces; see +`macrostep-gensym-faces')." + (or (get symbol 'macrostep-gensym-face) + (progn + (if (not macrostep-gensyms-this-level) + (setq macrostep-gensym-depth (1+ macrostep-gensym-depth) + macrostep-gensyms-this-level t)) + (let ((face (ring-ref macrostep-gensym-faces macrostep-gensym-depth))) + (put symbol 'macrostep-gensym-face face) + face)))) + + +(provide 'macrostep) +;;; macrostep.el ends here blob - f5f115bd45c200174e068eadd9a1e35d67ea8e23 (mode 644) blob + /dev/null --- elpa/macrostep-0.9.2.signed +++ /dev/null @@ -1 +0,0 @@ -Good signature from 066DAFCB81E42C40 GNU ELPA Signing Agent (2019) (trust undefined) created at 2023-05-18T23:10:03+0200 using RSA \ No newline at end of file blob - /dev/null blob + 61f3fd36ebd40cb4e4c5ee94d59a62a1f4c2db8f (mode 644) --- /dev/null +++ elpa/macrostep-0.9.4.signed @@ -0,0 +1 @@ +Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) (trust undefined) created at 2024-05-10T11:10:04+0200 using EDDSA \ No newline at end of file blob - c9805fa766993a0d5a7aa1ad1634045a9840f706 (mode 644) blob + /dev/null --- elpa/marginalia-1.5/.elpaignore +++ /dev/null @@ -1,2 +0,0 @@ -LICENSE -.github \ No newline at end of file blob - 8d4b0c3d1cbd8d4e385ff3692ae91230c39e4715 (mode 644) blob + /dev/null --- elpa/marginalia-1.5/CHANGELOG.org +++ /dev/null @@ -1,43 +0,0 @@ -#+title: marginalia.el - Changelog -#+author: Omar Antolín Camarena, Daniel Mendler -#+language: en - -* Version 1.5 (2023-12-27) - -- ~marginalia-annotate-bookmark~: Show location and more context. -- ~marginalia-annotate-tab~: Show tab index starting from one. - -* Version 1.4 (2023-12-01) - -- =marginalia-annotate-theme=: New annotator based on =marginalia-annotate-library=. -- =marginalia-remote-file-regexps=: New customization variable set to a list of - regexps matching remote paths, which should be excluded from file annotations. - -* Version 1.3 (2023-07-02) - -- =marginalia-classify-by-prompt=: Use case-insensitive matching. -- =marginalia-annotate-symbol=: Additional symbol classes. Use =M= for module - functions, =P= for primitives and =S= for special forms. -- =marginalia-annotate-symbol=: Add =symbol-file= column. -- =marginalia-cycle=: Add =completion-predicate= to display command only in - recursive minibuffers. - -* Version 1.2 (2023-04-17) - -- =marginalia-classify-by-command-name=: Resolve function aliases and use the name - of the original command to determine the completion category. - -* Version 1.1 (2023-02-17) - -- Require the =compat= library. -- Fix =marginalia-classify-by-prompt= such that it handles multiple brackets in - the prompt gracefully. -- Add =help-echo= properties to truncated annotations. The full string is shown on - mouse hover. -- Add =help-echo= to the symbol classes of =marginalia-annotate-symbol=. -- Add =help-echo= to file sizes showing the exact size in bytes. -- Add =help-echo= to file dates showing the exact date. - -* Version 1.0 (2022-12-22) - -- Start of changelog. blob - 08a74a2dcd89a6fb84f9bf8ad5b55e19d6c30d8c (mode 644) blob + /dev/null --- elpa/marginalia-1.5/README-elpa +++ /dev/null @@ -1,276 +0,0 @@ - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - MARGINALIA.EL - MARGINALIA IN THE MINIBUFFER - ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ - - -This package provides `marginalia-mode' which adds marginalia to the -minibuffer completions. [Marginalia] are marks or annotations placed at -the margin of the page of a book or in this case helpful colorful -annotations placed at the margin of the minibuffer for your completion -candidates. Marginalia can only add annotations to the completion -candidates. It cannot modify the appearance of the candidates -themselves, which are shown unaltered as supplied by the original -command. - -The annotations are added based on the completion category. For example -`find-file' reports the `file' category and `M-x' reports the `command' -category. You can cycle between more or less detailed annotators or even -disable the annotator with command `marginalia-cycle'. - -Table of Contents -───────────────── - -1. Configuration -2. Information shown by the annotators -3. Adding custom annotators or classifiers -4. Disabling annotators, builtin or lightweight annotators -5. Icons in the minibuffer -6. Contributions - - -[Marginalia] - - -1 Configuration -═══════════════ - - It is recommended to use Marginalia together with either the - [Vertico], [Mct], [Icomplete] or the default completion - UI. Furthermore Marginalia can be combined with [Embark] for action - support and [Consult], which provides many useful commands. - - ┌──── - │ ;; Enable rich annotations using the Marginalia package - │ (use-package marginalia - │ ;; Bind `marginalia-cycle' locally in the minibuffer. To make the binding - │ ;; available in the *Completions* buffer, add it to the - │ ;; `completion-list-mode-map'. - │ :bind (:map minibuffer-local-map - │ ("M-A" . marginalia-cycle)) - │ - │ ;; The :init section is always executed. - │ :init - │ - │ ;; Marginalia must be activated in the :init section of use-package such that - │ ;; the mode gets enabled right away. Note that this forces loading the - │ ;; package. - │ (marginalia-mode)) - └──── - - -[Vertico] - -[Mct] - -[Icomplete] - - -[Embark] - -[Consult] - - -2 Information shown by the annotators -═════════════════════════════════════ - - In general, to learn more about what different annotations mean, a - good starting point is to look at `marginalia-annotator-registry', and - follow up to the annotation function of the category you are - interested in. - - For example the annotations for Elisp symbols include their symbol - class - `v' for variable, `f' for function, `c' for command, etc. For - more information on what the different classifications mean, see the - docstring of `marginalia--symbol-class'. - - -3 Adding custom annotators or classifiers -═════════════════════════════════════════ - - *IMPORTANT NOTICE FOR PACKAGE AUTHORS*: The intention of the - Marginalia package is to give the user means to overwrite completion - categories and to add custom annotators for existing commands in their - user configuration. *Marginalia is a user facing package and is not - intended to be used as a library*. Therefore Marginalia does not - expose library functions as part of its public API. If you add your - own completion commands to your package we recommend to specify an - `annotation-function' or an `affixation-function', avoiding the - Marginalia dependency this way. The `annotation-function' and - `affixation-function' are documented in the [Elisp manual]. If you use - `consult--read', you can specify an `:annotate' keyword - argument. There is an exception to our recommendation: If you want to - implement annotations for an existing package `hypothetic.el', which - does not have annotations and where annotations cannot be added, then - the creation of a `marginalia-hypothetic.el' package is a good idea, - since Marginalia provides the facilities to enhance existing commands - from the outside. If you have questions feel free to ask on the - Marginalia issue tracker. - - Commands that support minibuffer completion use a completion table of - all the available candidates. Candidates are associated with a - *category* such as `command', `file', `face', or `variable' depending - on what the candidates are. Based on the category of the candidates, - Marginalia selects an *annotator* to generate annotations for display - for each candidate. - - Unfortunately, not all commands (including Emacs' builtin ones) - specify the category of their candidates. To compensate for this - shortcoming, Marginalia hooks into the Emacs completion framework and - runs the *classifiers* listed in the variable - `marginalia-classifiers', which use the command's prompt or other - properties of the candidates to specify the completion category. - - For example, the `marginalia-classify-by-prompt' classifier checks the - minibuffer prompt against regexps listed in the - `marginalia-prompt-categories' alist to determine a category. The - following is already included but would be a way to assign the - category `face' to all candidates from commands with prompts that - include the word "face". - - ┌──── - │ (add-to-list 'marginalia-prompt-categories '("\\" . face)) - └──── - - The `marginalia-classify-by-command-name' classifier uses the alist - `marginalia-command-categories' to specify the completion category - based on the command name. This is particularly useful if the prompt - classifier yields a false positive. - - Completion categories are also important for [Embark], which - associates actions based on the completion category and benefits from - Marginalia's classifiers. - - Once the category of the candidates is known, Marginalia looks in the - `marginalia-annotator-registry' to find the associated annotator to - use. An annotator is a function that takes a completion candidate - string as an argument and returns an annotation string to be displayed - after the candidate in the minibuffer. More than one annotator can be - assigned to each each category, displaying more, less or different - information. Use the `marginalia-cycle' command to cycle between the - annotations of different annotators defined for the current category. - - Here's an example of a basic face annotator: - - ┌──── - │ (defun my-face-annotator (cand) - │ (when-let (sym (intern-soft cand)) - │ (concat (propertize " " 'display '(space :align-to center)) - │ (propertize "The quick brown fox jumps over the lazy dog" 'face sym)))) - └──── - - Look at Marginalia's various annotators for examples of formatting - annotations. In particular, the helper function `marginalia--fields' - can be used to format information into columns. - - After defining a new annotator, associate it with a category in the - annotator registry as follows: - - ┌──── - │ (add-to-list 'marginalia-annotator-registry - │ '(face my-face-annotator marginalia-annotate-face builtin none)) - └──── - - This makes the `my-face-annotator' the first of four annotators for - the face category. The others are the annotator provided by Marginalia - (`marginalia-annotate-face'), the `builtin' annotator as defined by - Emacs and the `none' annotator, which disables the annotations. With - this setting, after invoking `M-x describe-face RET' you can cycle - between all of these annotators using `marginalia-cycle'. - - -[Elisp manual] - - -[Embark] - - -4 Disabling annotators, builtin or lightweight annotators -═════════════════════════════════════════════════════════ - - Marginalia activates rich annotators by default. Depending on your - preference you may want to use the builtin annotators or even no - annotators by default and only activate the annotators on demand by - invoking `marginalia-cycle'. - - In order to use the builtin annotators by default, you can use the - following command. Replace `builtin' by `none' to disable annotators - by default. - - ┌──── - │ (defun marginalia-use-builtin () - │ (interactive) - │ (mapc - │ (lambda (x) - │ (setcdr x (cons 'builtin (remq 'builtin (cdr x))))) - │ marginalia-annotator-registry)) - └──── - - If a completion category supports two annotators, you can toggle - between those using this command. - - ┌──── - │ (defun marginalia-toggle () - │ (interactive) - │ (mapc - │ (lambda (x) - │ (setcdr x (append (reverse (remq 'none - │ (remq 'builtin (cdr x)))) - │ '(builtin none)))) - │ marginalia-annotator-registry)) - └──── - - After cycling the annotators you may want to automatically save the - configuration. This can be achieved using an advice which calls - `customize-save-variable'. - - ┌──── - │ (advice-add #'marginalia-cycle :after - │ (lambda () - │ (let ((inhibit-message t)) - │ (customize-save-variable 'marginalia-annotator-registry - │ marginalia-annotator-registry)))) - └──── - - In order to disable an annotator permanently, the - `marginalia-annotator-registry' can be modified. For example if you - prefer to never see file annotations, you can delete all file - annotators from the registry. - - ┌──── - │ (setq marginalia-annotator-registry - │ (assq-delete-all 'file marginalia-annotator-registry)) - └──── - - -5 Icons in the minibuffer -═════════════════════════ - - Icons in the minibuffer completion UI are a commonly requested - feature. Marginalia focuses on text annotations only. The following - packages are compatible with Marginalia and use special fonts to add - icons in front of completion candidates. There also exist related - packages to enhance Dired, Ibuffer and other modes with icons - consistently. - - • [all-the-icons-completion]: Relies on the `all-the-icons.el' package - which configures multiple icon fonts. - • [nerd-icons-completion]: Relies on patched fonts including - icons. This package works even in the terminal where only a single - font can be used. - - -[all-the-icons-completion] - - -[nerd-icons-completion] - - - -6 Contributions -═══════════════ - - Since this package is part of [GNU ELPA] contributions require a - copyright assignment to the FSF. - - -[GNU ELPA] blob - c83bc4ce76d15f9ce7a72e854d9895561f0c2e04 (mode 644) blob + /dev/null --- elpa/marginalia-1.5/README.org +++ /dev/null @@ -1,222 +0,0 @@ -#+title: marginalia.el - Marginalia in the minibuffer -#+author: Omar Antolín Camarena, Daniel Mendler -#+language: en -#+export_file_name: marginalia.texi -#+texinfo_dir_category: Emacs misc features -#+texinfo_dir_title: Marginalia: (marginalia). -#+texinfo_dir_desc: Marginalia in the minibuffer - -#+html: GNU Emacs -#+html: GNU ELPA -#+html: GNU-devel ELPA -#+html: MELPA -#+html: MELPA Stable - -#+html: - -This package provides =marginalia-mode= which adds marginalia to the minibuffer -completions. [[https://en.wikipedia.org/wiki/Marginalia][Marginalia]] are marks or annotations placed at the margin of the -page of a book or in this case helpful colorful annotations placed at the margin -of the minibuffer for your completion candidates. Marginalia can only add -annotations to the completion candidates. It cannot modify the appearance of the -candidates themselves, which are shown unaltered as supplied by the original -command. - -The annotations are added based on the completion category. For example -=find-file= reports the =file= category and =M-x= reports the =command= category. You -can cycle between more or less detailed annotators or even disable the annotator -with command =marginalia-cycle=. - -#+html: - -#+toc: headlines 8 - -* Configuration - -It is recommended to use Marginalia together with either the [[https://github.com/minad/vertico][Vertico]], [[https://github.com/protesilaos/mct][Mct]], -[[https://www.gnu.org/software/emacs/manual/html_node/emacs/Icomplete.html][Icomplete]] or the default completion UI. Furthermore Marginalia can be combined -with [[https://github.com/oantolin/embark][Embark]] for action support and [[https://github.com/minad/consult][Consult]], which provides many useful commands. - -#+begin_src emacs-lisp -;; Enable rich annotations using the Marginalia package -(use-package marginalia - ;; Bind `marginalia-cycle' locally in the minibuffer. To make the binding - ;; available in the *Completions* buffer, add it to the - ;; `completion-list-mode-map'. - :bind (:map minibuffer-local-map - ("M-A" . marginalia-cycle)) - - ;; The :init section is always executed. - :init - - ;; Marginalia must be activated in the :init section of use-package such that - ;; the mode gets enabled right away. Note that this forces loading the - ;; package. - (marginalia-mode)) -#+end_src - -* Information shown by the annotators - -In general, to learn more about what different annotations mean, a good starting -point is to look at ~marginalia-annotator-registry~, and follow up to the -annotation function of the category you are interested in. - -For example the annotations for Elisp symbols include their symbol class - =v= for -variable, =f= for function, =c= for command, etc. For more information on what the -different classifications mean, see the docstring of ~marginalia--symbol-class~. - -* Adding custom annotators or classifiers - -*IMPORTANT NOTICE FOR PACKAGE AUTHORS*: The intention of the Marginalia package is -to give the user means to overwrite completion categories and to add custom -annotators for existing commands in their user configuration. *Marginalia is a -user facing package and is not intended to be used as a library*. Therefore -Marginalia does not expose library functions as part of its public API. If you -add your own completion commands to your package we recommend to specify an -=annotation-function= or an =affixation-function=, avoiding the Marginalia -dependency this way. The =annotation-function= and =affixation-function= are -documented in the [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Completion.html][Elisp manual]]. If you use =consult--read=, you can specify an -=:annotate= keyword argument. There is an exception to our recommendation: If you -want to implement annotations for an existing package =hypothetic.el=, which does -not have annotations and where annotations cannot be added, then the creation of -a =marginalia-hypothetic.el= package is a good idea, since Marginalia provides the -facilities to enhance existing commands from the outside. If you have questions -feel free to ask on the Marginalia issue tracker. - -Commands that support minibuffer completion use a completion table of all the -available candidates. Candidates are associated with a *category* such as =command=, -=file=, =face=, or =variable= depending on what the candidates are. Based on the -category of the candidates, Marginalia selects an *annotator* to generate -annotations for display for each candidate. - -Unfortunately, not all commands (including Emacs' builtin ones) specify the -category of their candidates. To compensate for this shortcoming, Marginalia -hooks into the Emacs completion framework and runs the *classifiers* listed in the -variable =marginalia-classifiers=, which use the command's prompt or other -properties of the candidates to specify the completion category. - -For example, the =marginalia-classify-by-prompt= classifier checks the minibuffer -prompt against regexps listed in the =marginalia-prompt-categories= alist to -determine a category. The following is already included but would be a way to -assign the category =face= to all candidates from commands with prompts that -include the word "face". - -#+begin_src emacs-lisp - (add-to-list 'marginalia-prompt-categories '("\\" . face)) -#+end_src - -The =marginalia-classify-by-command-name= classifier uses the alist -=marginalia-command-categories= to specify the completion category based on the -command name. This is particularly useful if the prompt classifier yields a -false positive. - -Completion categories are also important for [[https://github.com/oantolin/embark][Embark]], which associates actions -based on the completion category and benefits from Marginalia's classifiers. - -Once the category of the candidates is known, Marginalia looks in the -=marginalia-annotator-registry= to find the associated annotator to use. An -annotator is a function that takes a completion candidate string as an argument -and returns an annotation string to be displayed after the candidate in the -minibuffer. More than one annotator can be assigned to each each category, -displaying more, less or different information. Use the =marginalia-cycle= command -to cycle between the annotations of different annotators defined for the current -category. - -Here's an example of a basic face annotator: - -#+begin_src emacs-lisp - (defun my-face-annotator (cand) - (when-let (sym (intern-soft cand)) - (concat (propertize " " 'display '(space :align-to center)) - (propertize "The quick brown fox jumps over the lazy dog" 'face sym)))) -#+end_src - -Look at Marginalia's various annotators for examples of formatting annotations. -In particular, the helper function =marginalia--fields= can be used to format -information into columns. - -After defining a new annotator, associate it with a category in the annotator -registry as follows: - -#+begin_src emacs-lisp - (add-to-list 'marginalia-annotator-registry - '(face my-face-annotator marginalia-annotate-face builtin none)) -#+end_src - -This makes the =my-face-annotator= the first of four annotators for the face -category. The others are the annotator provided by Marginalia -(=marginalia-annotate-face=), the =builtin= annotator as defined by Emacs and the -=none= annotator, which disables the annotations. With this setting, after -invoking =M-x describe-face RET= you can cycle between all of these annotators -using =marginalia-cycle=. - -* Disabling annotators, builtin or lightweight annotators - -Marginalia activates rich annotators by default. Depending on your preference -you may want to use the builtin annotators or even no annotators by default and -only activate the annotators on demand by invoking ~marginalia-cycle~. - -In order to use the builtin annotators by default, you can use the following -command. Replace =builtin= by =none= to disable annotators by default. - -#+begin_src emacs-lisp - (defun marginalia-use-builtin () - (interactive) - (mapc - (lambda (x) - (setcdr x (cons 'builtin (remq 'builtin (cdr x))))) - marginalia-annotator-registry)) -#+end_src - -If a completion category supports two annotators, you can toggle between -those using this command. - -#+begin_src emacs-lisp - (defun marginalia-toggle () - (interactive) - (mapc - (lambda (x) - (setcdr x (append (reverse (remq 'none - (remq 'builtin (cdr x)))) - '(builtin none)))) - marginalia-annotator-registry)) -#+end_src - -After cycling the annotators you may want to automatically save the -configuration. This can be achieved using an advice which calls -~customize-save-variable~. - -#+begin_src emacs-lisp - (advice-add #'marginalia-cycle :after - (lambda () - (let ((inhibit-message t)) - (customize-save-variable 'marginalia-annotator-registry - marginalia-annotator-registry)))) -#+end_src - -In order to disable an annotator permanently, the ~marginalia-annotator-registry~ -can be modified. For example if you prefer to never see file annotations, you -can delete all file annotators from the registry. - -#+begin_src emacs-lisp - (setq marginalia-annotator-registry - (assq-delete-all 'file marginalia-annotator-registry)) -#+end_src - -* Icons in the minibuffer - -Icons in the minibuffer completion UI are a commonly requested feature. -Marginalia focuses on text annotations only. The following packages are -compatible with Marginalia and use special fonts to add icons in front of -completion candidates. There also exist related packages to enhance Dired, -Ibuffer and other modes with icons consistently. - -- [[https://github.com/iyefrat/all-the-icons-completion][all-the-icons-completion]]: Relies on the =all-the-icons.el= package which - configures multiple icon fonts. -- [[https://github.com/rainstormstudio/nerd-icons-completion][nerd-icons-completion]]: Relies on patched fonts including icons. This package - works even in the terminal where only a single font can be used. - -* Contributions - -Since this package is part of [[https://elpa.gnu.org/packages/marginalia.html][GNU ELPA]] contributions require a copyright -assignment to the FSF. blob - 1a17acbe202dc8c9ba6ce8ec3ace936c639a3a60 (mode 644) blob + /dev/null --- elpa/marginalia-1.5/dir +++ /dev/null @@ -1,18 +0,0 @@ -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 -* Marginalia: (marginalia). Marginalia in the minibuffer. blob - 6e9e8e65088e90220f855b618dc5891d4ba49765 (mode 644) blob + /dev/null --- elpa/marginalia-1.5/marginalia-autoloads.el +++ /dev/null @@ -1,56 +0,0 @@ -;;; marginalia-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 marginalia.el - -(defvar marginalia-mode nil "\ -Non-nil if Marginalia mode is enabled. -See the `marginalia-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 `marginalia-mode'.") -(custom-autoload 'marginalia-mode "marginalia" nil) -(autoload 'marginalia-mode "marginalia" "\ -Annotate completion candidates with richer information. - -This is a global minor mode. If called interactively, toggle the -`Marginalia 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 \\='marginalia-mode)'. - -The mode's hook is called both when the mode is enabled and when -it is disabled. - -(fn &optional ARG)" t) -(autoload 'marginalia-cycle "marginalia" "\ -Cycle between annotators in `marginalia-annotator-registry'." t) -(register-definition-prefixes "marginalia" '("marginalia-")) - -;;; End of scraped data - -(provide 'marginalia-autoloads) - -;; Local Variables: -;; version-control: never -;; no-byte-compile: t -;; no-update-autoloads: t -;; no-native-compile: t -;; coding: utf-8-emacs-unix -;; End: - -;;; marginalia-autoloads.el ends here blob - 8dd96ab7e595b3710889bccf24c63262e7024323 (mode 644) blob + /dev/null --- elpa/marginalia-1.5/marginalia-pkg.el +++ /dev/null @@ -1,2 +0,0 @@ -;; Generated package description from marginalia.el -*- no-byte-compile: t -*- -(define-package "marginalia" "1.5" "Enrich existing commands with completion annotations" '((emacs "27.1") (compat "29.1.4.4")) :commit "98f6e58c12d57283bd7c1cb241664c966dc38ac3" :authors '(("Omar Antolín Camarena" . "omar@matem.unam.mx") ("Daniel Mendler" . "mail@daniel-mendler.de")) :maintainer '(("Omar Antolín Camarena" . "omar@matem.unam.mx") ("Daniel Mendler" . "mail@daniel-mendler.de")) :keywords '("docs" "help" "matching" "completion") :url "https://github.com/minad/marginalia") blob - 5022094fa6d9700226c79c7b11dd0634d46efb3b (mode 644) blob + /dev/null --- elpa/marginalia-1.5/marginalia.el +++ /dev/null @@ -1,1356 +0,0 @@ -;;; marginalia.el --- Enrich existing commands with completion annotations -*- lexical-binding: t -*- - -;; Copyright (C) 2021-2023 Free Software Foundation, Inc. - -;; Author: Omar Antolín Camarena , Daniel Mendler -;; Maintainer: Omar Antolín Camarena , Daniel Mendler -;; Created: 2020 -;; Version: 1.5 -;; Package-Requires: ((emacs "27.1") (compat "29.1.4.4")) -;; Homepage: https://github.com/minad/marginalia -;; Keywords: docs, help, matching, completion - -;; This file is 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: - -;; Enrich existing commands with completion annotations - -;;; Code: - -(require 'compat) -(eval-when-compile - (require 'subr-x) - (require 'cl-lib)) - -;;;; Customization - -(defgroup marginalia nil - "Enrich existing commands with completion annotations." - :link '(info-link :tag "Info Manual" "(marginalia)") - :link '(url-link :tag "Homepage" "https://github.com/minad/marginalia") - :link '(emacs-library-link :tag "Library Source" "marginalia.el") - :group 'help - :group 'docs - :group 'minibuffer - :prefix "marginalia-") - -(defcustom marginalia-field-width 80 - "Maximum truncation width of annotation fields. - -This value is adjusted depending on the `window-width'." - :type 'natnum) - -(defcustom marginalia-separator " " - "Annotation field separator." - :type 'string) - -(defcustom marginalia-align 'left - "Alignment of the annotations." - :type '(choice (const :tag "Left" left) - (const :tag "Center" center) - (const :tag "Right" right))) - -(defcustom marginalia-align-offset 0 - "Additional offset added to the alignment." - :type 'natnum) - -(defcustom marginalia-max-relative-age (* 60 60 24 14) - "Maximum relative age in seconds displayed by the file annotator. - -Set to `most-positive-fixnum' to always use a relative age, or 0 to never show -a relative age." - :type 'natnum) - -(defcustom marginalia-remote-file-regexps - '("\\`/\\([^/|:]+\\):") ;; Tramp path - "List of remote file regexps where the files should not be annotated. - -The first match group is displayed instead of the detailed file -attribute information. For Tramp paths, the protocol is -displayed instead." - :type '(repeat regexp)) - -(defcustom marginalia-annotator-registry - (mapcar - (lambda (x) (append x '(builtin none))) - '((command marginalia-annotate-command marginalia-annotate-binding) - (embark-keybinding marginalia-annotate-embark-keybinding) - (customize-group marginalia-annotate-customize-group) - (variable marginalia-annotate-variable) - (function marginalia-annotate-function) - (face marginalia-annotate-face) - (color marginalia-annotate-color) - (unicode-name marginalia-annotate-char) - (minor-mode marginalia-annotate-minor-mode) - (symbol marginalia-annotate-symbol) - (environment-variable marginalia-annotate-environment-variable) - (input-method marginalia-annotate-input-method) - (coding-system marginalia-annotate-coding-system) - (charset marginalia-annotate-charset) - (package marginalia-annotate-package) - (imenu marginalia-annotate-imenu) - (bookmark marginalia-annotate-bookmark) - (file marginalia-annotate-file) - (project-file marginalia-annotate-project-file) - (buffer marginalia-annotate-buffer) - (library marginalia-annotate-library) - (theme marginalia-annotate-theme) - (tab marginalia-annotate-tab) - (multi-category marginalia-annotate-multi-category))) - "Annotator function registry. -Associates completion categories with annotation functions. -Each annotation function must return a string, -which is appended to the completion candidate." - :type '(alist :key-type symbol :value-type (repeat symbol))) - -(defcustom marginalia-classifiers - '(marginalia-classify-by-command-name - marginalia-classify-original-category - marginalia-classify-by-prompt - marginalia-classify-symbol) - "List of functions to determine current completion category. -Each function should take no arguments and return a symbol -indicating the category, or nil to indicate it could not -determine it." - :type 'hook) - -(defcustom marginalia-prompt-categories - '(("\\" . customize-group) - ("\\" . command) - ("\\" . package) - ("\\" . bookmark) - ("\\" . color) - ("\\" . face) - ("\\" . environment-variable) - ("\\" . function) - ("\\" . variable) - ("\\" . input-method) - ("\\" . charset) - ("\\" . coding-system) - ("\\" . minor-mode) - ("\\" . kill-ring) - ("\\" . tab) - ("\\" . library) - ("\\" . theme)) - "Associates regexps to match against minibuffer prompts with categories. -The prompts are matched case-insensitively." - :type '(alist :key-type regexp :value-type symbol)) - -(defcustom marginalia-censor-variables - '("pass\\|auth-source-netrc-cache\\|auth-source-.*-nonce\\|api-?key") - "The value of variables matching any of these regular expressions is not shown. -This configuration variable is useful to hide variables which may -hold sensitive data, e.g., passwords. The variable names are -matched case-sensitively." - :type '(repeat (choice symbol regexp))) - -(defcustom marginalia-command-categories - '((imenu . imenu) - (recentf-open . file) - (where-is . command)) - "Associate commands with a completion category. -The value of `this-command' is used as key for the lookup." - :type '(alist :key-type symbol :value-type symbol)) - -(defgroup marginalia-faces nil - "Faces used by `marginalia-mode'." - :group 'marginalia - :group 'faces) - -(defface marginalia-key - '((t :inherit font-lock-keyword-face)) - "Face used to highlight keys.") - -(defface marginalia-type - '((t :inherit marginalia-key)) - "Face used to highlight types.") - -(defface marginalia-char - '((t :inherit marginalia-key)) - "Face used to highlight character annotations.") - -(defface marginalia-lighter - '((t :inherit marginalia-size)) - "Face used to highlight minor mode lighters.") - -(defface marginalia-on - '((t :inherit success)) - "Face used to signal enabled modes.") - -(defface marginalia-off - '((t :inherit error)) - "Face used to signal disabled modes.") - -(defface marginalia-documentation - '((t :inherit completions-annotations)) - "Face used to highlight documentation strings.") - -(defface marginalia-value - '((t :inherit marginalia-key)) - "Face used to highlight general variable values.") - -(defface marginalia-null - '((t :inherit font-lock-comment-face)) - "Face used to highlight null or unbound variable values.") - -(defface marginalia-true - '((t :inherit font-lock-builtin-face)) - "Face used to highlight true variable values.") - -(defface marginalia-function - '((t :inherit font-lock-function-name-face)) - "Face used to highlight function symbols.") - -(defface marginalia-symbol - '((t :inherit font-lock-type-face)) - "Face used to highlight general symbols.") - -(defface marginalia-list - '((t :inherit font-lock-constant-face)) - "Face used to highlight list expressions.") - -(defface marginalia-mode - '((t :inherit marginalia-key)) - "Face used to highlight buffer major modes.") - -(defface marginalia-date - '((t :inherit marginalia-key)) - "Face used to highlight dates.") - -(defface marginalia-version - '((t :inherit marginalia-number)) - "Face used to highlight package versions.") - -(defface marginalia-archive - '((t :inherit warning)) - "Face used to highlight package archives.") - -(defface marginalia-installed - '((t :inherit success)) - "Face used to highlight the status of packages.") - -(defface marginalia-size - '((t :inherit marginalia-number)) - "Face used to highlight sizes.") - -(defface marginalia-number - '((t :inherit font-lock-constant-face)) - "Face used to highlight numeric values.") - -(defface marginalia-string - '((t :inherit font-lock-string-face)) - "Face used to highlight string values.") - -(defface marginalia-modified - '((t :inherit font-lock-negation-char-face)) - "Face used to highlight buffer modification indicators.") - -(defface marginalia-file-name - '((t :inherit marginalia-documentation)) - "Face used to highlight file names.") - -(defface marginalia-file-owner - '((t :inherit font-lock-preprocessor-face)) - "Face used to highlight file owner and group names.") - -(defface marginalia-file-priv-no - '((t :inherit shadow)) - "Face used to highlight the no file privilege attribute.") - -(defface marginalia-file-priv-dir - '((t :inherit font-lock-keyword-face)) - "Face used to highlight the dir file privilege attribute.") - -(defface marginalia-file-priv-link - '((t :inherit font-lock-keyword-face)) - "Face used to highlight the link file privilege attribute.") - -(defface marginalia-file-priv-read - '((t :inherit font-lock-type-face)) - "Face used to highlight the read file privilege attribute.") - -(defface marginalia-file-priv-write - '((t :inherit font-lock-builtin-face)) - "Face used to highlight the write file privilege attribute.") - -(defface marginalia-file-priv-exec - '((t :inherit font-lock-function-name-face)) - "Face used to highlight the exec file privilege attribute.") - -(defface marginalia-file-priv-other - '((t :inherit font-lock-constant-face)) - "Face used to highlight some other file privilege attribute.") - -(defface marginalia-file-priv-rare - '((t :inherit font-lock-variable-name-face)) - "Face used to highlight a rare file privilege attribute.") - -;;;; Pre-declarations for external packages - -(declare-function bookmark-prop-get "bookmark") - -(declare-function project-current "project") - -(defvar package--builtins) -(defvar package-archive-contents) -(declare-function package--from-builtin "package") -(declare-function package-desc-archive "package") -(declare-function package-desc-status "package") -(declare-function package-desc-summary "package") -(declare-function package-desc-version "package") -(declare-function package-version-join "package") - -(declare-function color-rgb-to-hex "color") -(declare-function color-rgb-to-hsl "color") -(declare-function color-hsl-to-rgb "color") - -;;;; Marginalia mode - -(defvar marginalia--pangram "Cwm fjord bank glyphs vext quiz.") - -(defvar marginalia--bookmark-type-transforms - (let ((words (regexp-opt '("handle" "handler" "jump" "bookmark")))) - `((,(format "-+%s-+" words) . "-") - (,(format "\\`%s-+" words) . "") - (,(format "-%s\\'" words) . "") - ("\\`default\\'" . "File") - (".*" . ,#'capitalize))) - "List of bookmark type transformers. -Relying on this mechanism is discouraged in favor of the -`bookmark-handler-type' property. The function names are matched -case-sensitively.") - -(defvar marginalia--cand-width-step 10 - "Round candidate width.") - -(defvar-local marginalia--cand-width-max 20 - "Maximum width of candidates.") - -(defvar marginalia--fontified-file-modes nil - "List of fontified file modes.") - -(defvar-local marginalia--cache nil - "The cache, pair of list and hashtable.") - -(defvar marginalia--cache-size 100 - "Size of the cache, set to 0 to disable the cache. -Disabling the cache is useful on non-incremental UIs like default completion or -for performance profiling of the annotators.") - -(defvar-local marginalia--command nil - "Last command symbol saved in order to allow annotations.") - -(defvar-local marginalia--base-position 0 - "Last completion base position saved to get full file paths.") - -(defvar marginalia--metadata nil - "Completion metadata from the current completion.") - -(defvar marginalia--ellipsis nil) -(defun marginalia--ellipsis () - "Return ellipsis." - (with-memoization marginalia--ellipsis - (cond - ((bound-and-true-p truncate-string-ellipsis)) - ((char-displayable-p ?…) "…") - ("...")))) - -(defun marginalia--truncate (str width) - "Truncate string STR to WIDTH." - (when (floatp width) (setq width (round (* width marginalia-field-width)))) - (when-let (pos (string-search "\n" str)) - (setq str (substring str 0 pos))) - (let* ((face (and (not (equal str "")) - (get-text-property (1- (length str)) 'face str))) - (ell (if face - (propertize (marginalia--ellipsis) 'face face) - (marginalia--ellipsis))) - (trunc - (if (< width 0) - (nreverse (truncate-string-to-width (reverse str) (- width) 0 ?\s ell)) - (truncate-string-to-width str width 0 ?\s ell)))) - (unless (string-prefix-p str trunc) - (put-text-property 0 (length trunc) 'help-echo str trunc)) - trunc)) - -(cl-defmacro marginalia--field (field &key truncate face width format) - "Format FIELD as a string according to some options. -TRUNCATE is the truncation width. -WIDTH is the field width. -FORMAT is a format string. -FACE is the name of the face, with which the field should be propertized." - (setq field (if format `(format ,format ,field) `(or ,field ""))) - (when width (setq field `(format ,(format "%%%ds" (- width)) ,field))) - (when truncate (setq field `(marginalia--truncate ,field ,truncate))) - (when face (setq field `(propertize ,field 'face ,face))) - field) - -(defmacro marginalia--fields (&rest fields) - "Format annotation FIELDS as a string with separators in between." - (let ((left t)) - (cons 'concat - (mapcan - (lambda (field) - (if (not (eq (car field) :left)) - `(,@(when left (setq left nil) `(#(" " 0 1 (marginalia--align t)))) - marginalia-separator (marginalia--field ,@field)) - (unless left (error "Left fields must come first")) - `((marginalia--field ,@(cdr field))))) - fields)))) - -(defmacro marginalia--in-minibuffer (&rest body) - "Run BODY inside minibuffer if minibuffer is active. -Otherwise stay within current buffer." - (declare (indent 0)) - `(with-current-buffer (if-let (win (active-minibuffer-window)) - (window-buffer win) - (current-buffer)) - ,@body)) - -(defun marginalia--documentation (str) - "Format documentation string STR." - (when str - (marginalia--fields - (str :truncate 1.0 :face 'marginalia-documentation)))) - -(defun marginalia-annotate-binding (cand) - "Annotate command CAND with keybinding." - (when-let ((sym (intern-soft cand)) - (key (and (commandp sym) (where-is-internal sym nil 'first-only)))) - (format #(" (%s)" 1 5 (face marginalia-key)) (key-description key)))) - -(defun marginalia--annotator (cat) - "Return annotation function for category CAT." - (pcase (car (alist-get cat marginalia-annotator-registry)) - ('none #'ignore) - ('builtin nil) - (fun fun))) - -(defun marginalia-annotate-multi-category (cand) - "Annotate multi-category CAND with the buffer class." - (if-let ((multi (get-text-property 0 'multi-category cand)) - (annotate (marginalia--annotator (car multi)))) - ;; Use the Marginalia annotator corresponding to the multi category. - (funcall annotate (cdr multi)) - ;; Apply the original annotation function on the original candidate, if - ;; there is one. Use `alist-get' instead of `completion-metadata-get' to - ;; bypass our `marginalia--completion-metadata-get' advice! - (when-let (annotate (alist-get 'annotation-function marginalia--metadata)) - (funcall annotate cand)))) - -(defconst marginalia--advice-regexp - (rx bos - (1+ (seq (? "This function has ") - (or ":before" ":after" ":around" ":override" - ":before-while" ":before-until" ":after-while" - ":after-until" ":filter-args" ":filter-return") - " advice: " (0+ nonl) "\n")) - "\n") - "Regexp to match lines about advice in function documentation strings.") - -;; Taken from advice--make-docstring, is this robust? -(defun marginalia--advised (fun) - "Return t if function FUN is advised." - (let ((flist (indirect-function fun))) - (advice--p (if (eq 'macro (car-safe flist)) (cdr flist) flist)))) - -(defun marginalia--symbol-class (s) - "Return symbol class characters for symbol S. - -This function is an extension of `help--symbol-class'. It returns -more fine-grained and more detailed symbol information. - -Function: -f function -c command -C interactive-only command -m macro -F special-form -M module function -P primitive -g cl-generic -p pure -s side-effect-free -@ autoloaded -! advised -- obsolete -& alias - -Variable: -u custom (U modified compared to global value) -v variable -l local (L modified compared to default value) -- obsolete -& alias - -Other: -a face -t cl-type" - (let ((class - (append - (when (fboundp s) - (list - (cond - ((get s 'pure) '("p" . "pure")) - ((get s 'side-effect-free) '("s" . "side-effect-free"))) - (cond - ((commandp s) - (if (get s 'interactive-only) - '("C" . "interactive-only command") - '("c" . "command"))) - ((cl-generic-p s) '("g" . "cl-generic")) - ((macrop (symbol-function s)) '("m" . "macro")) - ((special-form-p (symbol-function s)) '("F" . "special-form")) - ((subr-primitive-p (symbol-function s)) '("P" . "primitive")) - ((module-function-p (symbol-function s)) '("M" . "module function")) - (t '("f" . "function"))) - (and (autoloadp (symbol-function s)) '("@" . "autoload")) - (and (marginalia--advised s) '("!" . "advised")) - (and (symbolp (symbol-function s)) - (cons "&" (format "alias for `%s'" (symbol-function s)))) - (and (get s 'byte-obsolete-info) '("-" . "obsolete")))) - (when (boundp s) - (list - (when (local-variable-if-set-p s) - (if (ignore-errors - (not (equal (symbol-value s) - (default-value s)))) - '("L" . "local, modified from global") - '("l" . "local, unmodified"))) - (if (custom-variable-p s) - (if (ignore-errors - (not (equal (symbol-value s) - (eval (car (get s 'standard-value)))))) - '("U" . "custom, modified from standard") - '("u" . "custom, unmodified")) - '("v" . "variable")) - (and (not (eq (ignore-errors (indirect-variable s)) s)) - (cons "&" (format "alias for `%s'" (ignore-errors (indirect-variable s))))) - (and (get s 'byte-obsolete-variable) '("-" . "obsolete")))) - (list - (and (facep s) '("a" . "face")) - (and (get s 'cl--class) '("t" . "cl-type")))))) ;; cl-find-class, cl--find-class - (setq class (delq nil class)) - (propertize - (format " %-6s" (mapconcat #'car class "")) - 'help-echo - (mapconcat (pcase-lambda (`(,x . ,y)) (concat x " " y)) class "\n")))) - -(defun marginalia--function-doc (sym) - "Documentation string of function SYM." - (when-let (str (ignore-errors (documentation sym))) - (save-match-data - (if (string-match marginalia--advice-regexp str) - (substring str (match-end 0)) - str)))) - -;; Derived from elisp-get-fnsym-args-string -(defun marginalia--function-args (sym) - "Return function arguments for SYM." - (let ((tmp)) - (elisp-function-argstring - (cond - ((listp (setq tmp (gethash (indirect-function sym) - advertised-signature-table t))) - tmp) - ((setq tmp (help-split-fundoc - (ignore-errors (documentation sym t)) - sym)) - (substitute-command-keys (car tmp))) - ((setq tmp (help-function-arglist sym)) - (and - (if (and (stringp tmp) - (string-search "Arg list not available" tmp)) - ;; A shorter text fits better into the - ;; limited Marginalia space. - "[autoload]" - tmp))))))) - -(defun marginalia-annotate-symbol (cand) - "Annotate symbol CAND with its documentation string." - (when-let (sym (intern-soft cand)) - (marginalia--fields - (:left (marginalia-annotate-binding cand)) - ((marginalia--symbol-class sym) :face 'marginalia-type) - ((cond - ((fboundp sym) (marginalia--function-doc sym)) - ((facep sym) (documentation-property sym 'face-documentation)) - (t (documentation-property sym 'variable-documentation))) - :truncate 1.0 :face 'marginalia-documentation) - ((abbreviate-file-name (or (symbol-file sym) "")) - :truncate -0.5 :face 'marginalia-file-name)))) - -(defun marginalia-annotate-command (cand) - "Annotate command CAND with its documentation string. -Similar to `marginalia-annotate-symbol', but does not show symbol class." - (when-let (sym (intern-soft cand)) - (concat - (marginalia-annotate-binding cand) - (marginalia--documentation (marginalia--function-doc sym))))) - -(defun marginalia-annotate-embark-keybinding (cand) - "Annotate Embark keybinding CAND with its documentation string. -Similar to `marginalia-annotate-command', but does not show the -keybinding since CAND includes it." - (when-let (cmd (get-text-property 0 'embark-command cand)) - (marginalia--documentation (marginalia--function-doc cmd)))) - -(defun marginalia-annotate-imenu (cand) - "Annotate imenu CAND with its documentation string." - (when (derived-mode-p 'emacs-lisp-mode) - ;; Strip until the last whitespace in order to support flat imenu - (marginalia-annotate-symbol (replace-regexp-in-string "\\`.* " "" cand)))) - -(defun marginalia-annotate-function (cand) - "Annotate function CAND with its documentation string." - (when-let (sym (intern-soft cand)) - (when (fboundp sym) - (marginalia--fields - (:left (marginalia-annotate-binding cand)) - ((marginalia--symbol-class sym) :face 'marginalia-type) - ((marginalia--function-args sym) :face 'marginalia-value - :truncate 0.5) - ((marginalia--function-doc sym) :truncate 1.0 - :face 'marginalia-documentation))))) - -(defun marginalia--variable-value (sym) - "Return the variable value of SYM as string." - (cond - ((not (boundp sym)) - (propertize "#" 'face 'marginalia-null)) - ((and marginalia-censor-variables - (let ((name (symbol-name sym)) - case-fold-search) - (cl-loop for r in marginalia-censor-variables - thereis (if (symbolp r) - (eq r sym) - (string-match-p r name))))) - (propertize "*****" - 'face 'marginalia-null - 'help-echo "Hidden due to `marginalia-censor-variables'")) - (t - (let ((val (symbol-value sym))) - (pcase val - ('nil (propertize "nil" 'face 'marginalia-null)) - ('t (propertize "t" 'face 'marginalia-true)) - ((pred keymapp) (propertize "#" 'face 'marginalia-value)) - ((pred bool-vector-p) (propertize "#" 'face 'marginalia-value)) - ((pred hash-table-p) (propertize "#" 'face 'marginalia-value)) - ((pred syntax-table-p) (propertize "#" 'face 'marginalia-value)) - ;; Emacs bug#53988: abbrev-table-p throws an error - ((and (pred vectorp) (guard (ignore-errors (abbrev-table-p val)))) - (propertize "#" 'face 'marginalia-value)) - ((pred char-table-p) (propertize "#" 'face 'marginalia-value)) - ;; Emacs 29 comes with callable objects or object closures (OClosures) - ((guard (and (fboundp 'oclosure-type) (oclosure-type val))) - (format (propertize "#" 'face 'marginalia-function) - (and (fboundp 'oclosure-type) (oclosure-type val)))) - ((pred byte-code-function-p) (propertize "#" 'face 'marginalia-function)) - ((and (pred functionp) (pred symbolp)) - ;; We are not consistent here, values are generally printed - ;; unquoted. But we make an exception for function symbols to visually - ;; distinguish them from symbols. I am not entirely happy with this, - ;; but we should not add quotation to every type. - (format (propertize "#'%s" 'face 'marginalia-function) val)) - ((pred recordp) (format (propertize "#" 'face 'marginalia-value) (type-of val))) - ((pred symbolp) (propertize (symbol-name val) 'face 'marginalia-symbol)) - ((pred numberp) (propertize (number-to-string val) 'face 'marginalia-number)) - (_ (let ((print-escape-newlines t) - (print-escape-control-characters t) - ;;(print-escape-multibyte t) - (print-level 3) - (print-length marginalia-field-width)) - (propertize - (replace-regexp-in-string - ;; `print-escape-control-characters' does not escape Unicode control characters. - "[\x0-\x1F\x7f-\x9f\x061c\x200e\x200f\x202a-\x202e\x2066-\x2069]" - (lambda (x) (format "\\x%x" (string-to-char x))) - (prin1-to-string - (if (stringp val) - ;; Get rid of string properties to save some of the precious space - (substring-no-properties - val 0 - (min (length val) marginalia-field-width)) - val)) - 'fixedcase 'literal) - 'face - (cond - ((listp val) 'marginalia-list) - ((stringp val) 'marginalia-string) - (t 'marginalia-value)))))))))) - -(defun marginalia-annotate-variable (cand) - "Annotate variable CAND with its documentation string." - (when-let (sym (intern-soft cand)) - (marginalia--fields - ((marginalia--symbol-class sym) :face 'marginalia-type) - ((marginalia--variable-value sym) :truncate 0.5) - ((documentation-property sym 'variable-documentation) - :truncate 1.0 :face 'marginalia-documentation)))) - -(defun marginalia-annotate-environment-variable (cand) - "Annotate environment variable CAND with its current value." - (when-let (val (getenv cand)) - (marginalia--fields - (val :truncate 1.0 :face 'marginalia-value)))) - -(defun marginalia-annotate-face (cand) - "Annotate face CAND with its documentation string and face example." - (when-let (sym (intern-soft cand)) - (marginalia--fields - ;; HACK: Manual alignment to fix misalignment due to face - ((concat marginalia--pangram #(" " 0 1 (display (space :align-to center)))) - :face sym) - ((documentation-property sym 'face-documentation) - :truncate 1.0 :face 'marginalia-documentation)))) - -(defun marginalia-annotate-color (cand) - "Annotate face CAND with its documentation string and face example." - (when-let (rgb (color-name-to-rgb cand)) - (pcase-let* ((`(,r ,g ,b) rgb) - (`(,h ,s ,l) (apply #'color-rgb-to-hsl rgb)) - (cr (color-rgb-to-hex r 0 0)) - (cg (color-rgb-to-hex 0 g 0)) - (cb (color-rgb-to-hex 0 0 b)) - (ch (apply #'color-rgb-to-hex (color-hsl-to-rgb h 1 0.5))) - (cs (apply #'color-rgb-to-hex (color-hsl-to-rgb h s 0.5))) - (cl (apply #'color-rgb-to-hex (color-hsl-to-rgb 0 0 l)))) - (marginalia--fields - (" " :face `(:background ,(apply #'color-rgb-to-hex rgb))) - ((format - "%s%s%s %s" - (propertize "r" 'face `(:background ,cr :foreground ,(readable-foreground-color cr))) - (propertize "g" 'face `(:background ,cg :foreground ,(readable-foreground-color cg))) - (propertize "b" 'face `(:background ,cb :foreground ,(readable-foreground-color cb))) - (color-rgb-to-hex r g b 2))) - ((format - "%s%s%s %3s° %3s%% %3s%%" - (propertize "h" 'face `(:background ,ch :foreground ,(readable-foreground-color ch))) - (propertize "s" 'face `(:background ,cs :foreground ,(readable-foreground-color cs))) - (propertize "l" 'face `(:background ,cl :foreground ,(readable-foreground-color cl))) - (round (* 360 h)) - (round (* 100 s)) - (round (* 100 l)))))))) - -(defun marginalia-annotate-char (cand) - "Annotate character CAND with its general character category and character code." - (when-let (char (char-from-name cand t)) - (marginalia--fields - (:left char :format" (%c)" :face 'marginalia-char) - (char :format "%06X" :face 'marginalia-number) - ((char-code-property-description - 'general-category - (get-char-code-property char 'general-category)) - :width 30 :face 'marginalia-documentation)))) - -(defun marginalia-annotate-minor-mode (cand) - "Annotate minor-mode CAND with status and documentation string." - (let* ((sym (intern-soft cand)) - (message-log-max nil) - (mode (if (and sym (boundp sym)) - sym - (lookup-minor-mode-from-indicator cand))) - (lighter (cdr (assq mode minor-mode-alist))) - (lighter-str (and lighter (string-trim (format-mode-line (cons t lighter)))))) - (marginalia--fields - ((if (and (boundp mode) (symbol-value mode)) - (propertize "On" 'face 'marginalia-on) - (propertize "Off" 'face 'marginalia-off)) :width 3) - ((if (local-variable-if-set-p mode) "Local" "Global") :width 6 :face 'marginalia-type) - (lighter-str :width 20 :face 'marginalia-lighter) - ((marginalia--function-doc mode) - :truncate 1.0 :face 'marginalia-documentation)))) - -(defun marginalia-annotate-package (cand) - "Annotate package CAND with its description summary." - (when-let ((pkg-alist (bound-and-true-p package-alist)) - (name (replace-regexp-in-string "-[0-9\\.-]+\\'" "" cand)) - (pkg (intern-soft name)) - (desc (or (unless (equal name cand) - (cl-loop with version = (substring cand (1+ (length name))) - for d in (alist-get pkg pkg-alist) - if (equal (package-version-join (package-desc-version d)) version) - return d)) - ;; taken from `describe-package-1' - (car (alist-get pkg pkg-alist)) - (if-let (built-in (assq pkg package--builtins)) - (package--from-builtin built-in) - (car (alist-get pkg package-archive-contents)))))) - (marginalia--fields - ((package-version-join (package-desc-version desc)) :truncate 16 :face 'marginalia-version) - ((cond - ((package-desc-archive desc) (propertize (package-desc-archive desc) 'face 'marginalia-archive)) - (t (propertize (or (package-desc-status desc) "orphan") 'face 'marginalia-installed))) :truncate 12) - ((package-desc-summary desc) :truncate 1.0 :face 'marginalia-documentation)))) - -(defun marginalia--bookmark-type (bm) - "Return bookmark type string of BM. -The string is transformed according to `marginalia--bookmark-type-transforms'." - (let ((handler (or (bookmark-prop-get bm 'handler) 'bookmark-default-handler))) - (and - ;; Some libraries use lambda handlers instead of symbols. For - ;; example the function `xwidget-webkit-bookmark-make-record' is - ;; affected. I consider this bad style since then the lambda is - ;; persisted. - (symbolp handler) - (or (get handler 'bookmark-handler-type) - (let ((str (symbol-name handler)) - case-fold-search) - (dolist (transformer marginalia--bookmark-type-transforms str) - (when (string-match-p (car transformer) str) - (setq str - (if (stringp (cdr transformer)) - (replace-regexp-in-string (car transformer) (cdr transformer) str) - (funcall (cdr transformer) str)))))))))) - -(defun marginalia-annotate-bookmark (cand) - "Annotate bookmark CAND with its file name and front context string." - (when-let ((bm (assoc cand (bound-and-true-p bookmark-alist)))) - (marginalia--fields - ((marginalia--bookmark-type bm) :width 10 :face 'marginalia-type) - ((or (bookmark-prop-get bm 'filename) - (bookmark-prop-get bm 'location)) - :truncate (if (bookmark-prop-get bm 'filename) -0.5 0.5) - :face 'marginalia-file-name) - ((let ((front (or (bookmark-prop-get bm 'front-context-string) "")) - (rear (or (bookmark-prop-get bm 'rear-context-string) ""))) - (unless (and (string-blank-p front) (string-blank-p rear)) - (string-clean-whitespace - (concat front (marginalia--ellipsis) rear)))) - :truncate 0.5 :face 'marginalia-documentation)))) - -(defun marginalia-annotate-customize-group (cand) - "Annotate customization group CAND with its documentation string." - (marginalia--documentation (documentation-property (intern cand) 'group-documentation))) - -(defun marginalia-annotate-input-method (cand) - "Annotate input method CAND with its description." - (marginalia--documentation (nth 4 (assoc cand input-method-alist)))) - -(defun marginalia-annotate-charset (cand) - "Annotate charset CAND with its description." - (marginalia--documentation (charset-description (intern cand)))) - -(defun marginalia-annotate-coding-system (cand) - "Annotate coding system CAND with its description." - (marginalia--documentation (coding-system-doc-string (intern cand)))) - -(defun marginalia--buffer-status (buffer) - "Return the status of BUFFER as a string." - (format-mode-line '((:propertize "%1*%1+%1@" face marginalia-modified) - marginalia-separator - (7 (:propertize "%I" face marginalia-size)) - marginalia-separator - ;; InactiveMinibuffer has 18 letters, but there are longer names. - ;; For example Org-Agenda produces very long mode names. - ;; Therefore we have to truncate. - (20 (-20 (:propertize mode-name face marginalia-mode)))) - nil nil buffer)) - -(defun marginalia--buffer-file (buffer) - "Return the file or process name of BUFFER." - (if-let (proc (get-buffer-process buffer)) - (format "(%s %s) %s" - proc (process-status proc) - (abbreviate-file-name (buffer-local-value 'default-directory buffer))) - (abbreviate-file-name - (or (cond - ;; see ibuffer-buffer-file-name - ((buffer-file-name buffer)) - ((when-let (dir (and (local-variable-p 'dired-directory buffer) - (buffer-local-value 'dired-directory buffer))) - (expand-file-name (if (stringp dir) dir (car dir)) - (buffer-local-value 'default-directory buffer)))) - ((local-variable-p 'list-buffers-directory buffer) - (buffer-local-value 'list-buffers-directory buffer))) - "")))) - -(defun marginalia-annotate-buffer (cand) - "Annotate buffer CAND with modification status, file name and major mode." - (when-let (buffer (get-buffer cand)) - (marginalia--fields - ((marginalia--buffer-status buffer)) - ((marginalia--buffer-file buffer) - :truncate -0.5 :face 'marginalia-file-name)))) - -(defun marginalia--full-candidate (cand) - "Return completion candidate CAND in full. -For some completion tables, the completion candidates offered are -meant to be only a part of the full minibuffer contents. For -example, during file name completion the candidates are one path -component of a full file path." - (if-let (win (active-minibuffer-window)) - (with-current-buffer (window-buffer win) - (concat (let ((end (minibuffer-prompt-end))) - (buffer-substring-no-properties - end (+ end marginalia--base-position))) - cand)) - ;; no minibuffer is active, trust that cand already conveys all - ;; necessary information (there's not much else we can do) - cand)) - -(defun marginalia--remote-file-p (file) - "Return non-nil if FILE is remote. -The return value is a string describing the remote location, -e.g., the protocol." - (save-match-data - (setq file (substitute-in-file-name file)) - (cl-loop for r in marginalia-remote-file-regexps - if (string-match r file) - return (or (match-string 1 file) "remote")))) - -(defun marginalia--annotate-local-file (cand) - "Annotate local file CAND." - (marginalia--in-minibuffer - (when-let (attrs (ignore-errors - ;; may throw permission denied errors - (file-attributes (substitute-in-file-name - (marginalia--full-candidate cand)) - 'integer))) - ;; HACK: Format differently accordingly to alignment, since the file owner - ;; is usually not displayed. Otherwise we will see an excessive amount of - ;; whitespace in front of the file permissions. Furthermore the alignment - ;; in `consult-buffer' will look ugly. Find a better solution! - (if (eq marginalia-align 'right) - (marginalia--fields - ;; File owner at the left - ((marginalia--file-owner attrs) :face 'marginalia-file-owner) - ((marginalia--file-modes attrs)) - ((marginalia--file-size attrs) :face 'marginalia-size :width -7) - ((marginalia--time (file-attribute-modification-time attrs)) - :face 'marginalia-date :width -12)) - (marginalia--fields - ((marginalia--file-modes attrs)) - ((marginalia--file-size attrs) :face 'marginalia-size :width -7) - ((marginalia--time (file-attribute-modification-time attrs)) - :face 'marginalia-date :width -12) - ;; File owner at the right - ((marginalia--file-owner attrs) :face 'marginalia-file-owner)))))) - -(defun marginalia-annotate-file (cand) - "Annotate file CAND with its size, modification time and other attributes. -These annotations are skipped for remote paths." - (if-let (remote (or (marginalia--remote-file-p cand) - (when-let (win (active-minibuffer-window)) - (with-current-buffer (window-buffer win) - (marginalia--remote-file-p (minibuffer-contents-no-properties)))))) - (marginalia--fields (remote :format "*%s*" :face 'marginalia-documentation)) - (marginalia--annotate-local-file cand))) - -(defun marginalia--file-owner (attrs) - "Return file owner given ATTRS." - (let ((uid (file-attribute-user-id attrs)) - (gid (file-attribute-group-id attrs))) - (when (or (/= (user-uid) uid) (/= (group-gid) gid)) - (format "%s:%s" - (or (user-login-name uid) uid) - (or (group-name gid) gid))))) - -(defun marginalia--file-size (attrs) - "Return formatted file size given ATTRS." - (propertize (file-size-human-readable (file-attribute-size attrs)) - 'help-echo (number-to-string (file-attribute-size attrs)))) - -(defun marginalia--file-modes (attrs) - "Return fontified file modes given the ATTRS." - ;; Without caching this can a be significant portion of the time - ;; `marginalia-annotate-file' takes to execute. Caching improves performance - ;; by about a factor of 20. - (setq attrs (file-attribute-modes attrs)) - (or (car (member attrs marginalia--fontified-file-modes)) - (progn - (setq attrs (substring attrs)) ;; copy because attrs is about to be modified - (dotimes (i (length attrs)) - (put-text-property - i (1+ i) 'face - (pcase (aref attrs i) - (?- 'marginalia-file-priv-no) - (?d 'marginalia-file-priv-dir) - (?l 'marginalia-file-priv-link) - (?r 'marginalia-file-priv-read) - (?w 'marginalia-file-priv-write) - (?x 'marginalia-file-priv-exec) - ((or ?s ?S ?t ?T) 'marginalia-file-priv-other) - (_ 'marginalia-file-priv-rare)) - attrs)) - (push attrs marginalia--fontified-file-modes) - attrs))) - -(defconst marginalia--time-relative - `((100 "sec" 1) - (,(* 60 100) "min" 60.0) - (,(* 3600 30) "hour" 3600.0) - (,(* 3600 24 400) "day" ,(* 3600.0 24.0)) - (nil "year" ,(* 365.25 24 3600))) - "Formatting used by the function `marginalia--time-relative'.") - -;; Taken from `seconds-to-string'. -(defun marginalia--time-relative (time) - "Format TIME as a relative age." - (setq time (max 0 (float-time (time-since time)))) - (let ((sts marginalia--time-relative) here) - (while (and (car (setq here (pop sts))) (<= (car here) time))) - (setq time (round time (caddr here))) - (format "%s %s%s ago" time (cadr here) (if (= time 1) "" "s")))) - -(defun marginalia--time-absolute (time) - "Format TIME as an absolute age." - (let ((system-time-locale "C")) - (format-time-string - (if (> (decoded-time-year (decode-time (current-time))) - (decoded-time-year (decode-time time))) - " %Y %b %d" - "%b %d %H:%M") - time))) - -(defun marginalia--time (time) - "Format file age TIME, suitably for use in annotations." - (propertize - (if (< (float-time (time-since time)) marginalia-max-relative-age) - (marginalia--time-relative time) - (marginalia--time-absolute time)) - 'help-echo (format-time-string "%Y-%m-%d %T" time))) - -(defvar-local marginalia--project-root 'unset) -(defun marginalia--project-root () - "Return project root." - (marginalia--in-minibuffer - (when (eq marginalia--project-root 'unset) - (setq marginalia--project-root - (or (let ((prompt (minibuffer-prompt)) - case-fold-search) - (and (string-match - "\\`\\(?:Dired\\|Find file\\) in \\(.*\\): \\'" - prompt) - (match-string 1 prompt))) - (when-let (proj (project-current)) - (cond - ((fboundp 'project-root) (project-root proj)) - ((fboundp 'project-roots) (car (project-roots proj)))))))) - marginalia--project-root)) - -(defun marginalia-annotate-project-file (cand) - "Annotate file CAND with its size, modification time and other attributes." - ;; Absolute project directories also report project-file category - (if (file-name-absolute-p cand) - (marginalia-annotate-file cand) - (when-let (root (marginalia--project-root)) - (marginalia-annotate-file (expand-file-name cand root))))) - -(defvar-local marginalia--library-cache nil) -(defun marginalia--library-cache () - "Return hash table from library name to library file." - (marginalia--in-minibuffer - ;; `locate-file' and `locate-library' are bottlenecks for the - ;; annotator. Therefore we compute all the library paths first. - (unless marginalia--library-cache - (setq marginalia--library-cache (make-hash-table :test #'equal)) - (dolist (dir (delete-dups - (reverse ;; Reverse because of shadowing - (append load-path (custom-theme--load-path))))) ;; Include themes - (dolist (file (ignore-errors - (directory-files dir 'full - "\\.el\\(?:\\.gz\\)?\\'"))) - (puthash (marginalia--library-name file) - file marginalia--library-cache)))) - marginalia--library-cache)) - -(defun marginalia--library-name (file) - "Get name of library FILE." - (replace-regexp-in-string "\\(\\.gz\\|\\.elc?\\)+\\'" "" - (file-name-nondirectory file))) - -(defun marginalia--library-doc (file) - "Return library documentation string for FILE." - (let ((doc (get-text-property 0 'marginalia--library-doc file))) - (unless doc - ;; Extract documentation string. We cannot use `lm-summary' here, - ;; since it decompresses the whole file, which is slower. - (setq doc (or (ignore-errors - (let ((shell-file-name "sh") - (shell-command-switch "-c")) - (shell-command-to-string - (format (if (string-suffix-p ".gz" file) - "gzip -c -q -d %s | head -n1" - "head -n1 %s") - (shell-quote-argument file))))) - "")) - (cond - ((string-match "\\`(define-package\\s-+\"\\([^\"]+\\)\"" doc) - (setq doc (format "Generated package description from %s.el" - (match-string 1 doc)))) - ((string-match "\\`;+\\s-*" doc) - (setq doc (substring doc (match-end 0))) - (when (string-match "\\`[^ \t]+\\s-+-+\\s-+" doc) - (setq doc (substring doc (match-end 0)))) - (when (string-match "\\s-*-\\*-" doc) - (setq doc (substring doc 0 (match-beginning 0))))) - (t (setq doc ""))) - ;; Add the documentation string to the cache - (put-text-property 0 1 'marginalia--library-doc doc file)) - doc)) - -(defun marginalia-annotate-theme (cand) - "Annotate theme CAND with documentation and path." - (marginalia-annotate-library (concat cand "-theme"))) - -(defun marginalia-annotate-library (cand) - "Annotate library CAND with documentation and path." - (setq cand (marginalia--library-name cand)) - (when-let (file (gethash cand (marginalia--library-cache))) - (marginalia--fields - ;; Display if the corresponding feature is loaded. - ;; feature/=library file, but better than nothing. - ((when-let (sym (intern-soft cand)) - (when (memq sym features) - (propertize "Loaded" 'face 'marginalia-on))) - :width 8) - ((marginalia--library-doc file) - :truncate 1.0 :face 'marginalia-documentation) - ((abbreviate-file-name (file-name-directory file)) - :truncate -0.5 :face 'marginalia-file-name)))) - -(defun marginalia-annotate-tab (cand) - "Annotate named tab CAND with tab index, window and buffer information." - (when-let ((tabs (funcall tab-bar-tabs-function)) - (index (seq-position - tabs nil - (lambda (tab _) (equal (alist-get 'name tab) cand))))) - (let* ((tab (nth index tabs)) - (ws (alist-get 'ws tab)) - (bufs (window-state-buffers ws))) - ;; When the buffer key is present in the window state it is added in front - ;; of the window buffer list and gets duplicated. - (when (cadr (assq 'buffer ws)) (pop bufs)) - (marginalia--fields - (:left (1+ index) :format " (%s)" :face 'marginalia-key) - ((if (eq (car tab) 'current-tab) - (length (window-list nil 'no-minibuf)) - (length bufs)) - :format "win:%s" :face 'marginalia-size) - ((or (alist-get 'group tab) 'none) - :format "group:%s" :face 'marginalia-type :truncate 20) - ((if (eq (car tab) 'current-tab) - "(current tab)" - (string-join bufs " ")) - :face 'marginalia-documentation))))) - -(defun marginalia-classify-by-command-name () - "Lookup category for current command." - (and marginalia--command - (or (alist-get marginalia--command marginalia-command-categories) - ;; The command can be an alias, e.g., `recentf' -> `recentf-open'. - (when-let ((chain (function-alias-p marginalia--command))) - (alist-get (car (last chain)) marginalia-command-categories))))) - -(defun marginalia-classify-original-category () - "Return original category reported by completion metadata." - ;; Use `alist-get' instead of `completion-metadata-get' to bypass our - ;; `marginalia--completion-metadata-get' advice! - (when-let (cat (alist-get 'category marginalia--metadata)) - ;; Ignore Emacs 28 symbol-help category in order to ensure that the - ;; categories are refined to our categories function and variable. - (and (not (eq cat 'symbol-help)) cat))) - -(defun marginalia-classify-symbol () - "Determine if currently completing symbols." - (when-let (mct minibuffer-completion-table) - (when (or (eq mct 'help--symbol-completion-table) - (obarrayp mct) - (and (not (functionp mct)) (consp mct) (symbolp (car mct)))) ; assume list of symbols - 'symbol))) - -(defun marginalia-classify-by-prompt () - "Determine category by matching regexps against the minibuffer prompt. -This runs through the `marginalia-prompt-categories' alist -looking for a regexp that matches the prompt." - (when-let (prompt (minibuffer-prompt)) - (setq prompt - (replace-regexp-in-string "(.*?default.*?)\\|\\[.*?\\]" "" prompt)) - (cl-loop with case-fold-search = t - for (regexp . category) in marginalia-prompt-categories - when (string-match-p regexp prompt) - return category))) - -(defun marginalia--cache-reset (&rest _) - "Reset the cache." - (setq marginalia--cache (and marginalia--cache (> marginalia--cache-size 0) - (cons nil (make-hash-table :test #'equal - :size marginalia--cache-size))))) - -(defun marginalia--cached (cache fun key) - "Cached application of function FUN with KEY. -The CACHE keeps around the last `marginalia--cache-size' computed -annotations. The cache is mainly useful when scrolling in -completion UIs like Vertico or Icomplete." - (if cache - (let ((ht (cdr cache))) - (or (gethash key ht) - (let ((val (funcall fun key))) - (push key (car cache)) - (puthash key val ht) - (when (>= (hash-table-count ht) marginalia--cache-size) - (let ((end (last (car cache) 2))) - (remhash (cadr end) ht) - (setcdr end nil))) - val))) - (funcall fun key))) - -(defun marginalia--align (cands) - "Align annotations of CANDS according to `marginalia-align'." - (cl-loop - for (cand . ann) in cands do - (when-let (align (text-property-any 0 (length ann) 'marginalia--align t ann)) - (setq marginalia--cand-width-max - (max marginalia--cand-width-max - (* (ceiling (+ (string-width cand) - (compat-call string-width ann 0 align)) - marginalia--cand-width-step) - marginalia--cand-width-step))))) - (cl-loop - for (cand . ann) in cands collect - (progn - (when-let (align (text-property-any 0 (length ann) 'marginalia--align t ann)) - (put-text-property - align (1+ align) 'display - `(space :align-to - ,(pcase-exhaustive marginalia-align - ('center `(+ center ,marginalia-align-offset)) - ('left `(+ left ,(+ marginalia-align-offset marginalia--cand-width-max))) - ('right `(+ right ,(+ marginalia-align-offset 1 - (- (compat-call string-width ann 0 align) - (string-width ann))))))) - ann)) - (list cand "" ann)))) - -(defun marginalia--affixate (metadata annotator cands) - "Affixate CANDS given METADATA and Marginalia ANNOTATOR." - ;; Compute minimum width of windows, which display the minibuffer. - ;; vertico-buffer displays the minibuffer in different windows. We may want - ;; to generalize this and detect other types of completion buffers, e.g., - ;; Embark Collect or the default completion buffer. - (let* ((width (cl-loop for win in (get-buffer-window-list) minimize (window-width win))) - (marginalia-field-width (min (/ width 2) marginalia-field-width)) - (marginalia--metadata metadata) - (cache marginalia--cache)) - (marginalia--align - ;; Run the annotators in the original window. `with-selected-window' - ;; is necessary because of `lookup-minor-mode-from-indicator'. - ;; Otherwise it would suffice to only change the current buffer. We - ;; need the `selected-window' fallback for Embark Occur. - (with-selected-window (or (minibuffer-selected-window) (selected-window)) - (cl-loop for cand in cands collect - (let ((ann (or (marginalia--cached cache annotator cand) ""))) - (cons cand (if (string-blank-p ann) "" ann)))))))) - -(defun marginalia--completion-metadata-get (metadata prop) - "Meant as :before-until advice for `completion-metadata-get'. -METADATA is the metadata. -PROP is the property which is looked up." - (pcase prop - ('annotation-function - ;; We do want the advice triggered for `completion-metadata-get'. - (when-let ((cat (completion-metadata-get metadata 'category)) - (annotator (marginalia--annotator cat))) - (lambda (cand) - (let ((ann (caddar (marginalia--affixate metadata annotator (list cand))))) - (and (not (equal ann "")) ann))))) - ('affixation-function - ;; We do want the advice triggered for `completion-metadata-get'. - (when-let ((cat (completion-metadata-get metadata 'category)) - (annotator (marginalia--annotator cat))) - (apply-partially #'marginalia--affixate metadata annotator))) - ('category - ;; Find the completion category by trying each of our classifiers. - ;; Store the metadata for `marginalia-classify-original-category'. - (let ((marginalia--metadata metadata)) - (run-hook-with-args-until-success 'marginalia-classifiers))))) - -(defun marginalia--minibuffer-setup () - "Setup the minibuffer for Marginalia. -Remember `this-command' for `marginalia-classify-by-command-name'." - (setq marginalia--cache t marginalia--command this-command) - ;; Reset cache if window size changes, recompute alignment - (add-hook 'window-state-change-hook #'marginalia--cache-reset nil 'local) - (marginalia--cache-reset)) - -(defun marginalia--base-position (completions) - "Record the base position of COMPLETIONS." - ;; As a small optimization we track the base position only for file - ;; completions, since `marginalia--full-candidate' is currently used only by - ;; the file annotation function. - (when minibuffer-completing-file-name - (let ((base (or (cdr (last completions)) 0))) - (unless (= marginalia--base-position base) - (marginalia--cache-reset) - (setq marginalia--base-position base - marginalia--cand-width-max (default-value 'marginalia--cand-width-max))))) - completions) - -;;;###autoload -(define-minor-mode marginalia-mode - "Annotate completion candidates with richer information." - :global t :group 'marginalia - (if marginalia-mode - (progn - ;; Remember `this-command' in order to select the annotation function. - (add-hook 'minibuffer-setup-hook #'marginalia--minibuffer-setup) - ;; Replace the metadata function. - (advice-add #'completion-metadata-get :before-until #'marginalia--completion-metadata-get) - ;; Record completion base position, for `marginalia--full-candidate' - (advice-add #'completion-all-completions :filter-return #'marginalia--base-position)) - (advice-remove #'completion-all-completions #'marginalia--base-position) - (advice-remove #'completion-metadata-get #'marginalia--completion-metadata-get) - (remove-hook 'minibuffer-setup-hook #'marginalia--minibuffer-setup))) - -;;;###autoload -(defun marginalia-cycle () - "Cycle between annotators in `marginalia-annotator-registry'." - (interactive) - (with-current-buffer (window-buffer - (or (active-minibuffer-window) - (user-error "Marginalia: No active minibuffer"))) - (let* ((pt (max 0 (- (point) (minibuffer-prompt-end)))) - (metadata (completion-metadata (buffer-substring-no-properties - (minibuffer-prompt-end) - (+ (minibuffer-prompt-end) pt)) - minibuffer-completion-table - minibuffer-completion-predicate)) - (cat (or (completion-metadata-get metadata 'category) - (user-error "Marginalia: Unknown completion category"))) - (ann (or (assq cat marginalia-annotator-registry) - (user-error "Marginalia: No annotators found for category `%s'" cat)))) - (marginalia--cache-reset) - (setcdr ann (append (cddr ann) (list (cadr ann)))) - ;; When the builtin annotator is selected and no builtin function is - ;; available, skip to the next annotator. Note that we cannot use - ;; `completion-metadata-get' to access the metadata since we must - ;; bypass the `marginalia--completion-metadata-get' advice. - (when (and (eq (cadr ann) 'builtin) - (not (assq 'annotation-function metadata)) - (not (assq 'affixation-function metadata)) - (not (plist-get completion-extra-properties :annotation-function)) - (not (plist-get completion-extra-properties :affixation-function))) - (setcdr ann (append (cddr ann) (list (cadr ann))))) - (message "Marginalia: Use annotator `%s' for category `%s'" (cadr ann) (car ann))))) - -;; Emacs 28: Only show `marginalia-cycle' in M-x in recursive minibuffers -(put #'marginalia-cycle 'completion-predicate - (lambda (&rest _) (> (minibuffer-depth) 1))) - -(provide 'marginalia) -;;; marginalia.el ends here blob - 82bb2e880a0036d1768d94f3e97076dadc922961 (mode 644) blob + /dev/null --- elpa/marginalia-1.5/marginalia.info +++ /dev/null @@ -1,276 +0,0 @@ -This is doc5nyIuM.info, produced by makeinfo version 6.8 from -marginalia.texi. - -INFO-DIR-SECTION Emacs misc features -START-INFO-DIR-ENTRY -* Marginalia: (marginalia). Marginalia in the minibuffer. -END-INFO-DIR-ENTRY - - -File: doc5nyIuM.info, Node: Top, Next: Configuration, Up: (dir) - -marginalia.el - Marginalia in the minibuffer -******************************************** - -This package provides ‘marginalia-mode’ which adds marginalia to the -minibuffer completions. Marginalia -(https://en.wikipedia.org/wiki/Marginalia) are marks or annotations -placed at the margin of the page of a book or in this case helpful -colorful annotations placed at the margin of the minibuffer for your -completion candidates. Marginalia can only add annotations to the -completion candidates. It cannot modify the appearance of the -candidates themselves, which are shown unaltered as supplied by the -original command. - - The annotations are added based on the completion category. For -example ‘find-file’ reports the ‘file’ category and ‘M-x’ reports the -‘command’ category. You can cycle between more or less detailed -annotators or even disable the annotator with command -‘marginalia-cycle’. - -* Menu: - -* Configuration:: -* Information shown by the annotators:: -* Adding custom annotators or classifiers:: -* Disabling annotators, builtin or lightweight annotators: Disabling annotators builtin or lightweight annotators. -* Icons in the minibuffer:: -* Contributions:: - - -File: doc5nyIuM.info, Node: Configuration, Next: Information shown by the annotators, Prev: Top, Up: Top - -1 Configuration -*************** - -It is recommended to use Marginalia together with either the Vertico -(https://github.com/minad/vertico), Mct -(https://github.com/protesilaos/mct), Icomplete -(https://www.gnu.org/software/emacs/manual/html_node/emacs/Icomplete.html) -or the default completion UI. Furthermore Marginalia can be combined -with Embark (https://github.com/oantolin/embark) for action support and -Consult (https://github.com/minad/consult), which provides many useful -commands. - - ;; Enable rich annotations using the Marginalia package - (use-package marginalia - ;; Bind `marginalia-cycle' locally in the minibuffer. To make the binding - ;; available in the *Completions* buffer, add it to the - ;; `completion-list-mode-map'. - :bind (:map minibuffer-local-map - ("M-A" . marginalia-cycle)) - - ;; The :init section is always executed. - :init - - ;; Marginalia must be activated in the :init section of use-package such that - ;; the mode gets enabled right away. Note that this forces loading the - ;; package. - (marginalia-mode)) - - -File: doc5nyIuM.info, Node: Information shown by the annotators, Next: Adding custom annotators or classifiers, Prev: Configuration, Up: Top - -2 Information shown by the annotators -************************************* - -In general, to learn more about what different annotations mean, a good -starting point is to look at ‘marginalia-annotator-registry’, and follow -up to the annotation function of the category you are interested in. - - For example the annotations for Elisp symbols include their symbol -class - ‘v’ for variable, ‘f’ for function, ‘c’ for command, etc. For -more information on what the different classifications mean, see the -docstring of ‘marginalia--symbol-class’. - - -File: doc5nyIuM.info, Node: Adding custom annotators or classifiers, Next: Disabling annotators builtin or lightweight annotators, Prev: Information shown by the annotators, Up: Top - -3 Adding custom annotators or classifiers -***************************************** - -*IMPORTANT NOTICE FOR PACKAGE AUTHORS*: The intention of the Marginalia -package is to give the user means to overwrite completion categories and -to add custom annotators for existing commands in their user -configuration. *Marginalia is a user facing package and is not intended -to be used as a library*. Therefore Marginalia does not expose library -functions as part of its public API. If you add your own completion -commands to your package we recommend to specify an -‘annotation-function’ or an ‘affixation-function’, avoiding the -Marginalia dependency this way. The ‘annotation-function’ and -‘affixation-function’ are documented in the Elisp manual -(https://www.gnu.org/software/emacs/manual/html_node/elisp/Completion.html). -If you use ‘consult--read’, you can specify an ‘:annotate’ keyword -argument. There is an exception to our recommendation: If you want to -implement annotations for an existing package ‘hypothetic.el’, which -does not have annotations and where annotations cannot be added, then -the creation of a ‘marginalia-hypothetic.el’ package is a good idea, -since Marginalia provides the facilities to enhance existing commands -from the outside. If you have questions feel free to ask on the -Marginalia issue tracker. - - Commands that support minibuffer completion use a completion table of -all the available candidates. Candidates are associated with a -*category* such as ‘command’, ‘file’, ‘face’, or ‘variable’ depending on -what the candidates are. Based on the category of the candidates, -Marginalia selects an *annotator* to generate annotations for display -for each candidate. - - Unfortunately, not all commands (including Emacs’ builtin ones) -specify the category of their candidates. To compensate for this -shortcoming, Marginalia hooks into the Emacs completion framework and -runs the *classifiers* listed in the variable ‘marginalia-classifiers’, -which use the command’s prompt or other properties of the candidates to -specify the completion category. - - For example, the ‘marginalia-classify-by-prompt’ classifier checks -the minibuffer prompt against regexps listed in the -‘marginalia-prompt-categories’ alist to determine a category. The -following is already included but would be a way to assign the category -‘face’ to all candidates from commands with prompts that include the -word "face". - - (add-to-list 'marginalia-prompt-categories '("\\" . face)) - - The ‘marginalia-classify-by-command-name’ classifier uses the alist -‘marginalia-command-categories’ to specify the completion category based -on the command name. This is particularly useful if the prompt -classifier yields a false positive. - - Completion categories are also important for Embark -(https://github.com/oantolin/embark), which associates actions based on -the completion category and benefits from Marginalia’s classifiers. - - Once the category of the candidates is known, Marginalia looks in the -‘marginalia-annotator-registry’ to find the associated annotator to use. -An annotator is a function that takes a completion candidate string as -an argument and returns an annotation string to be displayed after the -candidate in the minibuffer. More than one annotator can be assigned to -each each category, displaying more, less or different information. Use -the ‘marginalia-cycle’ command to cycle between the annotations of -different annotators defined for the current category. - - Here’s an example of a basic face annotator: - - (defun my-face-annotator (cand) - (when-let (sym (intern-soft cand)) - (concat (propertize " " 'display '(space :align-to center)) - (propertize "The quick brown fox jumps over the lazy dog" 'face sym)))) - - Look at Marginalia’s various annotators for examples of formatting -annotations. In particular, the helper function ‘marginalia--fields’ -can be used to format information into columns. - - After defining a new annotator, associate it with a category in the -annotator registry as follows: - - (add-to-list 'marginalia-annotator-registry - '(face my-face-annotator marginalia-annotate-face builtin none)) - - This makes the ‘my-face-annotator’ the first of four annotators for -the face category. The others are the annotator provided by Marginalia -(‘marginalia-annotate-face’), the ‘builtin’ annotator as defined by -Emacs and the ‘none’ annotator, which disables the annotations. With -this setting, after invoking ‘M-x describe-face RET’ you can cycle -between all of these annotators using ‘marginalia-cycle’. - - -File: doc5nyIuM.info, Node: Disabling annotators builtin or lightweight annotators, Next: Icons in the minibuffer, Prev: Adding custom annotators or classifiers, Up: Top - -4 Disabling annotators, builtin or lightweight annotators -********************************************************* - -Marginalia activates rich annotators by default. Depending on your -preference you may want to use the builtin annotators or even no -annotators by default and only activate the annotators on demand by -invoking ‘marginalia-cycle’. - - In order to use the builtin annotators by default, you can use the -following command. Replace ‘builtin’ by ‘none’ to disable annotators by -default. - - (defun marginalia-use-builtin () - (interactive) - (mapc - (lambda (x) - (setcdr x (cons 'builtin (remq 'builtin (cdr x))))) - marginalia-annotator-registry)) - - If a completion category supports two annotators, you can toggle -between those using this command. - - (defun marginalia-toggle () - (interactive) - (mapc - (lambda (x) - (setcdr x (append (reverse (remq 'none - (remq 'builtin (cdr x)))) - '(builtin none)))) - marginalia-annotator-registry)) - - After cycling the annotators you may want to automatically save the -configuration. This can be achieved using an advice which calls -‘customize-save-variable’. - - (advice-add #'marginalia-cycle :after - (lambda () - (let ((inhibit-message t)) - (customize-save-variable 'marginalia-annotator-registry - marginalia-annotator-registry)))) - - In order to disable an annotator permanently, the -‘marginalia-annotator-registry’ can be modified. For example if you -prefer to never see file annotations, you can delete all file annotators -from the registry. - - (setq marginalia-annotator-registry - (assq-delete-all 'file marginalia-annotator-registry)) - - -File: doc5nyIuM.info, Node: Icons in the minibuffer, Next: Contributions, Prev: Disabling annotators builtin or lightweight annotators, Up: Top - -5 Icons in the minibuffer -************************* - -Icons in the minibuffer completion UI are a commonly requested feature. -Marginalia focuses on text annotations only. The following packages are -compatible with Marginalia and use special fonts to add icons in front -of completion candidates. There also exist related packages to enhance -Dired, Ibuffer and other modes with icons consistently. - - • all-the-icons-completion - (https://github.com/iyefrat/all-the-icons-completion): Relies on - the ‘all-the-icons.el’ package which configures multiple icon - fonts. - • nerd-icons-completion - (https://github.com/rainstormstudio/nerd-icons-completion): Relies - on patched fonts including icons. This package works even in the - terminal where only a single font can be used. - - -File: doc5nyIuM.info, Node: Contributions, Prev: Icons in the minibuffer, Up: Top - -6 Contributions -*************** - -Since this package is part of GNU ELPA -(https://elpa.gnu.org/packages/marginalia.html) contributions require a -copyright assignment to the FSF. - - - -Tag Table: -Node: Top216 -Node: Configuration1478 -Node: Information shown by the annotators2706 -Node: Adding custom annotators or classifiers3418 -Node: Disabling annotators builtin or lightweight annotators8334 -Node: Icons in the minibuffer10385 -Node: Contributions11344 - -End Tag Table - - -Local Variables: -coding: utf-8 -End: blob - /dev/null blob + c9805fa766993a0d5a7aa1ad1634045a9840f706 (mode 644) --- /dev/null +++ elpa/marginalia-1.6/.elpaignore @@ -0,0 +1,2 @@ +LICENSE +.github \ No newline at end of file blob - /dev/null blob + c6b75342ad162ff08c5a7e0431516930aba25141 (mode 644) --- /dev/null +++ elpa/marginalia-1.6/CHANGELOG.org @@ -0,0 +1,49 @@ +#+title: marginalia.el - Changelog +#+author: Omar Antolín Camarena, Daniel Mendler +#+language: en + +* Version 1.6 (2024-04-04) + +- ~marginalia-annotate-buffer~: Handle dead buffers, which can occur when + annotating the candidates of ~consult-buffer~, which maintains the actual buffer + objects during completion. + +* Version 1.5 (2023-12-27) + +- ~marginalia-annotate-bookmark~: Show location and more context. +- ~marginalia-annotate-tab~: Show tab index starting from one. + +* Version 1.4 (2023-12-01) + +- =marginalia-annotate-theme=: New annotator based on =marginalia-annotate-library=. +- =marginalia-remote-file-regexps=: New customization variable set to a list of + regexps matching remote paths, which should be excluded from file annotations. + +* Version 1.3 (2023-07-02) + +- =marginalia-classify-by-prompt=: Use case-insensitive matching. +- =marginalia-annotate-symbol=: Additional symbol classes. Use =M= for module + functions, =P= for primitives and =S= for special forms. +- =marginalia-annotate-symbol=: Add =symbol-file= column. +- =marginalia-cycle=: Add =completion-predicate= to display command only in + recursive minibuffers. + +* Version 1.2 (2023-04-17) + +- =marginalia-classify-by-command-name=: Resolve function aliases and use the name + of the original command to determine the completion category. + +* Version 1.1 (2023-02-17) + +- Require the =compat= library. +- Fix =marginalia-classify-by-prompt= such that it handles multiple brackets in + the prompt gracefully. +- Add =help-echo= properties to truncated annotations. The full string is shown on + mouse hover. +- Add =help-echo= to the symbol classes of =marginalia-annotate-symbol=. +- Add =help-echo= to file sizes showing the exact size in bytes. +- Add =help-echo= to file dates showing the exact date. + +* Version 1.0 (2022-12-22) + +- Start of changelog. blob - /dev/null blob + 08a74a2dcd89a6fb84f9bf8ad5b55e19d6c30d8c (mode 644) --- /dev/null +++ elpa/marginalia-1.6/README-elpa @@ -0,0 +1,276 @@ + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + MARGINALIA.EL - MARGINALIA IN THE MINIBUFFER + ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + +This package provides `marginalia-mode' which adds marginalia to the +minibuffer completions. [Marginalia] are marks or annotations placed at +the margin of the page of a book or in this case helpful colorful +annotations placed at the margin of the minibuffer for your completion +candidates. Marginalia can only add annotations to the completion +candidates. It cannot modify the appearance of the candidates +themselves, which are shown unaltered as supplied by the original +command. + +The annotations are added based on the completion category. For example +`find-file' reports the `file' category and `M-x' reports the `command' +category. You can cycle between more or less detailed annotators or even +disable the annotator with command `marginalia-cycle'. + +Table of Contents +───────────────── + +1. Configuration +2. Information shown by the annotators +3. Adding custom annotators or classifiers +4. Disabling annotators, builtin or lightweight annotators +5. Icons in the minibuffer +6. Contributions + + +[Marginalia] + + +1 Configuration +═══════════════ + + It is recommended to use Marginalia together with either the + [Vertico], [Mct], [Icomplete] or the default completion + UI. Furthermore Marginalia can be combined with [Embark] for action + support and [Consult], which provides many useful commands. + + ┌──── + │ ;; Enable rich annotations using the Marginalia package + │ (use-package marginalia + │ ;; Bind `marginalia-cycle' locally in the minibuffer. To make the binding + │ ;; available in the *Completions* buffer, add it to the + │ ;; `completion-list-mode-map'. + │ :bind (:map minibuffer-local-map + │ ("M-A" . marginalia-cycle)) + │ + │ ;; The :init section is always executed. + │ :init + │ + │ ;; Marginalia must be activated in the :init section of use-package such that + │ ;; the mode gets enabled right away. Note that this forces loading the + │ ;; package. + │ (marginalia-mode)) + └──── + + +[Vertico] + +[Mct] + +[Icomplete] + + +[Embark] + +[Consult] + + +2 Information shown by the annotators +═════════════════════════════════════ + + In general, to learn more about what different annotations mean, a + good starting point is to look at `marginalia-annotator-registry', and + follow up to the annotation function of the category you are + interested in. + + For example the annotations for Elisp symbols include their symbol + class - `v' for variable, `f' for function, `c' for command, etc. For + more information on what the different classifications mean, see the + docstring of `marginalia--symbol-class'. + + +3 Adding custom annotators or classifiers +═════════════════════════════════════════ + + *IMPORTANT NOTICE FOR PACKAGE AUTHORS*: The intention of the + Marginalia package is to give the user means to overwrite completion + categories and to add custom annotators for existing commands in their + user configuration. *Marginalia is a user facing package and is not + intended to be used as a library*. Therefore Marginalia does not + expose library functions as part of its public API. If you add your + own completion commands to your package we recommend to specify an + `annotation-function' or an `affixation-function', avoiding the + Marginalia dependency this way. The `annotation-function' and + `affixation-function' are documented in the [Elisp manual]. If you use + `consult--read', you can specify an `:annotate' keyword + argument. There is an exception to our recommendation: If you want to + implement annotations for an existing package `hypothetic.el', which + does not have annotations and where annotations cannot be added, then + the creation of a `marginalia-hypothetic.el' package is a good idea, + since Marginalia provides the facilities to enhance existing commands + from the outside. If you have questions feel free to ask on the + Marginalia issue tracker. + + Commands that support minibuffer completion use a completion table of + all the available candidates. Candidates are associated with a + *category* such as `command', `file', `face', or `variable' depending + on what the candidates are. Based on the category of the candidates, + Marginalia selects an *annotator* to generate annotations for display + for each candidate. + + Unfortunately, not all commands (including Emacs' builtin ones) + specify the category of their candidates. To compensate for this + shortcoming, Marginalia hooks into the Emacs completion framework and + runs the *classifiers* listed in the variable + `marginalia-classifiers', which use the command's prompt or other + properties of the candidates to specify the completion category. + + For example, the `marginalia-classify-by-prompt' classifier checks the + minibuffer prompt against regexps listed in the + `marginalia-prompt-categories' alist to determine a category. The + following is already included but would be a way to assign the + category `face' to all candidates from commands with prompts that + include the word "face". + + ┌──── + │ (add-to-list 'marginalia-prompt-categories '("\\" . face)) + └──── + + The `marginalia-classify-by-command-name' classifier uses the alist + `marginalia-command-categories' to specify the completion category + based on the command name. This is particularly useful if the prompt + classifier yields a false positive. + + Completion categories are also important for [Embark], which + associates actions based on the completion category and benefits from + Marginalia's classifiers. + + Once the category of the candidates is known, Marginalia looks in the + `marginalia-annotator-registry' to find the associated annotator to + use. An annotator is a function that takes a completion candidate + string as an argument and returns an annotation string to be displayed + after the candidate in the minibuffer. More than one annotator can be + assigned to each each category, displaying more, less or different + information. Use the `marginalia-cycle' command to cycle between the + annotations of different annotators defined for the current category. + + Here's an example of a basic face annotator: + + ┌──── + │ (defun my-face-annotator (cand) + │ (when-let (sym (intern-soft cand)) + │ (concat (propertize " " 'display '(space :align-to center)) + │ (propertize "The quick brown fox jumps over the lazy dog" 'face sym)))) + └──── + + Look at Marginalia's various annotators for examples of formatting + annotations. In particular, the helper function `marginalia--fields' + can be used to format information into columns. + + After defining a new annotator, associate it with a category in the + annotator registry as follows: + + ┌──── + │ (add-to-list 'marginalia-annotator-registry + │ '(face my-face-annotator marginalia-annotate-face builtin none)) + └──── + + This makes the `my-face-annotator' the first of four annotators for + the face category. The others are the annotator provided by Marginalia + (`marginalia-annotate-face'), the `builtin' annotator as defined by + Emacs and the `none' annotator, which disables the annotations. With + this setting, after invoking `M-x describe-face RET' you can cycle + between all of these annotators using `marginalia-cycle'. + + +[Elisp manual] + + +[Embark] + + +4 Disabling annotators, builtin or lightweight annotators +═════════════════════════════════════════════════════════ + + Marginalia activates rich annotators by default. Depending on your + preference you may want to use the builtin annotators or even no + annotators by default and only activate the annotators on demand by + invoking `marginalia-cycle'. + + In order to use the builtin annotators by default, you can use the + following command. Replace `builtin' by `none' to disable annotators + by default. + + ┌──── + │ (defun marginalia-use-builtin () + │ (interactive) + │ (mapc + │ (lambda (x) + │ (setcdr x (cons 'builtin (remq 'builtin (cdr x))))) + │ marginalia-annotator-registry)) + └──── + + If a completion category supports two annotators, you can toggle + between those using this command. + + ┌──── + │ (defun marginalia-toggle () + │ (interactive) + │ (mapc + │ (lambda (x) + │ (setcdr x (append (reverse (remq 'none + │ (remq 'builtin (cdr x)))) + │ '(builtin none)))) + │ marginalia-annotator-registry)) + └──── + + After cycling the annotators you may want to automatically save the + configuration. This can be achieved using an advice which calls + `customize-save-variable'. + + ┌──── + │ (advice-add #'marginalia-cycle :after + │ (lambda () + │ (let ((inhibit-message t)) + │ (customize-save-variable 'marginalia-annotator-registry + │ marginalia-annotator-registry)))) + └──── + + In order to disable an annotator permanently, the + `marginalia-annotator-registry' can be modified. For example if you + prefer to never see file annotations, you can delete all file + annotators from the registry. + + ┌──── + │ (setq marginalia-annotator-registry + │ (assq-delete-all 'file marginalia-annotator-registry)) + └──── + + +5 Icons in the minibuffer +═════════════════════════ + + Icons in the minibuffer completion UI are a commonly requested + feature. Marginalia focuses on text annotations only. The following + packages are compatible with Marginalia and use special fonts to add + icons in front of completion candidates. There also exist related + packages to enhance Dired, Ibuffer and other modes with icons + consistently. + + • [all-the-icons-completion]: Relies on the `all-the-icons.el' package + which configures multiple icon fonts. + • [nerd-icons-completion]: Relies on patched fonts including + icons. This package works even in the terminal where only a single + font can be used. + + +[all-the-icons-completion] + + +[nerd-icons-completion] + + + +6 Contributions +═══════════════ + + Since this package is part of [GNU ELPA] contributions require a + copyright assignment to the FSF. + + +[GNU ELPA] blob - /dev/null blob + c83bc4ce76d15f9ce7a72e854d9895561f0c2e04 (mode 644) --- /dev/null +++ elpa/marginalia-1.6/README.org @@ -0,0 +1,222 @@ +#+title: marginalia.el - Marginalia in the minibuffer +#+author: Omar Antolín Camarena, Daniel Mendler +#+language: en +#+export_file_name: marginalia.texi +#+texinfo_dir_category: Emacs misc features +#+texinfo_dir_title: Marginalia: (marginalia). +#+texinfo_dir_desc: Marginalia in the minibuffer + +#+html: GNU Emacs +#+html: GNU ELPA +#+html: GNU-devel ELPA +#+html: MELPA +#+html: MELPA Stable + +#+html: + +This package provides =marginalia-mode= which adds marginalia to the minibuffer +completions. [[https://en.wikipedia.org/wiki/Marginalia][Marginalia]] are marks or annotations placed at the margin of the +page of a book or in this case helpful colorful annotations placed at the margin +of the minibuffer for your completion candidates. Marginalia can only add +annotations to the completion candidates. It cannot modify the appearance of the +candidates themselves, which are shown unaltered as supplied by the original +command. + +The annotations are added based on the completion category. For example +=find-file= reports the =file= category and =M-x= reports the =command= category. You +can cycle between more or less detailed annotators or even disable the annotator +with command =marginalia-cycle=. + +#+html: + +#+toc: headlines 8 + +* Configuration + +It is recommended to use Marginalia together with either the [[https://github.com/minad/vertico][Vertico]], [[https://github.com/protesilaos/mct][Mct]], +[[https://www.gnu.org/software/emacs/manual/html_node/emacs/Icomplete.html][Icomplete]] or the default completion UI. Furthermore Marginalia can be combined +with [[https://github.com/oantolin/embark][Embark]] for action support and [[https://github.com/minad/consult][Consult]], which provides many useful commands. + +#+begin_src emacs-lisp +;; Enable rich annotations using the Marginalia package +(use-package marginalia + ;; Bind `marginalia-cycle' locally in the minibuffer. To make the binding + ;; available in the *Completions* buffer, add it to the + ;; `completion-list-mode-map'. + :bind (:map minibuffer-local-map + ("M-A" . marginalia-cycle)) + + ;; The :init section is always executed. + :init + + ;; Marginalia must be activated in the :init section of use-package such that + ;; the mode gets enabled right away. Note that this forces loading the + ;; package. + (marginalia-mode)) +#+end_src + +* Information shown by the annotators + +In general, to learn more about what different annotations mean, a good starting +point is to look at ~marginalia-annotator-registry~, and follow up to the +annotation function of the category you are interested in. + +For example the annotations for Elisp symbols include their symbol class - =v= for +variable, =f= for function, =c= for command, etc. For more information on what the +different classifications mean, see the docstring of ~marginalia--symbol-class~. + +* Adding custom annotators or classifiers + +*IMPORTANT NOTICE FOR PACKAGE AUTHORS*: The intention of the Marginalia package is +to give the user means to overwrite completion categories and to add custom +annotators for existing commands in their user configuration. *Marginalia is a +user facing package and is not intended to be used as a library*. Therefore +Marginalia does not expose library functions as part of its public API. If you +add your own completion commands to your package we recommend to specify an +=annotation-function= or an =affixation-function=, avoiding the Marginalia +dependency this way. The =annotation-function= and =affixation-function= are +documented in the [[https://www.gnu.org/software/emacs/manual/html_node/elisp/Completion.html][Elisp manual]]. If you use =consult--read=, you can specify an +=:annotate= keyword argument. There is an exception to our recommendation: If you +want to implement annotations for an existing package =hypothetic.el=, which does +not have annotations and where annotations cannot be added, then the creation of +a =marginalia-hypothetic.el= package is a good idea, since Marginalia provides the +facilities to enhance existing commands from the outside. If you have questions +feel free to ask on the Marginalia issue tracker. + +Commands that support minibuffer completion use a completion table of all the +available candidates. Candidates are associated with a *category* such as =command=, +=file=, =face=, or =variable= depending on what the candidates are. Based on the +category of the candidates, Marginalia selects an *annotator* to generate +annotations for display for each candidate. + +Unfortunately, not all commands (including Emacs' builtin ones) specify the +category of their candidates. To compensate for this shortcoming, Marginalia +hooks into the Emacs completion framework and runs the *classifiers* listed in the +variable =marginalia-classifiers=, which use the command's prompt or other +properties of the candidates to specify the completion category. + +For example, the =marginalia-classify-by-prompt= classifier checks the minibuffer +prompt against regexps listed in the =marginalia-prompt-categories= alist to +determine a category. The following is already included but would be a way to +assign the category =face= to all candidates from commands with prompts that +include the word "face". + +#+begin_src emacs-lisp + (add-to-list 'marginalia-prompt-categories '("\\" . face)) +#+end_src + +The =marginalia-classify-by-command-name= classifier uses the alist +=marginalia-command-categories= to specify the completion category based on the +command name. This is particularly useful if the prompt classifier yields a +false positive. + +Completion categories are also important for [[https://github.com/oantolin/embark][Embark]], which associates actions +based on the completion category and benefits from Marginalia's classifiers. + +Once the category of the candidates is known, Marginalia looks in the +=marginalia-annotator-registry= to find the associated annotator to use. An +annotator is a function that takes a completion candidate string as an argument +and returns an annotation string to be displayed after the candidate in the +minibuffer. More than one annotator can be assigned to each each category, +displaying more, less or different information. Use the =marginalia-cycle= command +to cycle between the annotations of different annotators defined for the current +category. + +Here's an example of a basic face annotator: + +#+begin_src emacs-lisp + (defun my-face-annotator (cand) + (when-let (sym (intern-soft cand)) + (concat (propertize " " 'display '(space :align-to center)) + (propertize "The quick brown fox jumps over the lazy dog" 'face sym)))) +#+end_src + +Look at Marginalia's various annotators for examples of formatting annotations. +In particular, the helper function =marginalia--fields= can be used to format +information into columns. + +After defining a new annotator, associate it with a category in the annotator +registry as follows: + +#+begin_src emacs-lisp + (add-to-list 'marginalia-annotator-registry + '(face my-face-annotator marginalia-annotate-face builtin none)) +#+end_src + +This makes the =my-face-annotator= the first of four annotators for the face +category. The others are the annotator provided by Marginalia +(=marginalia-annotate-face=), the =builtin= annotator as defined by Emacs and the +=none= annotator, which disables the annotations. With this setting, after +invoking =M-x describe-face RET= you can cycle between all of these annotators +using =marginalia-cycle=. + +* Disabling annotators, builtin or lightweight annotators + +Marginalia activates rich annotators by default. Depending on your preference +you may want to use the builtin annotators or even no annotators by default and +only activate the annotators on demand by invoking ~marginalia-cycle~. + +In order to use the builtin annotators by default, you can use the following +command. Replace =builtin= by =none= to disable annotators by default. + +#+begin_src emacs-lisp + (defun marginalia-use-builtin () + (interactive) + (mapc + (lambda (x) + (setcdr x (cons 'builtin (remq 'builtin (cdr x))))) + marginalia-annotator-registry)) +#+end_src + +If a completion category supports two annotators, you can toggle between +those using this command. + +#+begin_src emacs-lisp + (defun marginalia-toggle () + (interactive) + (mapc + (lambda (x) + (setcdr x (append (reverse (remq 'none + (remq 'builtin (cdr x)))) + '(builtin none)))) + marginalia-annotator-registry)) +#+end_src + +After cycling the annotators you may want to automatically save the +configuration. This can be achieved using an advice which calls +~customize-save-variable~. + +#+begin_src emacs-lisp + (advice-add #'marginalia-cycle :after + (lambda () + (let ((inhibit-message t)) + (customize-save-variable 'marginalia-annotator-registry + marginalia-annotator-registry)))) +#+end_src + +In order to disable an annotator permanently, the ~marginalia-annotator-registry~ +can be modified. For example if you prefer to never see file annotations, you +can delete all file annotators from the registry. + +#+begin_src emacs-lisp + (setq marginalia-annotator-registry + (assq-delete-all 'file marginalia-annotator-registry)) +#+end_src + +* Icons in the minibuffer + +Icons in the minibuffer completion UI are a commonly requested feature. +Marginalia focuses on text annotations only. The following packages are +compatible with Marginalia and use special fonts to add icons in front of +completion candidates. There also exist related packages to enhance Dired, +Ibuffer and other modes with icons consistently. + +- [[https://github.com/iyefrat/all-the-icons-completion][all-the-icons-completion]]: Relies on the =all-the-icons.el= package which + configures multiple icon fonts. +- [[https://github.com/rainstormstudio/nerd-icons-completion][nerd-icons-completion]]: Relies on patched fonts including icons. This package + works even in the terminal where only a single font can be used. + +* Contributions + +Since this package is part of [[https://elpa.gnu.org/packages/marginalia.html][GNU ELPA]] contributions require a copyright +assignment to the FSF. blob - /dev/null blob + 1a17acbe202dc8c9ba6ce8ec3ace936c639a3a60 (mode 644) --- /dev/null +++ elpa/marginalia-1.6/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 +* Marginalia: (marginalia). Marginalia in the minibuffer. blob - /dev/null blob + 6e9e8e65088e90220f855b618dc5891d4ba49765 (mode 644) --- /dev/null +++ elpa/marginalia-1.6/marginalia-autoloads.el @@ -0,0 +1,56 @@ +;;; marginalia-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 marginalia.el + +(defvar marginalia-mode nil "\ +Non-nil if Marginalia mode is enabled. +See the `marginalia-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 `marginalia-mode'.") +(custom-autoload 'marginalia-mode "marginalia" nil) +(autoload 'marginalia-mode "marginalia" "\ +Annotate completion candidates with richer information. + +This is a global minor mode. If called interactively, toggle the +`Marginalia 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 \\='marginalia-mode)'. + +The mode's hook is called both when the mode is enabled and when +it is disabled. + +(fn &optional ARG)" t) +(autoload 'marginalia-cycle "marginalia" "\ +Cycle between annotators in `marginalia-annotator-registry'." t) +(register-definition-prefixes "marginalia" '("marginalia-")) + +;;; End of scraped data + +(provide 'marginalia-autoloads) + +;; Local Variables: +;; version-control: never +;; no-byte-compile: t +;; no-update-autoloads: t +;; no-native-compile: t +;; coding: utf-8-emacs-unix +;; End: + +;;; marginalia-autoloads.el ends here blob - /dev/null blob + b5156d4e65758168ae3383131ef6cfbe694b832d (mode 644) --- /dev/null +++ elpa/marginalia-1.6/marginalia-pkg.el @@ -0,0 +1,2 @@ +;; Generated package description from marginalia.el -*- no-byte-compile: t -*- +(define-package "marginalia" "1.6" "Enrich existing commands with completion annotations" '((emacs "27.1") (compat "29.1.4.4")) :commit "58eb5fd6e5cc21b12c5455ae69e7ae93579647bc" :authors '(("Omar Antolín Camarena" . "omar@matem.unam.mx") ("Daniel Mendler" . "mail@daniel-mendler.de")) :maintainer '(("Omar Antolín Camarena" . "omar@matem.unam.mx") ("Daniel Mendler" . "mail@daniel-mendler.de")) :keywords '("docs" "help" "matching" "completion") :url "https://github.com/minad/marginalia") blob - /dev/null blob + f912a33eff4f43afe491e700b4a6d6ddfd33d7da (mode 644) --- /dev/null +++ elpa/marginalia-1.6/marginalia.el @@ -0,0 +1,1361 @@ +;;; marginalia.el --- Enrich existing commands with completion annotations -*- lexical-binding: t -*- + +;; Copyright (C) 2021-2024 Free Software Foundation, Inc. + +;; Author: Omar Antolín Camarena , Daniel Mendler +;; Maintainer: Omar Antolín Camarena , Daniel Mendler +;; Created: 2020 +;; Version: 1.6 +;; Package-Requires: ((emacs "27.1") (compat "29.1.4.4")) +;; Homepage: https://github.com/minad/marginalia +;; Keywords: docs, help, matching, completion + +;; This file is 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: + +;; Enrich existing commands with completion annotations + +;;; Code: + +(require 'compat) +(eval-when-compile + (require 'subr-x) + (require 'cl-lib)) + +;;;; Customization + +(defgroup marginalia nil + "Enrich existing commands with completion annotations." + :link '(info-link :tag "Info Manual" "(marginalia)") + :link '(url-link :tag "Homepage" "https://github.com/minad/marginalia") + :link '(emacs-library-link :tag "Library Source" "marginalia.el") + :group 'help + :group 'docs + :group 'minibuffer + :prefix "marginalia-") + +(defcustom marginalia-field-width 80 + "Maximum truncation width of annotation fields. + +This value is adjusted depending on the `window-width'." + :type 'natnum) + +(defcustom marginalia-separator " " + "Annotation field separator." + :type 'string) + +(defcustom marginalia-align 'left + "Alignment of the annotations." + :type '(choice (const :tag "Left" left) + (const :tag "Center" center) + (const :tag "Right" right))) + +(defcustom marginalia-align-offset 0 + "Additional offset added to the alignment." + :type 'natnum) + +(defcustom marginalia-max-relative-age (* 60 60 24 14) + "Maximum relative age in seconds displayed by the file annotator. + +Set to `most-positive-fixnum' to always use a relative age, or 0 to never show +a relative age." + :type 'natnum) + +(defcustom marginalia-remote-file-regexps + '("\\`/\\([^/|:]+\\):") ;; Tramp path + "List of remote file regexps where the files should not be annotated. + +The first match group is displayed instead of the detailed file +attribute information. For Tramp paths, the protocol is +displayed instead." + :type '(repeat regexp)) + +(defcustom marginalia-annotator-registry + (mapcar + (lambda (x) (append x '(builtin none))) + `((command ,#'marginalia-annotate-command ,#'marginalia-annotate-binding) + (embark-keybinding ,#'marginalia-annotate-embark-keybinding) + (customize-group ,#'marginalia-annotate-customize-group) + (variable ,#'marginalia-annotate-variable) + (function ,#'marginalia-annotate-function) + (face ,#'marginalia-annotate-face) + (color ,#'marginalia-annotate-color) + (unicode-name ,#'marginalia-annotate-char) + (minor-mode ,#'marginalia-annotate-minor-mode) + (symbol ,#'marginalia-annotate-symbol) + (environment-variable ,#'marginalia-annotate-environment-variable) + (input-method ,#'marginalia-annotate-input-method) + (coding-system ,#'marginalia-annotate-coding-system) + (charset ,#'marginalia-annotate-charset) + (package ,#'marginalia-annotate-package) + (imenu ,#'marginalia-annotate-imenu) + (bookmark ,#'marginalia-annotate-bookmark) + (file ,#'marginalia-annotate-file) + (project-file ,#'marginalia-annotate-project-file) + (buffer ,#'marginalia-annotate-buffer) + (library ,#'marginalia-annotate-library) + (theme ,#'marginalia-annotate-theme) + (tab ,#'marginalia-annotate-tab) + (multi-category ,#'marginalia-annotate-multi-category))) + "Annotator function registry. +Associates completion categories with annotation functions. +Each annotation function must return a string, +which is appended to the completion candidate." + :type '(alist :key-type symbol :value-type (repeat symbol))) + +(defcustom marginalia-classifiers + (list #'marginalia-classify-by-command-name + #'marginalia-classify-original-category + #'marginalia-classify-by-prompt + #'marginalia-classify-symbol) + "List of functions to determine current completion category. +Each function should take no arguments and return a symbol +indicating the category, or nil to indicate it could not +determine it." + :type 'hook) + +(defcustom marginalia-prompt-categories + '(("\\" . customize-group) + ("\\" . command) + ("\\" . package) + ("\\" . bookmark) + ("\\" . color) + ("\\" . face) + ("\\" . environment-variable) + ("\\" . function) + ("\\" . variable) + ("\\" . input-method) + ("\\" . charset) + ("\\" . coding-system) + ("\\" . minor-mode) + ("\\" . kill-ring) + ("\\" . tab) + ("\\" . library) + ("\\" . theme)) + "Associates regexps to match against minibuffer prompts with categories. +The prompts are matched case-insensitively." + :type '(alist :key-type regexp :value-type symbol)) + +(defcustom marginalia-censor-variables + '("pass\\|auth-source-netrc-cache\\|auth-source-.*-nonce\\|api-?key") + "The value of variables matching any of these regular expressions is not shown. +This configuration variable is useful to hide variables which may +hold sensitive data, e.g., passwords. The variable names are +matched case-sensitively." + :type '(repeat (choice symbol regexp))) + +(defcustom marginalia-command-categories + '((imenu . imenu) + (recentf-open . file) + (where-is . command)) + "Associate commands with a completion category. +The value of `this-command' is used as key for the lookup." + :type '(alist :key-type symbol :value-type symbol)) + +(defgroup marginalia-faces nil + "Faces used by `marginalia-mode'." + :group 'marginalia + :group 'faces) + +(defface marginalia-key + '((t :inherit font-lock-keyword-face)) + "Face used to highlight keys.") + +(defface marginalia-type + '((t :inherit marginalia-key)) + "Face used to highlight types.") + +(defface marginalia-char + '((t :inherit marginalia-key)) + "Face used to highlight character annotations.") + +(defface marginalia-lighter + '((t :inherit marginalia-size)) + "Face used to highlight minor mode lighters.") + +(defface marginalia-on + '((t :inherit success)) + "Face used to signal enabled modes.") + +(defface marginalia-off + '((t :inherit error)) + "Face used to signal disabled modes.") + +(defface marginalia-documentation + '((t :inherit completions-annotations)) + "Face used to highlight documentation strings.") + +(defface marginalia-value + '((t :inherit marginalia-key)) + "Face used to highlight general variable values.") + +(defface marginalia-null + '((t :inherit font-lock-comment-face)) + "Face used to highlight null or unbound variable values.") + +(defface marginalia-true + '((t :inherit font-lock-builtin-face)) + "Face used to highlight true variable values.") + +(defface marginalia-function + '((t :inherit font-lock-function-name-face)) + "Face used to highlight function symbols.") + +(defface marginalia-symbol + '((t :inherit font-lock-type-face)) + "Face used to highlight general symbols.") + +(defface marginalia-list + '((t :inherit font-lock-constant-face)) + "Face used to highlight list expressions.") + +(defface marginalia-mode + '((t :inherit marginalia-key)) + "Face used to highlight buffer major modes.") + +(defface marginalia-date + '((t :inherit marginalia-key)) + "Face used to highlight dates.") + +(defface marginalia-version + '((t :inherit marginalia-number)) + "Face used to highlight package versions.") + +(defface marginalia-archive + '((t :inherit warning)) + "Face used to highlight package archives.") + +(defface marginalia-installed + '((t :inherit success)) + "Face used to highlight the status of packages.") + +(defface marginalia-size + '((t :inherit marginalia-number)) + "Face used to highlight sizes.") + +(defface marginalia-number + '((t :inherit font-lock-constant-face)) + "Face used to highlight numeric values.") + +(defface marginalia-string + '((t :inherit font-lock-string-face)) + "Face used to highlight string values.") + +(defface marginalia-modified + '((t :inherit font-lock-negation-char-face)) + "Face used to highlight buffer modification indicators.") + +(defface marginalia-file-name + '((t :inherit marginalia-documentation)) + "Face used to highlight file names.") + +(defface marginalia-file-owner + '((t :inherit font-lock-preprocessor-face)) + "Face used to highlight file owner and group names.") + +(defface marginalia-file-priv-no + '((t :inherit shadow)) + "Face used to highlight the no file privilege attribute.") + +(defface marginalia-file-priv-dir + '((t :inherit font-lock-keyword-face)) + "Face used to highlight the dir file privilege attribute.") + +(defface marginalia-file-priv-link + '((t :inherit font-lock-keyword-face)) + "Face used to highlight the link file privilege attribute.") + +(defface marginalia-file-priv-read + '((t :inherit font-lock-type-face)) + "Face used to highlight the read file privilege attribute.") + +(defface marginalia-file-priv-write + '((t :inherit font-lock-builtin-face)) + "Face used to highlight the write file privilege attribute.") + +(defface marginalia-file-priv-exec + '((t :inherit font-lock-function-name-face)) + "Face used to highlight the exec file privilege attribute.") + +(defface marginalia-file-priv-other + '((t :inherit font-lock-constant-face)) + "Face used to highlight some other file privilege attribute.") + +(defface marginalia-file-priv-rare + '((t :inherit font-lock-variable-name-face)) + "Face used to highlight a rare file privilege attribute.") + +;;;; Pre-declarations for external packages + +(declare-function bookmark-prop-get "bookmark") + +(declare-function project-current "project") + +(defvar package--builtins) +(defvar package-archive-contents) +(declare-function package--from-builtin "package") +(declare-function package-desc-archive "package") +(declare-function package-desc-status "package") +(declare-function package-desc-summary "package") +(declare-function package-desc-version "package") +(declare-function package-version-join "package") + +(declare-function color-rgb-to-hex "color") +(declare-function color-rgb-to-hsl "color") +(declare-function color-hsl-to-rgb "color") + +;;;; Marginalia mode + +(defvar marginalia--pangram "Cwm fjord bank glyphs vext quiz.") + +(defvar marginalia--bookmark-type-transforms + (let ((words (regexp-opt '("handle" "handler" "jump" "bookmark")))) + `((,(format "-+%s-+" words) . "-") + (,(format "\\`%s-+" words) . "") + (,(format "-%s\\'" words) . "") + ("\\`default\\'" . "File") + (".*" . ,#'capitalize))) + "List of bookmark type transformers. +Relying on this mechanism is discouraged in favor of the +`bookmark-handler-type' property. The function names are matched +case-sensitively.") + +(defvar marginalia--cand-width-step 10 + "Round candidate width.") + +(defvar-local marginalia--cand-width-max 20 + "Maximum width of candidates.") + +(defvar marginalia--fontified-file-modes nil + "List of fontified file modes.") + +(defvar-local marginalia--cache nil + "The cache, pair of list and hashtable.") + +(defvar marginalia--cache-size 100 + "Size of the cache, set to 0 to disable the cache. +Disabling the cache is useful on non-incremental UIs like default completion or +for performance profiling of the annotators.") + +(defvar-local marginalia--command nil + "Last command symbol saved in order to allow annotations.") + +(defvar-local marginalia--base-position 0 + "Last completion base position saved to get full file paths.") + +(defvar marginalia--metadata nil + "Completion metadata from the current completion.") + +(defvar marginalia--ellipsis nil) +(defun marginalia--ellipsis () + "Return ellipsis." + (with-memoization marginalia--ellipsis + (cond + ((bound-and-true-p truncate-string-ellipsis)) + ((char-displayable-p ?…) "…") + ("...")))) + +(defun marginalia--truncate (str width) + "Truncate string STR to WIDTH." + (when (floatp width) (setq width (round (* width marginalia-field-width)))) + (when-let (pos (string-search "\n" str)) + (setq str (substring str 0 pos))) + (let* ((face (and (not (equal str "")) + (get-text-property (1- (length str)) 'face str))) + (ell (if face + (propertize (marginalia--ellipsis) 'face face) + (marginalia--ellipsis))) + (trunc + (if (< width 0) + (nreverse (truncate-string-to-width (reverse str) (- width) 0 ?\s ell)) + (truncate-string-to-width str width 0 ?\s ell)))) + (unless (string-prefix-p str trunc) + (put-text-property 0 (length trunc) 'help-echo str trunc)) + trunc)) + +(cl-defmacro marginalia--field (field &key truncate face width format) + "Format FIELD as a string according to some options. +TRUNCATE is the truncation width. +WIDTH is the field width. +FORMAT is a format string. +FACE is the name of the face, with which the field should be propertized." + (setq field (if format `(format ,format ,field) `(or ,field ""))) + (when width (setq field `(format ,(format "%%%ds" (- width)) ,field))) + (when truncate (setq field `(marginalia--truncate ,field ,truncate))) + (when face (setq field `(propertize ,field 'face ,face))) + field) + +(defmacro marginalia--fields (&rest fields) + "Format annotation FIELDS as a string with separators in between." + (let ((left t)) + (cons 'concat + (mapcan + (lambda (field) + (if (not (eq (car field) :left)) + `(,@(when left (setq left nil) `(#(" " 0 1 (marginalia--align t)))) + marginalia-separator (marginalia--field ,@field)) + (unless left (error "Left fields must come first")) + `((marginalia--field ,@(cdr field))))) + fields)))) + +(defmacro marginalia--in-minibuffer (&rest body) + "Run BODY inside minibuffer if minibuffer is active. +Otherwise stay within current buffer." + (declare (indent 0)) + `(with-current-buffer (if-let (win (active-minibuffer-window)) + (window-buffer win) + (current-buffer)) + ,@body)) + +(defun marginalia--documentation (str) + "Format documentation string STR." + (when str + (marginalia--fields + (str :truncate 1.0 :face 'marginalia-documentation)))) + +(defun marginalia-annotate-binding (cand) + "Annotate command CAND with keybinding." + (when-let ((sym (intern-soft cand)) + (key (and (commandp sym) (where-is-internal sym nil 'first-only)))) + (format #(" (%s)" 1 5 (face marginalia-key)) (key-description key)))) + +(defun marginalia--annotator (cat) + "Return annotation function for category CAT." + (pcase (car (alist-get cat marginalia-annotator-registry)) + ('none #'ignore) + ('builtin nil) + (fun fun))) + +(defun marginalia-annotate-multi-category (cand) + "Annotate multi-category CAND with the buffer class." + (if-let ((multi (get-text-property 0 'multi-category cand)) + (annotate (marginalia--annotator (car multi)))) + ;; Use the Marginalia annotator corresponding to the multi category. + (funcall annotate (cdr multi)) + ;; Apply the original annotation function on the original candidate, if + ;; there is one. Use `alist-get' instead of `completion-metadata-get' to + ;; bypass our `marginalia--completion-metadata-get' advice! + (when-let (annotate (alist-get 'annotation-function marginalia--metadata)) + (funcall annotate cand)))) + +(defconst marginalia--advice-regexp + (rx bos + (1+ (seq (? "This function has ") + (or ":before" ":after" ":around" ":override" + ":before-while" ":before-until" ":after-while" + ":after-until" ":filter-args" ":filter-return") + " advice: " (0+ nonl) "\n")) + "\n") + "Regexp to match lines about advice in function documentation strings.") + +;; Taken from advice--make-docstring, is this robust? +(defun marginalia--advised (fun) + "Return t if function FUN is advised." + (let ((flist (indirect-function fun))) + (advice--p (if (eq 'macro (car-safe flist)) (cdr flist) flist)))) + +(defun marginalia--symbol-class (s) + "Return symbol class characters for symbol S. + +This function is an extension of `help--symbol-class'. It returns +more fine-grained and more detailed symbol information. + +Function: +f function +c command +C interactive-only command +m macro +F special-form +M module function +P primitive +g cl-generic +p pure +s side-effect-free +@ autoloaded +! advised +- obsolete +& alias + +Variable: +u custom (U modified compared to global value) +v variable +l local (L modified compared to default value) +- obsolete +& alias + +Other: +a face +t cl-type" + (let ((class + (append + (when (fboundp s) + (list + (cond + ((get s 'pure) '("p" . "pure")) + ((get s 'side-effect-free) '("s" . "side-effect-free"))) + (cond + ((commandp s) + (if (get s 'interactive-only) + '("C" . "interactive-only command") + '("c" . "command"))) + ((cl-generic-p s) '("g" . "cl-generic")) + ((macrop (symbol-function s)) '("m" . "macro")) + ((special-form-p (symbol-function s)) '("F" . "special-form")) + ((subr-primitive-p (symbol-function s)) '("P" . "primitive")) + ((module-function-p (symbol-function s)) '("M" . "module function")) + (t '("f" . "function"))) + (and (autoloadp (symbol-function s)) '("@" . "autoload")) + (and (marginalia--advised s) '("!" . "advised")) + (and (symbolp (symbol-function s)) + (cons "&" (format "alias for `%s'" (symbol-function s)))) + (and (get s 'byte-obsolete-info) '("-" . "obsolete")))) + (when (boundp s) + (list + (when (local-variable-if-set-p s) + (if (ignore-errors + (not (equal (symbol-value s) + (default-value s)))) + '("L" . "local, modified from global") + '("l" . "local, unmodified"))) + (if (custom-variable-p s) + (if (ignore-errors + (not (equal (symbol-value s) + (eval (car (get s 'standard-value)))))) + '("U" . "custom, modified from standard") + '("u" . "custom, unmodified")) + '("v" . "variable")) + (and (not (eq (ignore-errors (indirect-variable s)) s)) + (cons "&" (format "alias for `%s'" (ignore-errors (indirect-variable s))))) + (and (get s 'byte-obsolete-variable) '("-" . "obsolete")))) + (list + (and (facep s) '("a" . "face")) + (and (get s 'cl--class) '("t" . "cl-type")))))) ;; cl-find-class, cl--find-class + (setq class (delq nil class)) + (propertize + (format " %-6s" (mapconcat #'car class "")) + 'help-echo + (mapconcat (pcase-lambda (`(,x . ,y)) (concat x " " y)) class "\n")))) + +(defun marginalia--function-doc (sym) + "Documentation string of function SYM." + (when-let (str (ignore-errors (documentation sym))) + (save-match-data + (if (string-match marginalia--advice-regexp str) + (substring str (match-end 0)) + str)))) + +;; Derived from elisp-get-fnsym-args-string +(defun marginalia--function-args (sym) + "Return function arguments for SYM." + (let ((tmp)) + (elisp-function-argstring + (cond + ((listp (setq tmp (gethash (indirect-function sym) + advertised-signature-table t))) + tmp) + ((setq tmp (help-split-fundoc + (ignore-errors (documentation sym t)) + sym)) + (substitute-command-keys (car tmp))) + ((setq tmp (help-function-arglist sym)) + (and + (if (and (stringp tmp) + (string-search "Arg list not available" tmp)) + ;; A shorter text fits better into the + ;; limited Marginalia space. + "[autoload]" + tmp))))))) + +(defun marginalia-annotate-symbol (cand) + "Annotate symbol CAND with its documentation string." + (when-let (sym (intern-soft cand)) + (marginalia--fields + (:left (marginalia-annotate-binding cand)) + ((marginalia--symbol-class sym) :face 'marginalia-type) + ((if (fboundp sym) (marginalia--function-doc sym) + (cl-loop + for doc in '(variable-documentation + face-documentation + group-documentation) + thereis (ignore-errors (documentation-property sym doc)))) + :truncate 1.0 :face 'marginalia-documentation) + ((abbreviate-file-name (or (symbol-file sym) "")) + :truncate -0.5 :face 'marginalia-file-name)))) + +(defun marginalia-annotate-command (cand) + "Annotate command CAND with its documentation string. +Similar to `marginalia-annotate-symbol', but does not show symbol class." + (when-let (sym (intern-soft cand)) + (concat + (marginalia-annotate-binding cand) + (marginalia--documentation (marginalia--function-doc sym))))) + +(defun marginalia-annotate-embark-keybinding (cand) + "Annotate Embark keybinding CAND with its documentation string. +Similar to `marginalia-annotate-command', but does not show the +keybinding since CAND includes it." + (when-let (cmd (get-text-property 0 'embark-command cand)) + (marginalia--documentation (marginalia--function-doc cmd)))) + +(defun marginalia-annotate-imenu (cand) + "Annotate imenu CAND with its documentation string." + (when (derived-mode-p 'emacs-lisp-mode) + ;; Strip until the last whitespace in order to support flat imenu + (marginalia-annotate-symbol (replace-regexp-in-string "\\`.* " "" cand)))) + +(defun marginalia-annotate-function (cand) + "Annotate function CAND with its documentation string." + (when-let (sym (intern-soft cand)) + (when (fboundp sym) + (marginalia--fields + (:left (marginalia-annotate-binding cand)) + ((marginalia--symbol-class sym) :face 'marginalia-type) + ((marginalia--function-args sym) :face 'marginalia-value + :truncate 0.5) + ((marginalia--function-doc sym) :truncate 1.0 + :face 'marginalia-documentation))))) + +(defun marginalia--variable-value (sym) + "Return the variable value of SYM as string." + (cond + ((not (boundp sym)) + (propertize "#" 'face 'marginalia-null)) + ((and marginalia-censor-variables + (let ((name (symbol-name sym)) + case-fold-search) + (cl-loop for r in marginalia-censor-variables + thereis (if (symbolp r) + (eq r sym) + (string-match-p r name))))) + (propertize "*****" + 'face 'marginalia-null + 'help-echo "Hidden due to `marginalia-censor-variables'")) + (t + (let ((val (symbol-value sym))) + (pcase val + ('nil (propertize "nil" 'face 'marginalia-null)) + ('t (propertize "t" 'face 'marginalia-true)) + ((pred keymapp) (propertize "#" 'face 'marginalia-value)) + ((pred bool-vector-p) (propertize "#" 'face 'marginalia-value)) + ((pred hash-table-p) (propertize "#" 'face 'marginalia-value)) + ((pred syntax-table-p) (propertize "#" 'face 'marginalia-value)) + ;; Emacs bug#53988: abbrev-table-p throws an error + ((and (pred vectorp) (guard (ignore-errors (abbrev-table-p val)))) + (propertize "#" 'face 'marginalia-value)) + ((pred char-table-p) (propertize "#" 'face 'marginalia-value)) + ;; Emacs 29 comes with callable objects or object closures (OClosures) + ((guard (and (fboundp 'oclosure-type) (oclosure-type val))) + (format (propertize "#" 'face 'marginalia-function) + (and (fboundp 'oclosure-type) (oclosure-type val)))) + ((pred byte-code-function-p) (propertize "#" 'face 'marginalia-function)) + ((and (pred functionp) (pred symbolp)) + ;; We are not consistent here, values are generally printed + ;; unquoted. But we make an exception for function symbols to visually + ;; distinguish them from symbols. I am not entirely happy with this, + ;; but we should not add quotation to every type. + (format (propertize "#'%s" 'face 'marginalia-function) val)) + ((pred recordp) (format (propertize "#" 'face 'marginalia-value) (type-of val))) + ((pred symbolp) (propertize (symbol-name val) 'face 'marginalia-symbol)) + ((pred numberp) (propertize (number-to-string val) 'face 'marginalia-number)) + (_ (let ((print-escape-newlines t) + (print-escape-control-characters t) + ;;(print-escape-multibyte t) + (print-level 3) + (print-length marginalia-field-width)) + (propertize + (replace-regexp-in-string + ;; `print-escape-control-characters' does not escape Unicode control characters. + "[\x0-\x1F\x7f-\x9f\x061c\x200e\x200f\x202a-\x202e\x2066-\x2069]" + (lambda (x) (format "\\x%x" (string-to-char x))) + (prin1-to-string + (if (stringp val) + ;; Get rid of string properties to save some of the precious space + (substring-no-properties + val 0 + (min (length val) marginalia-field-width)) + val)) + 'fixedcase 'literal) + 'face + (cond + ((listp val) 'marginalia-list) + ((stringp val) 'marginalia-string) + (t 'marginalia-value)))))))))) + +(defun marginalia-annotate-variable (cand) + "Annotate variable CAND with its documentation string." + (when-let (sym (intern-soft cand)) + (marginalia--fields + ((marginalia--symbol-class sym) :face 'marginalia-type) + ((marginalia--variable-value sym) :truncate 0.5) + ((documentation-property sym 'variable-documentation) + :truncate 1.0 :face 'marginalia-documentation)))) + +(defun marginalia-annotate-environment-variable (cand) + "Annotate environment variable CAND with its current value." + (when-let (val (getenv cand)) + (marginalia--fields + (val :truncate 1.0 :face 'marginalia-value)))) + +(defun marginalia-annotate-face (cand) + "Annotate face CAND with its documentation string and face example." + (when-let (sym (intern-soft cand)) + (marginalia--fields + ;; HACK: Manual alignment to fix misalignment due to face + ((concat marginalia--pangram #(" " 0 1 (display (space :align-to center)))) + :face sym) + ((documentation-property sym 'face-documentation) + :truncate 1.0 :face 'marginalia-documentation)))) + +(defun marginalia-annotate-color (cand) + "Annotate face CAND with its documentation string and face example." + (when-let (rgb (color-name-to-rgb cand)) + (pcase-let* ((`(,r ,g ,b) rgb) + (`(,h ,s ,l) (apply #'color-rgb-to-hsl rgb)) + (cr (color-rgb-to-hex r 0 0)) + (cg (color-rgb-to-hex 0 g 0)) + (cb (color-rgb-to-hex 0 0 b)) + (ch (apply #'color-rgb-to-hex (color-hsl-to-rgb h 1 0.5))) + (cs (apply #'color-rgb-to-hex (color-hsl-to-rgb h s 0.5))) + (cl (apply #'color-rgb-to-hex (color-hsl-to-rgb 0 0 l)))) + (marginalia--fields + (" " :face `(:background ,(apply #'color-rgb-to-hex rgb))) + ((format + "%s%s%s %s" + (propertize "r" 'face `(:background ,cr :foreground ,(readable-foreground-color cr))) + (propertize "g" 'face `(:background ,cg :foreground ,(readable-foreground-color cg))) + (propertize "b" 'face `(:background ,cb :foreground ,(readable-foreground-color cb))) + (color-rgb-to-hex r g b 2))) + ((format + "%s%s%s %3s° %3s%% %3s%%" + (propertize "h" 'face `(:background ,ch :foreground ,(readable-foreground-color ch))) + (propertize "s" 'face `(:background ,cs :foreground ,(readable-foreground-color cs))) + (propertize "l" 'face `(:background ,cl :foreground ,(readable-foreground-color cl))) + (round (* 360 h)) + (round (* 100 s)) + (round (* 100 l)))))))) + +(defun marginalia-annotate-char (cand) + "Annotate character CAND with its general character category and character code." + (when-let (char (char-from-name cand t)) + (marginalia--fields + (:left char :format" (%c)" :face 'marginalia-char) + (char :format "%06X" :face 'marginalia-number) + ((char-code-property-description + 'general-category + (get-char-code-property char 'general-category)) + :width 30 :face 'marginalia-documentation)))) + +(defun marginalia-annotate-minor-mode (cand) + "Annotate minor-mode CAND with status and documentation string." + (let* ((sym (intern-soft cand)) + (message-log-max nil) + (mode (if (and sym (boundp sym)) + sym + (lookup-minor-mode-from-indicator cand))) + (lighter (cdr (assq mode minor-mode-alist))) + (lighter-str (and lighter (string-trim (format-mode-line (cons t lighter)))))) + (marginalia--fields + ((if (and (boundp mode) (symbol-value mode)) + (propertize "On" 'face 'marginalia-on) + (propertize "Off" 'face 'marginalia-off)) :width 3) + ((if (local-variable-if-set-p mode) "Local" "Global") :width 6 :face 'marginalia-type) + (lighter-str :width 20 :face 'marginalia-lighter) + ((marginalia--function-doc mode) + :truncate 1.0 :face 'marginalia-documentation)))) + +(defun marginalia-annotate-package (cand) + "Annotate package CAND with its description summary." + (when-let ((pkg-alist (bound-and-true-p package-alist)) + (name (replace-regexp-in-string "-[0-9\\.-]+\\'" "" cand)) + (pkg (intern-soft name)) + (desc (or (unless (equal name cand) + (cl-loop with version = (substring cand (1+ (length name))) + for d in (alist-get pkg pkg-alist) + if (equal (package-version-join (package-desc-version d)) version) + return d)) + ;; taken from `describe-package-1' + (car (alist-get pkg pkg-alist)) + (if-let (built-in (assq pkg package--builtins)) + (package--from-builtin built-in) + (car (alist-get pkg package-archive-contents)))))) + (marginalia--fields + ((package-version-join (package-desc-version desc)) :truncate 16 :face 'marginalia-version) + ((cond + ((package-desc-archive desc) (propertize (package-desc-archive desc) 'face 'marginalia-archive)) + (t (propertize (or (package-desc-status desc) "orphan") 'face 'marginalia-installed))) :truncate 12) + ((package-desc-summary desc) :truncate 1.0 :face 'marginalia-documentation)))) + +(defun marginalia--bookmark-type (bm) + "Return bookmark type string of BM. +The string is transformed according to `marginalia--bookmark-type-transforms'." + (let ((handler (or (bookmark-prop-get bm 'handler) 'bookmark-default-handler))) + (and + ;; Some libraries use lambda handlers instead of symbols. For + ;; example the function `xwidget-webkit-bookmark-make-record' is + ;; affected. I consider this bad style since then the lambda is + ;; persisted. + (symbolp handler) + (or (get handler 'bookmark-handler-type) + (let ((str (symbol-name handler)) + case-fold-search) + (dolist (transformer marginalia--bookmark-type-transforms str) + (when (string-match-p (car transformer) str) + (setq str + (if (stringp (cdr transformer)) + (replace-regexp-in-string (car transformer) (cdr transformer) str) + (funcall (cdr transformer) str)))))))))) + +(defun marginalia-annotate-bookmark (cand) + "Annotate bookmark CAND with its file name and front context string." + (when-let ((bm (assoc cand (bound-and-true-p bookmark-alist)))) + (marginalia--fields + ((marginalia--bookmark-type bm) :width 10 :face 'marginalia-type) + ((or (bookmark-prop-get bm 'filename) + (bookmark-prop-get bm 'location)) + :truncate (if (bookmark-prop-get bm 'filename) -0.5 0.5) + :face 'marginalia-file-name) + ((let ((front (or (bookmark-prop-get bm 'front-context-string) "")) + (rear (or (bookmark-prop-get bm 'rear-context-string) ""))) + (unless (and (string-blank-p front) (string-blank-p rear)) + (string-clean-whitespace + (concat front (marginalia--ellipsis) rear)))) + :truncate 0.5 :face 'marginalia-documentation)))) + +(defun marginalia-annotate-customize-group (cand) + "Annotate customization group CAND with its documentation string." + (marginalia--documentation (documentation-property (intern cand) 'group-documentation))) + +(defun marginalia-annotate-input-method (cand) + "Annotate input method CAND with its description." + (marginalia--documentation (nth 4 (assoc cand input-method-alist)))) + +(defun marginalia-annotate-charset (cand) + "Annotate charset CAND with its description." + (marginalia--documentation (charset-description (intern cand)))) + +(defun marginalia-annotate-coding-system (cand) + "Annotate coding system CAND with its description." + (marginalia--documentation (coding-system-doc-string (intern cand)))) + +(defun marginalia--buffer-status (buffer) + "Return the status of BUFFER as a string." + (format-mode-line '((:propertize "%1*%1+%1@" face marginalia-modified) + marginalia-separator + (7 (:propertize "%I" face marginalia-size)) + marginalia-separator + ;; InactiveMinibuffer has 18 letters, but there are longer names. + ;; For example Org-Agenda produces very long mode names. + ;; Therefore we have to truncate. + (20 (-20 (:propertize mode-name face marginalia-mode)))) + nil nil buffer)) + +(defun marginalia--buffer-file (buffer) + "Return the file or process name of BUFFER." + (if-let (proc (get-buffer-process buffer)) + (format "(%s %s) %s" + proc (process-status proc) + (abbreviate-file-name (buffer-local-value 'default-directory buffer))) + (abbreviate-file-name + (or (cond + ;; see ibuffer-buffer-file-name + ((buffer-file-name buffer)) + ((when-let (dir (and (local-variable-p 'dired-directory buffer) + (buffer-local-value 'dired-directory buffer))) + (expand-file-name (if (stringp dir) dir (car dir)) + (buffer-local-value 'default-directory buffer)))) + ((local-variable-p 'list-buffers-directory buffer) + (buffer-local-value 'list-buffers-directory buffer))) + "")))) + +(defun marginalia-annotate-buffer (cand) + "Annotate buffer CAND with modification status, file name and major mode." + (when-let ((buffer (get-buffer cand))) + (if (buffer-live-p buffer) + (marginalia--fields + ((marginalia--buffer-status buffer)) + ((marginalia--buffer-file buffer) + :truncate -0.5 :face 'marginalia-file-name)) + (marginalia--fields ("(dead buffer)" :face 'error))))) + +(defun marginalia--full-candidate (cand) + "Return completion candidate CAND in full. +For some completion tables, the completion candidates offered are +meant to be only a part of the full minibuffer contents. For +example, during file name completion the candidates are one path +component of a full file path." + (if-let (win (active-minibuffer-window)) + (with-current-buffer (window-buffer win) + (concat (let ((end (minibuffer-prompt-end))) + (buffer-substring-no-properties + end (+ end marginalia--base-position))) + cand)) + ;; no minibuffer is active, trust that cand already conveys all + ;; necessary information (there's not much else we can do) + cand)) + +(defun marginalia--remote-file-p (file) + "Return non-nil if FILE is remote. +The return value is a string describing the remote location, +e.g., the protocol." + (save-match-data + (setq file (substitute-in-file-name file)) + (cl-loop for r in marginalia-remote-file-regexps + if (string-match r file) + return (or (match-string 1 file) "remote")))) + +(defun marginalia--annotate-local-file (cand) + "Annotate local file CAND." + (marginalia--in-minibuffer + (when-let (attrs (ignore-errors + ;; may throw permission denied errors + (file-attributes (substitute-in-file-name + (marginalia--full-candidate cand)) + 'integer))) + ;; HACK: Format differently accordingly to alignment, since the file owner + ;; is usually not displayed. Otherwise we will see an excessive amount of + ;; whitespace in front of the file permissions. Furthermore the alignment + ;; in `consult-buffer' will look ugly. Find a better solution! + (if (eq marginalia-align 'right) + (marginalia--fields + ;; File owner at the left + ((marginalia--file-owner attrs) :face 'marginalia-file-owner) + ((marginalia--file-modes attrs)) + ((marginalia--file-size attrs) :face 'marginalia-size :width -7) + ((marginalia--time (file-attribute-modification-time attrs)) + :face 'marginalia-date :width -12)) + (marginalia--fields + ((marginalia--file-modes attrs)) + ((marginalia--file-size attrs) :face 'marginalia-size :width -7) + ((marginalia--time (file-attribute-modification-time attrs)) + :face 'marginalia-date :width -12) + ;; File owner at the right + ((marginalia--file-owner attrs) :face 'marginalia-file-owner)))))) + +(defun marginalia-annotate-file (cand) + "Annotate file CAND with its size, modification time and other attributes. +These annotations are skipped for remote paths." + (if-let (remote (or (marginalia--remote-file-p cand) + (when-let (win (active-minibuffer-window)) + (with-current-buffer (window-buffer win) + (marginalia--remote-file-p (minibuffer-contents-no-properties)))))) + (marginalia--fields (remote :format "*%s*" :face 'marginalia-documentation)) + (marginalia--annotate-local-file cand))) + +(defun marginalia--file-owner (attrs) + "Return file owner given ATTRS." + (let ((uid (file-attribute-user-id attrs)) + (gid (file-attribute-group-id attrs))) + (when (or (/= (user-uid) uid) (/= (group-gid) gid)) + (format "%s:%s" + (or (user-login-name uid) uid) + (or (group-name gid) gid))))) + +(defun marginalia--file-size (attrs) + "Return formatted file size given ATTRS." + (propertize (file-size-human-readable (file-attribute-size attrs)) + 'help-echo (number-to-string (file-attribute-size attrs)))) + +(defun marginalia--file-modes (attrs) + "Return fontified file modes given the ATTRS." + ;; Without caching this can a be significant portion of the time + ;; `marginalia-annotate-file' takes to execute. Caching improves performance + ;; by about a factor of 20. + (setq attrs (file-attribute-modes attrs)) + (or (car (member attrs marginalia--fontified-file-modes)) + (progn + (setq attrs (substring attrs)) ;; copy because attrs is about to be modified + (dotimes (i (length attrs)) + (put-text-property + i (1+ i) 'face + (pcase (aref attrs i) + (?- 'marginalia-file-priv-no) + (?d 'marginalia-file-priv-dir) + (?l 'marginalia-file-priv-link) + (?r 'marginalia-file-priv-read) + (?w 'marginalia-file-priv-write) + (?x 'marginalia-file-priv-exec) + ((or ?s ?S ?t ?T) 'marginalia-file-priv-other) + (_ 'marginalia-file-priv-rare)) + attrs)) + (push attrs marginalia--fontified-file-modes) + attrs))) + +(defconst marginalia--time-relative + `((100 "sec" 1) + (,(* 60 100) "min" 60.0) + (,(* 3600 30) "hour" 3600.0) + (,(* 3600 24 400) "day" ,(* 3600.0 24.0)) + (nil "year" ,(* 365.25 24 3600))) + "Formatting used by the function `marginalia--time-relative'.") + +;; Taken from `seconds-to-string'. +(defun marginalia--time-relative (time) + "Format TIME as a relative age." + (setq time (max 0 (float-time (time-since time)))) + (let ((sts marginalia--time-relative) here) + (while (and (car (setq here (pop sts))) (<= (car here) time))) + (setq time (round time (caddr here))) + (format "%s %s%s ago" time (cadr here) (if (= time 1) "" "s")))) + +(defun marginalia--time-absolute (time) + "Format TIME as an absolute age." + (let ((system-time-locale "C")) + (format-time-string + (if (> (decoded-time-year (decode-time (current-time))) + (decoded-time-year (decode-time time))) + " %Y %b %d" + "%b %d %H:%M") + time))) + +(defun marginalia--time (time) + "Format file age TIME, suitably for use in annotations." + (propertize + (if (< (float-time (time-since time)) marginalia-max-relative-age) + (marginalia--time-relative time) + (marginalia--time-absolute time)) + 'help-echo (format-time-string "%Y-%m-%d %T" time))) + +(defvar-local marginalia--project-root 'unset) +(defun marginalia--project-root () + "Return project root." + (marginalia--in-minibuffer + (when (eq marginalia--project-root 'unset) + (setq marginalia--project-root + (or (let ((prompt (minibuffer-prompt)) + case-fold-search) + (and (string-match + "\\`\\(?:Dired\\|Find file\\) in \\(.*\\): \\'" + prompt) + (match-string 1 prompt))) + (when-let (proj (project-current)) + (cond + ((fboundp 'project-root) (project-root proj)) + ((fboundp 'project-roots) (car (project-roots proj)))))))) + marginalia--project-root)) + +(defun marginalia-annotate-project-file (cand) + "Annotate file CAND with its size, modification time and other attributes." + ;; Absolute project directories also report project-file category + (if (file-name-absolute-p cand) + (marginalia-annotate-file cand) + (when-let (root (marginalia--project-root)) + (marginalia-annotate-file (expand-file-name cand root))))) + +(defvar-local marginalia--library-cache nil) +(defun marginalia--library-cache () + "Return hash table from library name to library file." + (marginalia--in-minibuffer + ;; `locate-file' and `locate-library' are bottlenecks for the + ;; annotator. Therefore we compute all the library paths first. + (unless marginalia--library-cache + (setq marginalia--library-cache (make-hash-table :test #'equal)) + (dolist (dir (delete-dups + (reverse ;; Reverse because of shadowing + (append load-path (custom-theme--load-path))))) ;; Include themes + (dolist (file (ignore-errors + (directory-files dir 'full + "\\.el\\(?:\\.gz\\)?\\'"))) + (puthash (marginalia--library-name file) + file marginalia--library-cache)))) + marginalia--library-cache)) + +(defun marginalia--library-name (file) + "Get name of library FILE." + (replace-regexp-in-string "\\(\\.gz\\|\\.elc?\\)+\\'" "" + (file-name-nondirectory file))) + +(defun marginalia--library-doc (file) + "Return library documentation string for FILE." + (let ((doc (get-text-property 0 'marginalia--library-doc file))) + (unless doc + ;; Extract documentation string. We cannot use `lm-summary' here, + ;; since it decompresses the whole file, which is slower. + (setq doc (or (ignore-errors + (let ((shell-file-name "sh") + (shell-command-switch "-c")) + (shell-command-to-string + (format (if (string-suffix-p ".gz" file) + "gzip -c -q -d %s | head -n1" + "head -n1 %s") + (shell-quote-argument file))))) + "")) + (cond + ((string-match "\\`(define-package\\s-+\"\\([^\"]+\\)\"" doc) + (setq doc (format "Generated package description from %s.el" + (match-string 1 doc)))) + ((string-match "\\`;+\\s-*" doc) + (setq doc (substring doc (match-end 0))) + (when (string-match "\\`[^ \t]+\\s-+-+\\s-+" doc) + (setq doc (substring doc (match-end 0)))) + (when (string-match "\\s-*-\\*-" doc) + (setq doc (substring doc 0 (match-beginning 0))))) + (t (setq doc ""))) + ;; Add the documentation string to the cache + (put-text-property 0 1 'marginalia--library-doc doc file)) + doc)) + +(defun marginalia-annotate-theme (cand) + "Annotate theme CAND with documentation and path." + (marginalia-annotate-library (concat cand "-theme"))) + +(defun marginalia-annotate-library (cand) + "Annotate library CAND with documentation and path." + (setq cand (marginalia--library-name cand)) + (when-let (file (gethash cand (marginalia--library-cache))) + (marginalia--fields + ;; Display if the corresponding feature is loaded. + ;; feature/=library file, but better than nothing. + ((when-let (sym (intern-soft cand)) + (when (memq sym features) + (propertize "Loaded" 'face 'marginalia-on))) + :width 8) + ((marginalia--library-doc file) + :truncate 1.0 :face 'marginalia-documentation) + ((abbreviate-file-name (file-name-directory file)) + :truncate -0.5 :face 'marginalia-file-name)))) + +(defun marginalia-annotate-tab (cand) + "Annotate named tab CAND with tab index, window and buffer information." + (when-let ((tabs (funcall tab-bar-tabs-function)) + (index (seq-position + tabs nil + (lambda (tab _) (equal (alist-get 'name tab) cand))))) + (let* ((tab (nth index tabs)) + (ws (alist-get 'ws tab)) + (bufs (window-state-buffers ws))) + ;; When the buffer key is present in the window state it is added in front + ;; of the window buffer list and gets duplicated. + (when (cadr (assq 'buffer ws)) (pop bufs)) + (marginalia--fields + (:left (1+ index) :format " (%s)" :face 'marginalia-key) + ((if (eq (car tab) 'current-tab) + (length (window-list nil 'no-minibuf)) + (length bufs)) + :format "win:%s" :face 'marginalia-size) + ((or (alist-get 'group tab) 'none) + :format "group:%s" :face 'marginalia-type :truncate 20) + ((if (eq (car tab) 'current-tab) + "(current tab)" + (string-join bufs " ")) + :face 'marginalia-documentation))))) + +(defun marginalia-classify-by-command-name () + "Lookup category for current command." + (and marginalia--command + (or (alist-get marginalia--command marginalia-command-categories) + ;; The command can be an alias, e.g., `recentf' -> `recentf-open'. + (when-let ((chain (function-alias-p marginalia--command))) + (alist-get (car (last chain)) marginalia-command-categories))))) + +(defun marginalia-classify-original-category () + "Return original category reported by completion metadata." + ;; Use `alist-get' instead of `completion-metadata-get' to bypass our + ;; `marginalia--completion-metadata-get' advice! + (when-let (cat (alist-get 'category marginalia--metadata)) + ;; Ignore Emacs 28 symbol-help category in order to ensure that the + ;; categories are refined to our categories function and variable. + (and (not (eq cat 'symbol-help)) cat))) + +(defun marginalia-classify-symbol () + "Determine if currently completing symbols." + (when-let (mct minibuffer-completion-table) + (when (or (eq mct 'help--symbol-completion-table) + (obarrayp mct) + (and (not (functionp mct)) (consp mct) (symbolp (car mct)))) ; assume list of symbols + 'symbol))) + +(defun marginalia-classify-by-prompt () + "Determine category by matching regexps against the minibuffer prompt. +This runs through the `marginalia-prompt-categories' alist +looking for a regexp that matches the prompt." + (when-let (prompt (minibuffer-prompt)) + (setq prompt + (replace-regexp-in-string "(.*?default.*?)\\|\\[.*?\\]" "" prompt)) + (cl-loop with case-fold-search = t + for (regexp . category) in marginalia-prompt-categories + when (string-match-p regexp prompt) + return category))) + +(defun marginalia--cache-reset (&rest _) + "Reset the cache." + (setq marginalia--cache (and marginalia--cache (> marginalia--cache-size 0) + (cons nil (make-hash-table :test #'equal + :size marginalia--cache-size))))) + +(defun marginalia--cached (cache fun key) + "Cached application of function FUN with KEY. +The CACHE keeps around the last `marginalia--cache-size' computed +annotations. The cache is mainly useful when scrolling in +completion UIs like Vertico or Icomplete." + (if cache + (let ((ht (cdr cache))) + (or (gethash key ht) + (let ((val (funcall fun key))) + (push key (car cache)) + (puthash key val ht) + (when (>= (hash-table-count ht) marginalia--cache-size) + (let ((end (last (car cache) 2))) + (remhash (cadr end) ht) + (setcdr end nil))) + val))) + (funcall fun key))) + +(defun marginalia--align (cands) + "Align annotations of CANDS according to `marginalia-align'." + (cl-loop + for (cand . ann) in cands do + (when-let (align (text-property-any 0 (length ann) 'marginalia--align t ann)) + (setq marginalia--cand-width-max + (max marginalia--cand-width-max + (* (ceiling (+ (string-width cand) + (compat-call string-width ann 0 align)) + marginalia--cand-width-step) + marginalia--cand-width-step))))) + (cl-loop + for (cand . ann) in cands collect + (progn + (when-let (align (text-property-any 0 (length ann) 'marginalia--align t ann)) + (put-text-property + align (1+ align) 'display + `(space :align-to + ,(pcase-exhaustive marginalia-align + ('center `(+ center ,marginalia-align-offset)) + ('left `(+ left ,(+ marginalia-align-offset marginalia--cand-width-max))) + ('right `(+ right ,(+ marginalia-align-offset 1 + (- (compat-call string-width ann 0 align) + (string-width ann))))))) + ann)) + (list cand "" ann)))) + +(defun marginalia--affixate (metadata annotator cands) + "Affixate CANDS given METADATA and Marginalia ANNOTATOR." + ;; Compute minimum width of windows, which display the minibuffer, including + ;; the miniwindow. In general the computed width corresponds to the full + ;; frame width, since the miniwindow spans the full frame. For example + ;; `vertico-buffer' displays the minibuffer in a separate window. Similarly, + ;; we could detect other types of completion buffers, e.g., Embark Collect or + ;; the default completion buffer, and compute smaller widths. + (let* ((width (cl-loop for win in (get-buffer-window-list) minimize (window-width win))) + (marginalia-field-width (min (/ width 2) marginalia-field-width)) + (marginalia--metadata metadata) + (cache marginalia--cache)) + (marginalia--align + ;; Run the annotators in the original window. `with-selected-window' + ;; is necessary because of `lookup-minor-mode-from-indicator'. + ;; Otherwise it would suffice to only change the current buffer. We + ;; need the `selected-window' fallback for Embark Occur. + (with-selected-window (or (minibuffer-selected-window) (selected-window)) + (cl-loop for cand in cands collect + (let ((ann (or (marginalia--cached cache annotator cand) ""))) + (cons cand (if (string-blank-p ann) "" ann)))))))) + +(defun marginalia--completion-metadata-get (metadata prop) + "Meant as :before-until advice for `completion-metadata-get'. +METADATA is the metadata. +PROP is the property which is looked up." + (pcase prop + ('annotation-function + ;; We do want the advice triggered for `completion-metadata-get'. + (when-let ((cat (completion-metadata-get metadata 'category)) + (annotator (marginalia--annotator cat))) + (lambda (cand) + (let ((ann (caddar (marginalia--affixate metadata annotator (list cand))))) + (and (not (equal ann "")) ann))))) + ('affixation-function + ;; We do want the advice triggered for `completion-metadata-get'. + (when-let ((cat (completion-metadata-get metadata 'category)) + (annotator (marginalia--annotator cat))) + (apply-partially #'marginalia--affixate metadata annotator))) + ('category + ;; Find the completion category by trying each of our classifiers. + ;; Store the metadata for `marginalia-classify-original-category'. + (let ((marginalia--metadata metadata)) + (run-hook-with-args-until-success 'marginalia-classifiers))))) + +(defun marginalia--minibuffer-setup () + "Setup the minibuffer for Marginalia. +Remember `this-command' for `marginalia-classify-by-command-name'." + (setq marginalia--cache t marginalia--command this-command) + ;; Reset cache if window size changes, recompute alignment + (add-hook 'window-state-change-hook #'marginalia--cache-reset nil 'local) + (marginalia--cache-reset)) + +(defun marginalia--base-position (completions) + "Record the base position of COMPLETIONS." + ;; As a small optimization we track the base position only for file + ;; completions, since `marginalia--full-candidate' is currently used only by + ;; the file annotation function. + (when minibuffer-completing-file-name + (let ((base (or (cdr (last completions)) 0))) + (unless (= marginalia--base-position base) + (marginalia--cache-reset) + (setq marginalia--base-position base + marginalia--cand-width-max (default-value 'marginalia--cand-width-max))))) + completions) + +;;;###autoload +(define-minor-mode marginalia-mode + "Annotate completion candidates with richer information." + :global t :group 'marginalia + (if marginalia-mode + (progn + ;; Remember `this-command' in order to select the annotation function. + (add-hook 'minibuffer-setup-hook #'marginalia--minibuffer-setup) + ;; Replace the metadata function. + (advice-add #'completion-metadata-get :before-until #'marginalia--completion-metadata-get) + ;; Record completion base position, for `marginalia--full-candidate' + (advice-add #'completion-all-completions :filter-return #'marginalia--base-position)) + (advice-remove #'completion-all-completions #'marginalia--base-position) + (advice-remove #'completion-metadata-get #'marginalia--completion-metadata-get) + (remove-hook 'minibuffer-setup-hook #'marginalia--minibuffer-setup))) + +;;;###autoload +(defun marginalia-cycle () + "Cycle between annotators in `marginalia-annotator-registry'." + (interactive) + (with-current-buffer (window-buffer + (or (active-minibuffer-window) + (user-error "Marginalia: No active minibuffer"))) + (let* ((end (minibuffer-prompt-end)) + (pt (max 0 (- (point) end))) + (metadata (completion-metadata (buffer-substring-no-properties end (+ end pt)) + minibuffer-completion-table + minibuffer-completion-predicate)) + (cat (or (completion-metadata-get metadata 'category) + (user-error "Marginalia: Unknown completion category"))) + (ann (or (assq cat marginalia-annotator-registry) + (user-error "Marginalia: No annotators found for category `%s'" cat)))) + (marginalia--cache-reset) + (setcdr ann (append (cddr ann) (list (cadr ann)))) + ;; When the builtin annotator is selected and no builtin function is + ;; available, skip to the next annotator. Note that we cannot use + ;; `completion-metadata-get' to access the metadata since we must + ;; bypass the `marginalia--completion-metadata-get' advice. + (when (and (eq (cadr ann) 'builtin) + (not (assq 'annotation-function metadata)) + (not (assq 'affixation-function metadata)) + (not (plist-get completion-extra-properties :annotation-function)) + (not (plist-get completion-extra-properties :affixation-function))) + (setcdr ann (append (cddr ann) (list (cadr ann))))) + (message "Marginalia: Use annotator `%s' for category `%s'" (cadr ann) (car ann))))) + +;; Emacs 28: Only show `marginalia-cycle' in M-x in recursive minibuffers +(put #'marginalia-cycle 'completion-predicate + (lambda (&rest _) (> (minibuffer-depth) 1))) + +(provide 'marginalia) +;;; marginalia.el ends here blob - /dev/null blob + cdef4b354cfffeec9939f3d05b650b76775e4d2d (mode 644) --- /dev/null +++ elpa/marginalia-1.6/marginalia.info @@ -0,0 +1,276 @@ +This is docYDUx1k.info, produced by makeinfo version 6.8 from +marginalia.texi. + +INFO-DIR-SECTION Emacs misc features +START-INFO-DIR-ENTRY +* Marginalia: (marginalia). Marginalia in the minibuffer. +END-INFO-DIR-ENTRY + + +File: docYDUx1k.info, Node: Top, Next: Configuration, Up: (dir) + +marginalia.el - Marginalia in the minibuffer +******************************************** + +This package provides ‘marginalia-mode’ which adds marginalia to the +minibuffer completions. Marginalia +(https://en.wikipedia.org/wiki/Marginalia) are marks or annotations +placed at the margin of the page of a book or in this case helpful +colorful annotations placed at the margin of the minibuffer for your +completion candidates. Marginalia can only add annotations to the +completion candidates. It cannot modify the appearance of the +candidates themselves, which are shown unaltered as supplied by the +original command. + + The annotations are added based on the completion category. For +example ‘find-file’ reports the ‘file’ category and ‘M-x’ reports the +‘command’ category. You can cycle between more or less detailed +annotators or even disable the annotator with command +‘marginalia-cycle’. + +* Menu: + +* Configuration:: +* Information shown by the annotators:: +* Adding custom annotators or classifiers:: +* Disabling annotators, builtin or lightweight annotators: Disabling annotators builtin or lightweight annotators. +* Icons in the minibuffer:: +* Contributions:: + + +File: docYDUx1k.info, Node: Configuration, Next: Information shown by the annotators, Prev: Top, Up: Top + +1 Configuration +*************** + +It is recommended to use Marginalia together with either the Vertico +(https://github.com/minad/vertico), Mct +(https://github.com/protesilaos/mct), Icomplete +(https://www.gnu.org/software/emacs/manual/html_node/emacs/Icomplete.html) +or the default completion UI. Furthermore Marginalia can be combined +with Embark (https://github.com/oantolin/embark) for action support and +Consult (https://github.com/minad/consult), which provides many useful +commands. + + ;; Enable rich annotations using the Marginalia package + (use-package marginalia + ;; Bind `marginalia-cycle' locally in the minibuffer. To make the binding + ;; available in the *Completions* buffer, add it to the + ;; `completion-list-mode-map'. + :bind (:map minibuffer-local-map + ("M-A" . marginalia-cycle)) + + ;; The :init section is always executed. + :init + + ;; Marginalia must be activated in the :init section of use-package such that + ;; the mode gets enabled right away. Note that this forces loading the + ;; package. + (marginalia-mode)) + + +File: docYDUx1k.info, Node: Information shown by the annotators, Next: Adding custom annotators or classifiers, Prev: Configuration, Up: Top + +2 Information shown by the annotators +************************************* + +In general, to learn more about what different annotations mean, a good +starting point is to look at ‘marginalia-annotator-registry’, and follow +up to the annotation function of the category you are interested in. + + For example the annotations for Elisp symbols include their symbol +class - ‘v’ for variable, ‘f’ for function, ‘c’ for command, etc. For +more information on what the different classifications mean, see the +docstring of ‘marginalia--symbol-class’. + + +File: docYDUx1k.info, Node: Adding custom annotators or classifiers, Next: Disabling annotators builtin or lightweight annotators, Prev: Information shown by the annotators, Up: Top + +3 Adding custom annotators or classifiers +***************************************** + +*IMPORTANT NOTICE FOR PACKAGE AUTHORS*: The intention of the Marginalia +package is to give the user means to overwrite completion categories and +to add custom annotators for existing commands in their user +configuration. *Marginalia is a user facing package and is not intended +to be used as a library*. Therefore Marginalia does not expose library +functions as part of its public API. If you add your own completion +commands to your package we recommend to specify an +‘annotation-function’ or an ‘affixation-function’, avoiding the +Marginalia dependency this way. The ‘annotation-function’ and +‘affixation-function’ are documented in the Elisp manual +(https://www.gnu.org/software/emacs/manual/html_node/elisp/Completion.html). +If you use ‘consult--read’, you can specify an ‘:annotate’ keyword +argument. There is an exception to our recommendation: If you want to +implement annotations for an existing package ‘hypothetic.el’, which +does not have annotations and where annotations cannot be added, then +the creation of a ‘marginalia-hypothetic.el’ package is a good idea, +since Marginalia provides the facilities to enhance existing commands +from the outside. If you have questions feel free to ask on the +Marginalia issue tracker. + + Commands that support minibuffer completion use a completion table of +all the available candidates. Candidates are associated with a +*category* such as ‘command’, ‘file’, ‘face’, or ‘variable’ depending on +what the candidates are. Based on the category of the candidates, +Marginalia selects an *annotator* to generate annotations for display +for each candidate. + + Unfortunately, not all commands (including Emacs’ builtin ones) +specify the category of their candidates. To compensate for this +shortcoming, Marginalia hooks into the Emacs completion framework and +runs the *classifiers* listed in the variable ‘marginalia-classifiers’, +which use the command’s prompt or other properties of the candidates to +specify the completion category. + + For example, the ‘marginalia-classify-by-prompt’ classifier checks +the minibuffer prompt against regexps listed in the +‘marginalia-prompt-categories’ alist to determine a category. The +following is already included but would be a way to assign the category +‘face’ to all candidates from commands with prompts that include the +word "face". + + (add-to-list 'marginalia-prompt-categories '("\\" . face)) + + The ‘marginalia-classify-by-command-name’ classifier uses the alist +‘marginalia-command-categories’ to specify the completion category based +on the command name. This is particularly useful if the prompt +classifier yields a false positive. + + Completion categories are also important for Embark +(https://github.com/oantolin/embark), which associates actions based on +the completion category and benefits from Marginalia’s classifiers. + + Once the category of the candidates is known, Marginalia looks in the +‘marginalia-annotator-registry’ to find the associated annotator to use. +An annotator is a function that takes a completion candidate string as +an argument and returns an annotation string to be displayed after the +candidate in the minibuffer. More than one annotator can be assigned to +each each category, displaying more, less or different information. Use +the ‘marginalia-cycle’ command to cycle between the annotations of +different annotators defined for the current category. + + Here’s an example of a basic face annotator: + + (defun my-face-annotator (cand) + (when-let (sym (intern-soft cand)) + (concat (propertize " " 'display '(space :align-to center)) + (propertize "The quick brown fox jumps over the lazy dog" 'face sym)))) + + Look at Marginalia’s various annotators for examples of formatting +annotations. In particular, the helper function ‘marginalia--fields’ +can be used to format information into columns. + + After defining a new annotator, associate it with a category in the +annotator registry as follows: + + (add-to-list 'marginalia-annotator-registry + '(face my-face-annotator marginalia-annotate-face builtin none)) + + This makes the ‘my-face-annotator’ the first of four annotators for +the face category. The others are the annotator provided by Marginalia +(‘marginalia-annotate-face’), the ‘builtin’ annotator as defined by +Emacs and the ‘none’ annotator, which disables the annotations. With +this setting, after invoking ‘M-x describe-face RET’ you can cycle +between all of these annotators using ‘marginalia-cycle’. + + +File: docYDUx1k.info, Node: Disabling annotators builtin or lightweight annotators, Next: Icons in the minibuffer, Prev: Adding custom annotators or classifiers, Up: Top + +4 Disabling annotators, builtin or lightweight annotators +********************************************************* + +Marginalia activates rich annotators by default. Depending on your +preference you may want to use the builtin annotators or even no +annotators by default and only activate the annotators on demand by +invoking ‘marginalia-cycle’. + + In order to use the builtin annotators by default, you can use the +following command. Replace ‘builtin’ by ‘none’ to disable annotators by +default. + + (defun marginalia-use-builtin () + (interactive) + (mapc + (lambda (x) + (setcdr x (cons 'builtin (remq 'builtin (cdr x))))) + marginalia-annotator-registry)) + + If a completion category supports two annotators, you can toggle +between those using this command. + + (defun marginalia-toggle () + (interactive) + (mapc + (lambda (x) + (setcdr x (append (reverse (remq 'none + (remq 'builtin (cdr x)))) + '(builtin none)))) + marginalia-annotator-registry)) + + After cycling the annotators you may want to automatically save the +configuration. This can be achieved using an advice which calls +‘customize-save-variable’. + + (advice-add #'marginalia-cycle :after + (lambda () + (let ((inhibit-message t)) + (customize-save-variable 'marginalia-annotator-registry + marginalia-annotator-registry)))) + + In order to disable an annotator permanently, the +‘marginalia-annotator-registry’ can be modified. For example if you +prefer to never see file annotations, you can delete all file annotators +from the registry. + + (setq marginalia-annotator-registry + (assq-delete-all 'file marginalia-annotator-registry)) + + +File: docYDUx1k.info, Node: Icons in the minibuffer, Next: Contributions, Prev: Disabling annotators builtin or lightweight annotators, Up: Top + +5 Icons in the minibuffer +************************* + +Icons in the minibuffer completion UI are a commonly requested feature. +Marginalia focuses on text annotations only. The following packages are +compatible with Marginalia and use special fonts to add icons in front +of completion candidates. There also exist related packages to enhance +Dired, Ibuffer and other modes with icons consistently. + + • all-the-icons-completion + (https://github.com/iyefrat/all-the-icons-completion): Relies on + the ‘all-the-icons.el’ package which configures multiple icon + fonts. + • nerd-icons-completion + (https://github.com/rainstormstudio/nerd-icons-completion): Relies + on patched fonts including icons. This package works even in the + terminal where only a single font can be used. + + +File: docYDUx1k.info, Node: Contributions, Prev: Icons in the minibuffer, Up: Top + +6 Contributions +*************** + +Since this package is part of GNU ELPA +(https://elpa.gnu.org/packages/marginalia.html) contributions require a +copyright assignment to the FSF. + + + +Tag Table: +Node: Top216 +Node: Configuration1478 +Node: Information shown by the annotators2706 +Node: Adding custom annotators or classifiers3418 +Node: Disabling annotators builtin or lightweight annotators8334 +Node: Icons in the minibuffer10385 +Node: Contributions11344 + +End Tag Table + + +Local Variables: +coding: utf-8 +End: blob - a6140a65392c40dc14ad85b4a1f25e94c16f601e (mode 644) blob + /dev/null --- elpa/marginalia-1.5.signed +++ /dev/null @@ -1,2 +0,0 @@ -Good signature from 066DAFCB81E42C40 GNU ELPA Signing Agent (2019) (trust undefined) created at 2023-12-28T11:05:03+0100 using RSA -Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) (trust undefined) created at 2023-12-28T11:05:03+0100 using EDDSA \ No newline at end of file blob - /dev/null blob + a0d8dc796f6a623f3477aa9e38bcd94b510df7ab (mode 644) --- /dev/null +++ elpa/marginalia-1.6.signed @@ -0,0 +1 @@ +Good signature from 645357D2883A0966 GNU ELPA Signing Agent (2023) (trust undefined) created at 2024-04-04T11:10:02+0200 using EDDSA \ No newline at end of file blob - 02f3c45299431cd2baa03e6e40952bd3dd397ceb (mode 644) blob + /dev/null --- elpa/sly-20231213.1030/contrib/sly-autodoc.el +++ /dev/null @@ -1,180 +0,0 @@ -;;; -*-lexical-binding:t-*- -;;; (require 'sly) -(require 'eldoc) -(require 'cl-lib) -(require 'sly-parse "lib/sly-parse") - -(define-sly-contrib sly-autodoc - "Show fancy arglist in echo area." - (:license "GPL") - (:authors "Luke Gorrie " - "Lawrence Mitchell " - "Matthias Koeppe " - "Tobias C. Rittweiler ") - (:slynk-dependencies slynk/arglists) - (:on-load (add-hook 'sly-editing-mode-hook 'sly-autodoc-mode) - (add-hook 'sly-mrepl-mode-hook 'sly-autodoc-mode) - (add-hook 'sly-minibuffer-setup-hook 'sly-autodoc-mode)) - (:on-unload (remove-hook 'sly-editing-mode-hook 'sly-autodoc-mode) - (remove-hook 'sly-mrepl-mode-hook 'sly-autodoc-mode) - (remove-hook 'sly-minibuffer-setup-hook 'sly-autodoc-mode))) - -(defcustom sly-autodoc-accuracy-depth 10 - "Number of paren levels that autodoc takes into account for - context-sensitive arglist display (local functions. etc)" - :type 'integer - :group 'sly-ui) - - - -(defun sly-arglist (name) - "Show the argument list for NAME." - (interactive (list (sly-read-symbol-name "Arglist of: " t))) - (let ((arglist (sly-autodoc--retrieve-arglist name))) - (if (eq arglist :not-available) - (error "Arglist not available") - (message "%s" (sly-autodoc--fontify arglist))))) - -(defun sly-autodoc--retrieve-arglist (name) - (let ((name (cl-etypecase name - (string name) - (symbol (symbol-name name))))) - (car (sly-eval `(slynk:autodoc '(,name ,sly-cursor-marker)))))) - -(defun sly-autodoc-manually () - "Like autodoc information forcing multiline display." - (interactive) - (let ((doc (sly-autodoc t))) - (cond (doc (eldoc-message (format "%s" doc))) - (t (eldoc-message nil))))) - -;; Must call eldoc-add-command otherwise (eldoc-display-message-p) -;; returns nil and eldoc clears the echo area instead. -(eldoc-add-command 'sly-autodoc-manually) - -(defun sly-autodoc-space (n) - "Like `sly-space' but nicer." - (interactive "p") - (self-insert-command n) - (let ((doc (sly-autodoc))) - (when doc - (eldoc-message (format "%s" doc))))) - -(eldoc-add-command 'sly-autodoc-space) - - -;;;; Autodoc cache - -(defvar sly-autodoc--cache-last-context nil) -(defvar sly-autodoc--cache-last-autodoc nil) - - -;;;; Formatting autodoc - -(defsubst sly-autodoc--canonicalize-whitespace (string) - (replace-regexp-in-string "[ \n\t]+" " " string)) - -(defvar sly-autodoc-preamble nil) - -(defun sly-autodoc--format (doc multilinep) - (let* ((strings (delete nil - (list sly-autodoc-preamble - (and doc - (sly-autodoc--fontify doc))))) - (message (and strings (mapconcat #'identity strings "\n")))) - (when message - (cond (multilinep message) - (t (sly-oneliner (sly-autodoc--canonicalize-whitespace message))))))) - -(defun sly-autodoc--fontify (string) - "Fontify STRING as `font-lock-mode' does in Lisp mode." - (with-current-buffer (get-buffer-create (sly-buffer-name :fontify :hidden t)) - (erase-buffer) - (unless (eq major-mode 'lisp-mode) - ;; Just calling (lisp-mode) will turn sly-mode on in that buffer, - ;; which may interfere with this function - (setq major-mode 'lisp-mode) - (lisp-mode-variables t)) - (insert string) - (let ((font-lock-verbose nil)) - (font-lock-fontify-buffer)) - (goto-char (point-min)) - (when (re-search-forward "===> \\(\\(.\\|\n\\)*\\) <===" nil t) - (let ((highlight (match-string 1))) - ;; Can't use (replace-match highlight) here -- broken in Emacs 21 - (delete-region (match-beginning 0) (match-end 0)) - (sly-insert-propertized '(face eldoc-highlight-function-argument) - highlight))) - (buffer-substring (point-min) (point-max)))) - - -;;;; Autodocs (automatic context-sensitive help) - -(defun sly-autodoc (&optional force-multiline) - "Returns the cached arglist information as string, or nil. -If it's not in the cache, the cache will be updated asynchronously." - (interactive "P") - (save-excursion - (save-match-data - ;; See github#385 and - ;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=45117 - (let* ((inhibit-quit t) - (context - (cons - (sly-current-connection) - (sly-autodoc--parse-context)))) - (when (car context) - (let* ((cached (and (equal context sly-autodoc--cache-last-context) - sly-autodoc--cache-last-autodoc)) - (multilinep (or force-multiline - eldoc-echo-area-use-multiline-p))) - (cond (cached (sly-autodoc--format cached multilinep)) - (t - (when (sly-background-activities-enabled-p) - (sly-autodoc--async context multilinep)) - nil)))))))) - -;; Return the context around point that can be passed to -;; slynk:autodoc. nil is returned if nothing reasonable could be -;; found. -(defun sly-autodoc--parse-context () - (and (not (sly-inside-string-or-comment-p)) - (sly-parse-form-upto-point sly-autodoc-accuracy-depth))) - -(defun sly-autodoc--async (context multilinep) - (sly-eval-async - `(slynk:autodoc ',(cdr context) ;; FIXME: misuse of quote - :print-right-margin ,(window-width (minibuffer-window))) - (sly-curry #'sly-autodoc--async% context multilinep))) - -(defun sly-autodoc--async% (context multilinep doc) - (cl-destructuring-bind (doc &optional cache-p) doc - (unless (eq doc :not-available) - (when cache-p - (setq sly-autodoc--cache-last-context context) - (setq sly-autodoc--cache-last-autodoc doc)) - ;; Now that we've got our information, - ;; get it to the user ASAP. - (when (eldoc-display-message-p) - (eldoc-message (format "%s" (sly-autodoc--format doc multilinep))))))) - - -;;; Minor mode definition -(defvar sly-autodoc-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "C-c C-d A") 'sly-autodoc) - map)) - -(define-minor-mode sly-autodoc-mode - "Toggle echo area display of Lisp objects at point." - nil nil nil - (cond (sly-autodoc-mode - (set (make-local-variable 'eldoc-documentation-function) 'sly-autodoc) - (set (make-local-variable 'eldoc-minor-mode-string) "") - (eldoc-mode sly-autodoc-mode)) - (t - (eldoc-mode -1) - (set (make-local-variable 'eldoc-documentation-function) nil) - (set (make-local-variable 'eldoc-minor-mode-string) " ElDoc")))) - -(provide 'sly-autodoc) blob - ceda2fc8bf2981d9dcaaed14b9320fedfc7964ac (mode 644) blob + /dev/null --- elpa/sly-20231213.1030/contrib/sly-fancy-inspector.el +++ /dev/null @@ -1,22 +0,0 @@ -;; -*- lexical-binding: t; -*- -(require 'sly) -(require 'sly-parse "lib/sly-parse") - -(define-sly-contrib sly-fancy-inspector - "Fancy inspector for CLOS objects." - (:authors "Marco Baringer and others") - (:license "GPL") - (:slynk-dependencies slynk/fancy-inspector)) - -(defun sly-inspect-definition () - "Inspect definition at point" - (interactive) - (sly-inspect (sly-definition-at-point))) - -(defun sly-disassemble-definition () - "Disassemble definition at point" - (interactive) - (sly-eval-describe `(slynk:disassemble-form - ,(sly-definition-at-point t)))) - -(provide 'sly-fancy-inspector) blob - aee84993565672d128c9479b545ce48b22ec76b0 (mode 644) blob + /dev/null --- elpa/sly-20231213.1030/contrib/sly-fancy-trace.el +++ /dev/null @@ -1,68 +0,0 @@ -;; -*- lexical-binding: t; -*- -(require 'sly) -(require 'sly-parse "lib/sly-parse") - -(define-sly-contrib sly-fancy-trace - "Enhanced version of sly-trace capable of tracing local functions, -methods, setf functions, and other entities supported by specific -slynk:slynk-toggle-trace backends. Invoke via C-u C-t." - (:authors "Matthias Koeppe " - "Tobias C. Rittweiler ") - (:license "GPL")) - -(defun sly-trace-query (spec) - "Ask the user which function to trace; SPEC is the default. -The result is a string." - (cond ((null spec) - (sly-read-from-minibuffer "(Un)trace: ")) - ((stringp spec) - (sly-read-from-minibuffer "(Un)trace: " spec)) - ((symbolp spec) ; `sly-extract-context' can return symbols. - (sly-read-from-minibuffer "(Un)trace: " (prin1-to-string spec))) - (t - (sly-dcase spec - ((setf n) - (sly-read-from-minibuffer "(Un)trace: " (prin1-to-string spec))) - ((:defun n) - (sly-read-from-minibuffer "(Un)trace: " (prin1-to-string n))) - ((:defgeneric n) - (let* ((name (prin1-to-string n)) - (answer (sly-read-from-minibuffer "(Un)trace: " name))) - (cond ((and (string= name answer) - (y-or-n-p (concat "(Un)trace also all " - "methods implementing " - name "? "))) - (prin1-to-string `(:defgeneric ,n))) - (t - answer)))) - ((:defmethod &rest _) - (sly-read-from-minibuffer "(Un)trace: " (prin1-to-string spec))) - ((:call caller callee) - (let* ((callerstr (prin1-to-string caller)) - (calleestr (prin1-to-string callee)) - (answer (sly-read-from-minibuffer "(Un)trace: " - calleestr))) - (cond ((and (string= calleestr answer) - (y-or-n-p (concat "(Un)trace only when " calleestr - " is called by " callerstr "? "))) - (prin1-to-string `(:call ,caller ,callee))) - (t - answer)))) - (((:labels :flet) &rest _) - (sly-read-from-minibuffer "(Un)trace local function: " - (prin1-to-string spec))) - (t (error "Don't know how to trace the spec %S" spec)))))) - -(defun sly-toggle-fancy-trace (&optional using-context-p) - "Toggle trace." - (interactive "P") - (let* ((spec (if using-context-p - (sly-extract-context) - (sly-symbol-at-point))) - (spec (sly-trace-query spec))) - (sly-message "%s" (sly-eval `(slynk:slynk-toggle-trace ,spec))))) - -;; override sly-toggle-trace-fdefinition -(define-key sly-prefix-map "\C-t" 'sly-toggle-fancy-trace) - -(provide 'sly-fancy-trace) blob - 9753b0106750d36ae0ecf343b72b3384062f871d (mode 644) blob + /dev/null --- elpa/sly-20231213.1030/contrib/sly-fancy.el +++ /dev/null @@ -1,22 +0,0 @@ -;; -*- lexical-binding: t; -*- -(require 'sly) - -(define-sly-contrib sly-fancy - "Make SLY fancy." - (:authors "Matthias Koeppe " - "Tobias C Rittweiler ") - (:license "GPL") - (:sly-dependencies sly-mrepl - sly-autodoc - sly-fancy-inspector - sly-fancy-trace - sly-scratch - sly-package-fu - sly-fontifying-fu - sly-trace-dialog - ;; sly-profiler ;; not ready for prime-time yet - sly-stickers - sly-indentation - sly-tramp)) - -(provide 'sly-fancy) blob - b6050ba8b1111b25c4987ba06a519afe7488b181 (mode 644) blob + /dev/null --- elpa/sly-20231213.1030/contrib/sly-fontifying-fu.el +++ /dev/null @@ -1,206 +0,0 @@ -;; -*- lexical-binding: t; -*- -(require 'sly) -(require 'sly-parse "lib/sly-parse") -(require 'font-lock) -(require 'cl-lib) - -;;; Fontify WITH-FOO, DO-FOO, and DEFINE-FOO like standard macros. -;;; Fontify CHECK-FOO like CHECK-TYPE. -(defvar sly-additional-font-lock-keywords - '(("(\\(\\(\\s_\\|\\w\\)*:\\(define-\\|do-\\|with-\\|without-\\)\\(\\s_\\|\\w\\)*\\)" 1 font-lock-keyword-face) - ("(\\(\\(define-\\|do-\\|with-\\)\\(\\s_\\|\\w\\)*\\)" 1 font-lock-keyword-face) - ("(\\(check-\\(\\s_\\|\\w\\)*\\)" 1 font-lock-warning-face) - ("(\\(assert-\\(\\s_\\|\\w\\)*\\)" 1 font-lock-warning-face))) - -;;;; Specially fontify forms suppressed by a reader conditional. -(defcustom sly-highlight-suppressed-forms t - "Display forms disabled by reader conditionals as comments." - :type '(choice (const :tag "Enable" t) (const :tag "Disable" nil)) - :group 'sly-mode) - -(define-sly-contrib sly-fontifying-fu - "Additional fontification tweaks: -Fontify WITH-FOO, DO-FOO, DEFINE-FOO like standard macros. -Fontify CHECK-FOO like CHECK-TYPE." - (:authors "Tobias C. Rittweiler ") - (:license "GPL") - (:on-load - (font-lock-add-keywords - 'lisp-mode sly-additional-font-lock-keywords) - (when sly-highlight-suppressed-forms - (sly-activate-font-lock-magic))) - (:on-unload - ;; FIXME: remove `sly-search-suppressed-forms', and remove the - ;; extend-region hook. - (font-lock-remove-keywords - 'lisp-mode sly-additional-font-lock-keywords))) - -(defface sly-reader-conditional-face - '((t (:inherit font-lock-comment-face))) - "Face for compiler notes while selected." - :group 'sly-mode-faces) - -(defvar sly-search-suppressed-forms-match-data (list nil nil)) - -(defun sly-search-suppressed-forms-internal (limit) - (when (search-forward-regexp sly-reader-conditionals-regexp limit t) - (let ((start (match-beginning 0)) ; save match data - (state (sly-current-parser-state))) - (if (or (nth 3 state) (nth 4 state)) ; inside string or comment? - (sly-search-suppressed-forms-internal limit) - (let* ((char (char-before)) - (expr (read (current-buffer))) - (val (sly-eval-feature-expression expr))) - (when (<= (point) limit) - (if (or (and (eq char ?+) (not val)) - (and (eq char ?-) val)) - ;; If `sly-extend-region-for-font-lock' did not - ;; fully extend the region, the assertion below may - ;; fail. This should only happen on XEmacs and older - ;; versions of GNU Emacs. - (ignore-errors - (forward-sexp) (backward-sexp) - ;; Try to suppress as far as possible. - (sly-forward-sexp) - (cl-assert (<= (point) limit)) - (let ((md (match-data nil sly-search-suppressed-forms-match-data))) - (setf (cl-first md) start) - (setf (cl-second md) (point)) - (set-match-data md) - t)) - (sly-search-suppressed-forms-internal limit)))))))) - -(defun sly-search-suppressed-forms (limit) - "Find reader conditionalized forms where the test is false." - (when (and sly-highlight-suppressed-forms - (sly-connected-p)) - (let ((result 'retry)) - (while (and (eq result 'retry) (<= (point) limit)) - (condition-case condition - (setq result (sly-search-suppressed-forms-internal limit)) - (end-of-file ; e.g. #+( - (setq result nil)) - ;; We found a reader conditional we couldn't process for - ;; some reason; however, there may still be other reader - ;; conditionals before `limit'. - (invalid-read-syntax ; e.g. #+#.foo - (setq result 'retry)) - (scan-error ; e.g. #+nil (foo ... - (setq result 'retry)) - (sly-incorrect-feature-expression ; e.g. #+(not foo bar) - (setq result 'retry)) - (sly-unknown-feature-expression ; e.g. #+(foo) - (setq result 'retry)) - (error - (setq result nil) - (sly-warning - (concat "Caught error during fontification while searching for forms\n" - "that are suppressed by reader-conditionals. The error was: %S.") - condition)))) - result))) - - -(defun sly-search-directly-preceding-reader-conditional () - "Search for a directly preceding reader conditional. Return its -position, or nil." - ;;; We search for a preceding reader conditional. Then we check that - ;;; between the reader conditional and the point where we started is - ;;; no other intervening sexp, and we check that the reader - ;;; conditional is at the same nesting level. - (condition-case nil - (let* ((orig-pt (point)) - (reader-conditional-pt - (search-backward-regexp sly-reader-conditionals-regexp - ;; We restrict the search to the - ;; beginning of the /previous/ defun. - (save-excursion - (beginning-of-defun) - (point)) - t))) - (when reader-conditional-pt - (let* ((parser-state - (parse-partial-sexp - (progn (goto-char (+ reader-conditional-pt 2)) - (forward-sexp) ; skip feature expr. - (point)) - orig-pt)) - (paren-depth (car parser-state)) - (last-sexp-pt (cl-caddr parser-state))) - (if (and paren-depth - (not (cl-plusp paren-depth)) ; no '(' in between? - (not last-sexp-pt)) ; no complete sexp in between? - reader-conditional-pt - nil)))) - (scan-error nil))) ; improper feature expression - - -;;; We'll push this onto `font-lock-extend-region-functions'. In past, -;;; we didn't do so which made our reader-conditional font-lock magic -;;; pretty unreliable (it wouldn't highlight all suppressed forms, and -;;; worked quite non-deterministic in general.) -;;; -;;; Cf. _Elisp Manual_, 23.6.10 Multiline Font Lock Constructs. -;;; -;;; We make sure that `font-lock-beg' and `font-lock-end' always point -;;; to the beginning or end of a toplevel form. So we never miss a -;;; reader-conditional, or point in mid of one. -(defvar font-lock-beg) ; shoosh compiler -(defvar font-lock-end) - -(defun sly-extend-region-for-font-lock () - (when sly-highlight-suppressed-forms - (condition-case c - (let (changedp) - (cl-multiple-value-setq (changedp font-lock-beg font-lock-end) - (sly-compute-region-for-font-lock font-lock-beg font-lock-end)) - changedp) - (error - (sly-warning - (concat "Caught error when trying to extend the region for fontification.\n" - "The error was: %S\n" - "Further: font-lock-beg=%d, font-lock-end=%d.") - c font-lock-beg font-lock-end))))) - -(defsubst sly-beginning-of-tlf () - (let ((pos (syntax-ppss-toplevel-pos (sly-current-parser-state)))) - (if pos (goto-char pos)))) - -(defun sly-compute-region-for-font-lock (orig-beg orig-end) - (let ((beg orig-beg) - (end orig-end)) - (goto-char beg) - (sly-beginning-of-tlf) - (cl-assert (not (cl-plusp (nth 0 (sly-current-parser-state))))) - (setq beg (let ((pt (point))) - (cond ((> (- beg pt) 20000) beg) - ((sly-search-directly-preceding-reader-conditional)) - (t pt)))) - (goto-char end) - (while (search-backward-regexp sly-reader-conditionals-regexp beg t) - (setq end (max end (save-excursion - (ignore-errors (sly-forward-reader-conditional)) - (point))))) - (cl-values (or (/= beg orig-beg) (/= end orig-end)) beg end))) - - -(defun sly-activate-font-lock-magic () - (font-lock-add-keywords - 'lisp-mode - `((sly-search-suppressed-forms 0 ,''sly-reader-conditional-face t))) - - (add-hook 'lisp-mode-hook - #'(lambda () - (add-hook 'font-lock-extend-region-functions - 'sly-extend-region-for-font-lock t t)))) - - -;;; Compile hotspots -;;; -(sly-byte-compile-hotspots - '(sly-extend-region-for-font-lock - sly-compute-region-for-font-lock - sly-search-directly-preceding-reader-conditional - sly-search-suppressed-forms - sly-beginning-of-tlf)) - -(provide 'sly-fontifying-fu) blob - 7524b01275e7350ab1d1bc78eee52b8ce249f194 (mode 644) blob + /dev/null --- elpa/sly-20231213.1030/contrib/sly-indentation.el +++ /dev/null @@ -1,31 +0,0 @@ -;; -*- lexical-binding: t; -*- -(require 'sly) -(require 'cl-lib) -(require 'sly-cl-indent "lib/sly-cl-indent") - -(define-sly-contrib sly-indentation - "Contrib interfacing `sly-cl-indent' and SLY." - (:slynk-dependencies slynk/indentation) - (:on-load - (setq sly--lisp-indent-current-package-function 'sly-current-package))) - -(defun sly-update-system-indentation (symbol indent packages) - (let ((list (gethash symbol sly-common-lisp-system-indentation)) - (ok nil)) - (if (not list) - (puthash symbol (list (cons indent packages)) - sly-common-lisp-system-indentation) - (dolist (spec list) - (cond ((equal (car spec) indent) - (dolist (p packages) - (unless (member p (cdr spec)) - (push p (cdr spec)))) - (setf ok t)) - (t - (setf (cdr spec) - (cl-set-difference (cdr spec) packages :test 'equal))))) - (unless ok - (puthash symbol (cons (cons indent packages) list) - sly-common-lisp-system-indentation))))) - -(provide 'sly-indentation) blob - 25d49fe1cffe5fff7a7e732dc9b384a63b04dff1 (mode 644) blob + /dev/null --- elpa/sly-20231213.1030/contrib/sly-mrepl.el +++ /dev/null @@ -1,1568 +0,0 @@ -;; -*- lexical-binding: t -*- An experimental implementation of -;; multiple REPLs multiplexed over a single Slime socket. M-x -;; sly-mrepl or M-x sly-mrepl-new create new REPL buffers. -;; -(require 'sly) -(require 'sly-autodoc) -(require 'cl-lib) -(require 'comint) - -(define-sly-contrib sly-mrepl - "Multiple REPLs." - (:license "GPL") - (:sly-dependencies sly-autodoc) - (:slynk-dependencies slynk/mrepl) - (:on-load - ;; Define a new "part action" for the `sly-part' buttons and change - ;; the `sly-inspector-part', `sly-db-local-variable' and - ;; `sly-trace-dialog-part' to include it. - ;; - (sly-button-define-part-action sly-mrepl-copy-part-to-repl - "Copy to REPL" (kbd "M-RET")) - (sly-button-define-part-action sly-mrepl-copy-call-to-repl - "Copy call to REPL" (kbd "M-S-")) - (button-type-put 'sly-inspector-part - 'sly-mrepl-copy-part-to-repl - 'sly-inspector-copy-part-to-repl) - (button-type-put 'sly-db-local-variable - 'sly-mrepl-copy-part-to-repl - 'sly-db-copy-part-to-repl) - (button-type-put 'sly-apropos-symbol - 'sly-mrepl-copy-part-to-repl - 'sly-apropos-copy-symbol-to-repl) - (button-type-put 'sly-db-frame - 'sly-mrepl-copy-call-to-repl - 'sly-db-copy-call-to-repl) - (eval-after-load "sly-trace-dialog" - `(progn - (button-type-put 'sly-trace-dialog-part - 'sly-mrepl-copy-part-to-repl - 'sly-trace-dialog-copy-part-to-repl) - (button-type-put 'sly-trace-dialog-spec - 'sly-mrepl-copy-call-to-repl - 'sly-trace-dialog-copy-call-to-repl))) - ;; Make C-c ~ bring popup REPL - ;; - (define-key sly-mode-map (kbd "C-c ~") 'sly-mrepl-sync) - (define-key sly-mode-map (kbd "C-c C-z") 'sly-mrepl) - (define-key sly-selector-map (kbd "~") 'sly-mrepl-sync) - (define-key sly-selector-map (kbd "r") 'sly-mrepl) - - ;; Insinuate ourselves in hooks - ;; - (add-hook 'sly-connected-hook 'sly-mrepl-on-connection) - (add-hook 'sly-net-process-close-hooks 'sly-mrepl--teardown-repls) - ;; The connection list is also tweaked - ;; - (setq sly-connection-list-button-action - #'(lambda (process) - (let ((sly-default-connection process)) - (sly-mrepl 'pop-to-buffer))))) - (:on-unload - ;; FIXME: This `:on-unload' is grossly incomplete - ;; - (remove-hook 'sly-connected-hook 'sly-mrepl-on-connection) - (remove-hook 'sly-net-process-close-hooks 'sly-mrepl--teardown-repls))) - - -;; User-visible variables -;; -(defvar sly-mrepl-mode-hook nil - "Functions run after `sly-mrepl-mode' is set up") - -(defvar sly-mrepl-hook nil - "Functions run after `sly-mrepl-new' sets up a REPL.") - -(defvar sly-mrepl-runonce-hook nil - "Functions run once after `sly-mrepl-new' sets up a REPL. - -After running the contents of this hook its default value is -emptied. See also `sly-mrepl-hook'") - -(defvar sly-mrepl-output-filter-functions comint-preoutput-filter-functions - "List of functions filtering Slynk's REPL output. -This variables behaves like `comint-preoutput-filter-functions', -for output printed to the REPL (not for evaluation results)") - -(defvar sly-mrepl-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "RET") 'sly-mrepl-return) - (define-key map (kbd "TAB") 'sly-mrepl-indent-and-complete-symbol) - (define-key map (kbd "C-c C-b") 'sly-interrupt) - (define-key map (kbd "C-c C-c") 'sly-interrupt) - (define-key map (kbd "C-c C-o") 'sly-mrepl-clear-recent-output) - (define-key map (kbd "C-c M-o") 'sly-mrepl-clear-repl) - (define-key map (kbd "M-p") 'sly-mrepl-previous-input-or-button) - (define-key map (kbd "M-n") 'sly-mrepl-next-input-or-button) - (define-key map (kbd "C-M-p") 'sly-button-backward) - (define-key map (kbd "C-M-n") 'sly-button-forward) - map)) - -(defvar sly-mrepl-pop-sylvester 'on-connection) - -(defface sly-mrepl-prompt-face - `((t (:inherit font-lock-builtin-face))) - "Face for the regular MREPL prompt." - :group 'sly-mode-faces) - -(defface sly-mrepl-note-face - `((t (:inherit font-lock-keyword-face))) - "Face for the MREPL notes." - :group 'sly-mode-faces) - -(defface sly-mrepl-output-face - '((((class color) - (background dark)) - (:foreground "VioletRed1")) - (((class color) - (background light)) - (:foreground "steel blue")) - (t - (:bold t :italic t))) - "Face for the regular MREPL prompt." - :group 'sly-mode-faces) - - -;; Internal variables -;; -(defvar sly-mrepl--remote-channel nil) -(defvar sly-mrepl--local-channel nil) -(defvar sly-mrepl--read-mark nil) -(defvar sly-mrepl--output-mark nil) -(defvar sly-mrepl--dedicated-stream nil) -(defvar sly-mrepl--last-prompt-overlay nil) -(defvar sly-mrepl--pending-output nil - "Output that can't be inserted right now.") -(defvar sly-mrepl--dedicated-stream-hooks) -(defvar sly-mrepl--history-separator "####\n") -(defvar sly-mrepl--dirty-history nil) - - -;; Major mode -;; -(define-derived-mode sly-mrepl-mode comint-mode "mrepl" - (sly-mode 1) - (cl-loop for (var value) - in `((comint-use-prompt-regexp nil) - (comint-inhibit-carriage-motion t) - (comint-input-sender sly-mrepl--input-sender) - (comint-output-filter-functions nil) - (comint-input-filter-functions nil) - (comint-history-isearch dwim) - (comint-input-ignoredups t) - (comint-input-history-ignore "^;") - (comint-prompt-read-only t) - (comint-process-echoes nil) - (comint-completion-addsuffix "") - (indent-line-function lisp-indent-line) - (sly-mrepl--read-mark nil) - (sly-mrepl--pending-output nil) - (sly-mrepl--output-mark ,(point-marker)) - (sly-mrepl--last-prompt-overlay ,(make-overlay 0 0 nil nil)) - (sly-find-buffer-package-function sly-mrepl-guess-package) - (sly-autodoc-inhibit-autodoc - sly-mrepl-inside-string-or-comment-p) - (mode-line-process nil) - (parse-sexp-ignore-comments t) - (syntax-propertize-function sly-mrepl--syntax-propertize) - (forward-sexp-function sly-mrepl--forward-sexp) - (comint-scroll-show-maximum-output nil) - (comint-scroll-to-bottom-on-input nil) - (comint-scroll-to-bottom-on-output nil) - (inhibit-field-text-motion nil) - (lisp-indent-function sly-common-lisp-indent-function) - (open-paren-in-column-0-is-defun-start nil) - (buffer-file-coding-system utf-8-unix) - ;; Paredit workaround (see - ;; https://github.com/joaotavora/sly/issues/110) - (paredit-override-check-parens-function (lambda (_c) t)) - (comment-start ";")) - do (set (make-local-variable var) value)) - (set-marker-insertion-type sly-mrepl--output-mark nil) - (add-hook 'kill-emacs-hook 'sly-mrepl--save-all-histories) - ;;(set (make-local-variable 'comint-get-old-input) 'ielm-get-old-input) - (set-syntax-table lisp-mode-syntax-table) - (set-keymap-parent sly-mrepl-mode-map nil) - - ;; The REPL buffer has interactive text buttons - (sly-interactive-buttons-mode 1) - - ;; Add hooks to isearch-mode placed strategically after the ones - ;; set by comint.el itself. - ;; - (add-hook 'isearch-mode-hook 'sly-mrepl--setup-comint-isearch t t) - (add-hook 'isearch-mode-end-hook 'sly-mrepl--teardown-comint-isearch t t) - - ;; Add a post-command-handler - ;; - (add-hook 'post-command-hook 'sly-mrepl--highlight-backreferences-maybe t t)) - - -;;; Channel methods -(sly-define-channel-type listener) - -(sly-define-channel-method listener :write-values (results) - (with-current-buffer (sly-channel-get self 'buffer) - (sly-mrepl--insert-results results))) - -(sly-define-channel-method listener :evaluation-aborted (&optional condition) - (with-current-buffer (sly-channel-get self 'buffer) - (sly-mrepl--catch-up) - (sly-mrepl--insert-note (format "Evaluation aborted on %s" condition)))) - -(sly-define-channel-method listener :write-string (string) - (with-current-buffer (sly-channel-get self 'buffer) - (sly-mrepl--insert-output string))) - -(sly-define-channel-method listener :set-read-mode (mode) - (with-current-buffer (sly-channel-get self 'buffer) - (cl-macrolet ((assert-soft - (what) `(unless ,what - (sly-warning - ,(format "Expectation failed: %s" what))))) - (let ((inhibit-read-only t)) - (cl-ecase mode - (:read - (assert-soft (null sly-mrepl--read-mark)) - ;; Give a chance for output to come in before we block it - ;; during the read. - (sly-mrepl--accept-process-output) - (setq sly-mrepl--read-mark (point)) - (add-text-properties (1- (point)) (point) - `(rear-nonsticky t)) - (sly-message "REPL now waiting for input to read")) - (:finished-reading - (assert-soft (integer-or-marker-p sly-mrepl--read-mark)) - (when sly-mrepl--read-mark - (add-text-properties (1- sly-mrepl--read-mark) (point) - `(face bold read-only t))) - (setq sly-mrepl--read-mark nil) - ;; github#456 need to flush any output that has overtaken - ;; the set-read-mode rpc. - (when sly-mrepl--pending-output - (sly-mrepl--insert-output "\n")) - (sly-message "REPL back to normal evaluation mode"))))))) - -(sly-define-channel-method listener :prompt (&rest prompt-args) - (with-current-buffer (sly-channel-get self 'buffer) - (apply #'sly-mrepl--insert-prompt prompt-args))) - -(sly-define-channel-method listener :open-dedicated-output-stream - (port _coding-system) - (with-current-buffer (sly-channel-get self 'buffer) - ;; HACK: no coding system - (set (make-local-variable 'sly-mrepl--dedicated-stream) - (sly-mrepl--open-dedicated-stream self port nil)))) - -(sly-define-channel-method listener :clear-repl-history () - (with-current-buffer (sly-channel-get self 'buffer) - (let ((inhibit-read-only t)) - (erase-buffer) - (sly-mrepl--insert-note "Cleared REPL history")))) - -(sly-define-channel-method listener :server-side-repl-close () - (with-current-buffer (sly-channel-get self 'buffer) - (sly-mrepl--teardown "Server side close" 'dont-signal-server))) - - -;;; Button type -;;; -(define-button-type 'sly-mrepl-part :supertype 'sly-part - 'sly-button-inspect - #'(lambda (entry-idx value-idx) - (sly-eval-for-inspector `(slynk-mrepl:inspect-entry - ,sly-mrepl--remote-channel - ,entry-idx - ,value-idx) - :inspector-name (sly-maybe-read-inspector-name))) - 'sly-button-describe - #'(lambda (entry-idx value-idx) - (sly-eval-describe `(slynk-mrepl:describe-entry ,sly-mrepl--remote-channel - ,entry-idx - ,value-idx))) - 'sly-button-pretty-print - #'(lambda (entry-idx value-idx) - (sly-eval-describe `(slynk-mrepl:pprint-entry ,sly-mrepl--remote-channel - ,entry-idx - ,value-idx))) - 'sly-mrepl-copy-part-to-repl 'sly-mrepl--copy-part-to-repl) - - -;;; Internal functions -;;; -(defun sly-mrepl--buffer-name (connection &optional handle) - (sly-buffer-name :mrepl :connection connection - :suffix handle)) - -(defun sly-mrepl--teardown-repls (process) - (cl-loop for buffer in (buffer-list) - when (buffer-live-p buffer) - do (with-current-buffer buffer - (when (and (eq major-mode 'sly-mrepl-mode) - (eq sly-buffer-connection process)) - (sly-mrepl--teardown (process-get process - 'sly-net-close-reason)))))) - -(defun sly-mrepl--process () (get-buffer-process (current-buffer))) ;stupid - -(defun sly-mrepl--mark () - "Returns a marker to the end of the last prompt." - (let ((proc (sly-mrepl--process))) - (unless proc (sly-user-error "Not in a connected REPL")) - (process-mark proc))) - -(defun sly-mrepl--safe-mark () - "Like `sly-mrepl--mark', but safe if there's no process." - (if (sly-mrepl--process) (sly-mrepl--mark) (point-max))) - -(defmacro sly-mrepl--commiting-text (props &rest body) - (declare (debug (sexp &rest form)) - (indent 1)) - (let ((start-sym (cl-gensym))) - `(let ((,start-sym (marker-position (sly-mrepl--mark))) - (inhibit-read-only t)) - ,@body - (add-text-properties ,start-sym (sly-mrepl--mark) - (append '(read-only t front-sticky (read-only)) - ,props))))) - -(defun sly-mrepl--forward-sexp (n) - "Just like `forward-sexp' unless point it at prompt start. -In that case, moving a sexp backward does nothing." - (if (or (cl-plusp n) - (/= (point) (sly-mrepl--safe-mark))) - (let ((forward-sexp-function nil)) - (forward-sexp n)))) - -(defun sly-mrepl--syntax-propertize (beg end) - "Make everything up to current prompt comment syntax." - (remove-text-properties beg end '(syntax-table nil)) - (let ((end (min end (sly-mrepl--safe-mark))) - (beg beg)) - (when (> end beg) - (unless (nth 8 (syntax-ppss beg)) - (add-text-properties beg (1+ beg) - `(syntax-table ,(string-to-syntax "!")))) - (add-text-properties (1- end) end - `(syntax-table ,(string-to-syntax "!")))))) - -(defun sly-mrepl--call-with-repl (repl-buffer fn) - (with-current-buffer repl-buffer - (cl-loop - while (not (buffer-local-value 'sly-mrepl--remote-channel - (current-buffer))) - do - (sly-warning "Waiting for a REPL to be setup for %s" - (sly-connection-name (sly-current-connection))) - (sit-for 0.5)) - (funcall fn))) - -(defmacro sly-mrepl--with-repl (repl-buffer &rest body) - (declare (indent 1) (debug (sexp &rest form))) - `(sly-mrepl--call-with-repl ,repl-buffer #'(lambda () ,@body))) - -(defun sly-mrepl--insert (string &optional face) - (sly-mrepl--commiting-text (when face - `(face ,face font-lock-face ,face)) - (comint-output-filter (sly-mrepl--process) - (propertize string 'sly-mrepl-break-output t)))) - -(defun sly-mrepl--break-output-p (pos) - (and (not (eq ?\n (char-after pos))) - (get-char-property pos 'sly-mrepl-break-output))) - -(defun sly-mrepl--insert-output (string &optional face nofilters) - (cond ((and (not sly-mrepl--read-mark) string) - (let ((inhibit-read-only t) - (start (marker-position sly-mrepl--output-mark)) - (face (or face - 'sly-mrepl-output-face))) - - (save-excursion - (goto-char sly-mrepl--output-mark) - (cond ((and (not (bobp)) - (sly-mrepl--break-output-p (1- start)) - (not (zerop (current-column)))) - (insert-before-markers "\n"))) - (setq string - (propertize (concat sly-mrepl--pending-output string) - 'face face - 'font-lock-face face)) - (setq sly-mrepl--pending-output nil) - (unless nofilters - (run-hook-wrapped - 'sly-mrepl-output-filter-functions - (lambda (fn) - (setq string (funcall fn string)) - nil))) - (insert-before-markers string) - (cond ((and (not (zerop (current-column))) - (sly-mrepl--break-output-p (point))) - (save-excursion (insert "\n")))) - (add-text-properties start sly-mrepl--output-mark - `(read-only t front-sticky (read-only) - field sly-mrepl--output))))) - (t - (setq sly-mrepl--pending-output - (concat sly-mrepl--pending-output string)) - (sly-message "Some output saved for later insertion")))) - -(defun sly-mrepl--insert-note (string &optional face) - (let* ((face (or face 'sly-mrepl-note-face)) - (string (replace-regexp-in-string "^" "; " string))) - (cond ((sly-mrepl--process) - ;; notes are inserted "synchronously" with the process mark process - (sly-mrepl--ensure-newline) - (sly-mrepl--insert string face)) - (t - ;; If no process yet, fall back to the simpler strategy. - (sly-mrepl--insert-output string face))))) - -(defun sly-mrepl--send-input-sexp () - (goto-char (point-max)) - (save-excursion - (skip-chars-backward "\n\t\s") - (delete-region (max (point) - (sly-mrepl--mark)) - (point-max))) - (buffer-disable-undo) - (overlay-put sly-mrepl--last-prompt-overlay 'face 'highlight) - (set (make-local-variable 'sly-mrepl--dirty-history) t) - (sly-mrepl--commiting-text - `(field sly-mrepl-input - keymap ,(let ((map (make-sparse-keymap))) - (define-key map (kbd "RET") 'sly-mrepl-insert-input) - (define-key map [return] 'sly-mrepl-insert-input) - (define-key map [mouse-2] 'sly-mrepl-insert-input) - map)) - (comint-send-input)) - (sly-mrepl--ensure-prompt-face)) - -(defun sly-mrepl--ensure-newline () - (unless (save-excursion - (goto-char (sly-mrepl--mark)) - (zerop (current-column))) - (sly-mrepl--insert "\n"))) - -(defun sly-mrepl--accept-process-output () - (when (and sly-mrepl--dedicated-stream - (process-live-p sly-mrepl--dedicated-stream)) - ;; This non-blocking call should be enough to allow asynch calls - ;; to `sly-mrepl--insert-output' to still see the correct value - ;; for `sly-mrepl--output-mark' just before we call - ;; `sly-mrepl--catch-up'. - (while (accept-process-output sly-mrepl--dedicated-stream - 0 - (and (eq (window-system) 'w32) 1))))) - -(defun sly-mrepl--ensure-prompt-face () - "Override `comint.el''s use of `comint-highlight-prompt'." - (let ((inhibit-read-only t)) - (add-text-properties (overlay-start sly-mrepl--last-prompt-overlay) - (overlay-end sly-mrepl--last-prompt-overlay) - '(font-lock-face sly-mrepl-prompt-face)))) - -(defun sly-mrepl-default-prompt (_package - nickname - error-level - _entry-idx - _condition) - "Compute default SLY prompt string. -Suitable for `sly-mrepl-prompt-formatter'." - (concat - (when (cl-plusp error-level) - (concat (sly-make-action-button - (format "[%d]" error-level) - #'sly-db-pop-to-debugger-maybe) - " ")) - (propertize - (concat nickname "> ") - 'face 'sly-mrepl-prompt-face - 'font-lock-face 'sly-mrepl-prompt-face))) - -(defcustom sly-mrepl-prompt-formatter #'sly-mrepl-default-prompt - "Compute propertized string to use as REPL prompt. -Value is a function passed at least 5 arguments with the -following signature: - -(PACKAGE NICKNAME ERROR-LEVEL NEXT-ENTRY-IDX CONDITION &REST) - -PACKAGE is a string denoring the full name of the current -package. NICKNAME is the shortest or preferred nickname of -PACKAGE, according to the Lisp variables -SLYNK:*CANONICAL-PACKAGE-NICKNAMES* and -SLYNK:*AUTO-ABBREVIATE-DOTTED-PACKAGES*. ERROR-LEVEL is a -integer counting the number of outstanding errors. -NEXT-ENTRY-IDX is a number identifying future evaluation results -for backreferencing purposes. Depending on ERROR-LEVEL, -CONDITION is either nil or a string containing the printed -representation of the outstanding condition that caused the -current ERROR-LEVEL." - :type 'function - :group 'sly) - -(defun sly-mrepl--insert-prompt (package nickname error-level - &optional next-entry-idx condition) - (sly-mrepl--accept-process-output) - (overlay-put sly-mrepl--last-prompt-overlay 'face 'bold) - (when condition - (sly-mrepl--insert-note (format "Debugger entered on %s" condition))) - (sly-mrepl--ensure-newline) - (sly-mrepl--catch-up) - (let ((beg (marker-position (sly-mrepl--mark)))) - (sly-mrepl--insert - (propertize - (funcall sly-mrepl-prompt-formatter - package - nickname - error-level - next-entry-idx - condition) - 'sly-mrepl--prompt (downcase package))) - (move-overlay sly-mrepl--last-prompt-overlay beg (sly-mrepl--mark))) - (sly-mrepl--ensure-prompt-face) - (buffer-disable-undo) - (buffer-enable-undo)) - -(defun sly-mrepl--copy-part-to-repl (entry-idx value-idx) - (sly-mrepl--copy-objects-to-repl - `(,entry-idx ,value-idx) - :before (format "Returning value %s of history entry %s" - value-idx entry-idx))) - -(cl-defun sly-mrepl--eval-for-repl - (slyfun-and-args - &key insert-p before-prompt after-prompt (pop-to-buffer t)) - "Evaluate SLYFUN-AND-ARGS in Slynk, then call callbacks. - -SLYFUN-AND-ARGS is (SLYFUN . ARGS) and is called in -Slynk. SLYFUN's multiple return values are captured in a list and -passed to the optional unary callbacks BEFORE-PROMPT and -AFTER-PROMPT, called before or after prompt insertion, -respectively. - -If INSERT-P is non-nil, SLYFUN's results are printable -representations of Slynk objects and should be inserted into the -REPL. POP-TO-BUFFER says whether to pop the REPL buffer." - (sly-eval-async `(slynk-mrepl:eval-for-mrepl - ,sly-mrepl--remote-channel - ',(car slyfun-and-args) - ,@(cdr slyfun-and-args)) - (lambda (prompt-args-and-results) - (cl-destructuring-bind (prompt-args results) - prompt-args-and-results - (goto-char (sly-mrepl--mark)) - (let ((saved-text (buffer-substring (point) (point-max)))) - (delete-region (point) (point-max)) - (sly-mrepl--catch-up) - (when before-prompt - (funcall before-prompt results)) - (when insert-p - (sly-mrepl--insert-results results)) - (apply #'sly-mrepl--insert-prompt prompt-args) - (when pop-to-buffer - (pop-to-buffer (current-buffer))) - (goto-char (sly-mrepl--mark)) - (insert saved-text) - (when after-prompt - (funcall after-prompt results))))))) - -(cl-defun sly-mrepl--copy-objects-to-repl - (method-args &key before after (pop-to-buffer t)) - "Recall objects in the REPL history as a new entry. -METHOD-ARGS are SLYNK-MREPL:COPY-TO-REPL's optional args. If nil -, consider the globally saved objects that -SLYNK-MREPL:GLOBALLY-SAVE-OBJECT stored. Otherwise, it is a -list (ENTRY-IDX VALUE-IDX). BEFORE and AFTER as in -`sly-mrepl--save-and-copy-for-repl' POP-TO-BUFFER as in -`sly-mrepl--eval-for-repl'." - (sly-mrepl--eval-for-repl - `(slynk-mrepl:copy-to-repl - ,@method-args) - :before-prompt (if (stringp before) - (lambda (objects) - (sly-mrepl--insert-note before) - (sly-mrepl--insert-results objects)) - before) - :after-prompt after - :pop-to-buffer pop-to-buffer)) - -(defun sly-mrepl--make-result-button (result idx) - (sly--make-text-button (car result) nil - :type 'sly-mrepl-part - 'part-args (list (cadr result) idx) - 'part-label (format "REPL Result") - 'sly-mrepl--result result - 'sly-button-search-id (sly-button-next-search-id))) - -(defun sly-mrepl--insert-results (results) - (let* ((comint-preoutput-filter-functions nil)) - (if (null results) - (sly-mrepl--insert-note "No values") - (cl-loop for result in results - for idx from 0 - do - (sly-mrepl--ensure-newline) - (sly-mrepl--insert - (sly-mrepl--make-result-button result idx)))))) - -(defun sly-mrepl--catch-up () - "Synchronize the output mark with the REPL process mark." - (set-marker sly-mrepl--output-mark (sly-mrepl--mark))) - -(defun sly-mrepl--input-sender (_proc string) - (sly-mrepl--send-string (substring-no-properties string))) - -(defun sly-mrepl--send-string (string &optional _command-string) - (sly-mrepl--send `(:process ,string))) - -(defun sly-mrepl--send (msg) - "Send MSG to the remote channel." - (sly-send-to-remote-channel sly-mrepl--remote-channel msg)) - -(defun sly-mrepl--find-buffer (&optional connection thread) - "Find the shortest-named (default) `sly-mrepl' buffer for CONNECTION." - ;; CONNECTION defaults to the `sly-default-connection' passing - ;; through `sly-connection'. Seems to work OK... - ;; - (let* ((connection (or connection - (let ((sly-buffer-connection nil) - (sly-dispatching-connection nil)) - (sly-connection)))) - (repls (cl-remove-if-not - (lambda (x) - (with-current-buffer x - (and (eq major-mode 'sly-mrepl-mode) - (eq sly-buffer-connection connection) - (or (not thread) - (eq thread sly-current-thread))))) - (buffer-list))) - (sorted (cl-sort repls #'< :key (sly-compose #'length #'buffer-name)))) - (car sorted))) - -(defun sly-mrepl--find-create (connection) - (or (sly-mrepl--find-buffer connection) - (sly-mrepl-new connection))) - -(defun sly-mrepl--busy-p () - (>= sly-mrepl--output-mark (sly-mrepl--mark))) - -(defcustom sly-mrepl-history-file-name (expand-file-name "~/.sly-mrepl-history") - "File used to store SLY REPL's input history across sessions." - :type 'file - :group 'sly) - -(defun sly-mrepl--read-input-ring () - (let ((comint-input-ring-separator sly-mrepl--history-separator) - (comint-input-ring-file-name sly-mrepl-history-file-name)) - (comint-read-input-ring))) - -(defcustom sly-mrepl-prevent-duplicate-history 'move - "If non-nil, prevent duplicate entries in input history. - -Otherwise (if nil), input entry are always added to the end of -the history, even if they already occur in the history. - -If the non-nil value is `move', the previously occuring entry is -discarded, i.e. moved to a more recent spot. Any other non-nil -value laves the previous entry untouched and it is the more -recent entry that is discarded." - :type 'symbol - :group 'sly) - -(defun sly-mrepl--merge-and-save-history () - (let* - ;; To merge the file's history with the current buffer's - ;; history, sntart by deep-copying `comint-input-ring' to a - ;; separate variable. - ;; - ((current-ring (copy-tree comint-input-ring 'vectors-too)) - (index (ring-length current-ring)) - (comint-input-ring-separator sly-mrepl--history-separator) - (comint-input-ring-file-name sly-mrepl-history-file-name)) - ;; this sets `comint-input-ring' from the file - ;; - (sly-mrepl--read-input-ring) - ;; loop `current-ring', which potentially contains new entries and - ;; re-add entries to `comint-input-ring', which is now synched - ;; with the file and will be written to disk. Respect - ;; `sly-mrepl-prevent-duplicate-history'. - ;; - (cl-loop for i from (1- index) downto 0 - for item = (ring-ref current-ring i) - for existing-index = (ring-member comint-input-ring item) - do (cond ((and existing-index - (eq sly-mrepl-prevent-duplicate-history 'move)) - (ring-remove comint-input-ring existing-index) - (ring-insert comint-input-ring item)) - ((and existing-index - (not sly-mrepl-prevent-duplicate-history)) - (ring-insert comint-input-ring item)) - (t - (ring-insert comint-input-ring item))) - unless (ring-member comint-input-ring item) - do (ring-insert comint-input-ring item)) - ;; Now save `comint-input-ring' - (let ((coding-system-for-write 'utf-8-unix)) - (comint-write-input-ring)) - (set (make-local-variable 'sly-mrepl--dirty-history) nil))) - -(defun sly-mrepl--save-all-histories () - (cl-loop for buffer in (buffer-list) - do - (with-current-buffer buffer - (when (and (eq major-mode 'sly-mrepl-mode) - sly-mrepl--dirty-history) - (sly-mrepl--merge-and-save-history))))) - -(defun sly-mrepl--teardown (&optional reason dont-signal-server) - (remove-hook 'kill-buffer-hook 'sly-mrepl--teardown t) - (let ((inhibit-read-only t)) - (goto-char (point-max)) - (let ((start (point))) - (unless (zerop (current-column)) (insert "\n")) - (insert (format "; %s" (or reason "REPL teardown"))) - (unless (zerop (current-column)) (insert "\n")) - (insert "; --------------------------------------------------------\n") - (add-text-properties start (point) '(read-only t)))) - (sly-mrepl--merge-and-save-history) - (when sly-mrepl--dedicated-stream - (process-put sly-mrepl--dedicated-stream 'sly-mrepl--channel nil) - (kill-buffer (process-buffer sly-mrepl--dedicated-stream))) - (sly-close-channel sly-mrepl--local-channel) - ;; signal lisp that we're closingq - (unless dont-signal-server - (ignore-errors - ;; uses `sly-connection', which falls back to - ;; `sly-buffer-connection'. If that is closed it's probably - ;; because lisp died from (SLYNK:QUIT-LISP) already, and so - (sly-mrepl--send `(:teardown)))) - (set (make-local-variable 'sly-mrepl--remote-channel) nil) - (when (sly-mrepl--process) - (delete-process (sly-mrepl--process)))) - -(defun sly-mrepl--dedicated-stream-output-filter (process string) - (let* ((channel (process-get process 'sly-mrepl--channel)) - (buffer (and channel - (sly-channel-get channel 'buffer)))) - (if (buffer-live-p buffer) - (with-current-buffer buffer - (when (and (cl-plusp (length string)) - (eq (process-status sly-buffer-connection) 'open)) - (sly-mrepl--insert-output string))) - (sly-warning "No channel in process %s, probably torn down" process)))) - -(defun sly-mrepl--open-dedicated-stream (channel port coding-system) - (let* ((name (format "sly-dds-%s-%s" - (process-get sly-buffer-connection - 'sly--net-connect-counter) - (sly-channel.id channel))) - (stream (open-network-stream - name - (generate-new-buffer - (format " *%s*" name)) - (car (process-contact sly-buffer-connection)) - port)) - (emacs-coding-system (car (cl-find coding-system - sly-net-valid-coding-systems - :key #'cl-third)))) - (set-process-query-on-exit-flag stream nil) - (set-process-plist stream `(sly-mrepl--channel ,channel)) - (set-process-filter stream 'sly-mrepl--dedicated-stream-output-filter) - (set-process-coding-system stream emacs-coding-system emacs-coding-system) - (sly--when-let (secret (sly-secret)) - (sly-net-send secret stream)) - (run-hook-with-args 'sly-mrepl--dedicated-stream-hooks stream) - stream)) - -(cl-defun sly-mrepl--save-and-copy-for-repl - (slyfun-and-args &key repl before after) - "Evaluate SLYFUN-AND-ARGS in Slynk and prepare to copy to REPL. -BEFORE is a string inserted as a note, or a nullary function -which is run just before the object is copied to the -REPL. Optional BEFORE and AFTER are unary functions called with a -list of the saved values' presentations strings and run before -and after the the the prompt are inserted, respectively. BEFORE -can also be a string in which case it is inserted via -`sly-insert-note' followed by the saved values' presentations. -REPL is the REPL buffer to return the objects to." - (sly-eval-async - `(slynk-mrepl:globally-save-object ',(car slyfun-and-args) - ,@(cdr slyfun-and-args)) - #'(lambda (_ignored) - (sly-mrepl--copy-globally-saved-to-repl :before before - :after after - :repl repl)))) - -(cl-defun sly-mrepl--copy-globally-saved-to-repl - (&key before after repl (pop-to-buffer t)) - "Copy last globally saved values to REPL, or active REPL. -BEFORE and AFTER as described in -`sly-mrepl--save-and-copy-for-repl'." - (sly-mrepl--with-repl (or repl - (sly-mrepl--find-create (sly-connection))) - (sly-mrepl--copy-objects-to-repl nil - :before before - :after after - :pop-to-buffer pop-to-buffer))) - -(defun sly-mrepl--insert-call (spec results) - (delete-region (sly-mrepl--mark) (point-max)) - (insert (format - "%s" - `(,spec - ,@(cl-loop for (_object j constant) in results - for i from 0 - collect - (or constant - (make-symbol (format "#v%d:%d" j i)))))))) - -(defun sly-mrepl--assert-mrepl () - (unless (eq major-mode 'sly-mrepl-mode) - (sly-error "Not in a mREPL buffer"))) - - -;;; ELI-like history (and a bugfix) -;;; -;;; -(defcustom sly-mrepl-eli-like-history-navigation nil - "If non-NIL navigate history like ELI. -When this option is active, previous history entries navigated to -by M-p and M-n keep the current input and use it to surround the -history entry navigated to." - :type 'boolean - :group 'sly) - -(defvar sly-mrepl--eli-input nil) - -(defun sly-mrepl--set-eli-input () - (setq sly-mrepl--eli-input - (and sly-mrepl-eli-like-history-navigation - (let* ((offset (- (point) (sly-mrepl--mark))) - (existing (and (> offset 0) - (buffer-substring (sly-mrepl--mark) - (point-max))))) - (when existing - (cons (substring existing 0 offset) - (substring existing offset))))))) - -(defun sly-mrepl--keep-eli-input-maybe () - (when sly-mrepl--eli-input - (save-excursion - (goto-char (sly-mrepl--mark)) - (insert (car sly-mrepl--eli-input)) - (goto-char (point-max)) - (insert (cdr sly-mrepl--eli-input))))) - -(defvar sly-mrepl--eli-input-overlay nil) - -(defun sly-mrepl--surround-with-eli-input-overlay () - (if sly-mrepl--eli-input-overlay - (move-overlay sly-mrepl--eli-input-overlay - (sly-mrepl--mark) (point-max)) - (setq sly-mrepl--eli-input-overlay - (make-overlay (sly-mrepl--mark) (point-max)))) - (overlay-put sly-mrepl--eli-input-overlay - 'before-string (car sly-mrepl--eli-input)) - (overlay-put sly-mrepl--eli-input-overlay - 'after-string (cdr sly-mrepl--eli-input))) - -(defun sly-mrepl--setup-comint-isearch () - ;; Defeat Emacs bug 19572 in Emacs whereby comint refuses to - ;; i-search multi-line history entries. The doc of - ;; `isearch-search-fun-function' should explain the need for this - ;; lambda madness. - ;; - (unless (eq isearch-search-fun-function - 'isearch-search-fun-default) - (set (make-local-variable 'isearch-search-fun-function) - #'(lambda () - #'(lambda (&rest args) - (cl-letf - (((symbol-function - 'comint-line-beginning-position) - #'field-beginning)) - (apply (comint-history-isearch-search) - args)))))) - (sly-mrepl--set-eli-input) - (when sly-mrepl-eli-like-history-navigation - (set (make-local-variable 'isearch-push-state-function) - #'sly-mrepl--isearch-push-state))) - -(defun sly-mrepl--isearch-push-state (&rest args) - (apply #'comint-history-isearch-push-state args) - (unless (memq this-command - '(isearch-backward isearch-forward)) - (sly-mrepl--surround-with-eli-input-overlay))) - -(defun sly-mrepl--teardown-comint-isearch () - (set (make-local-variable 'isearch-search-fun-function) - 'isearch-search-fun-default) - (when (overlayp sly-mrepl--eli-input-overlay) - (delete-overlay sly-mrepl--eli-input-overlay) - (setq sly-mrepl--eli-input-overlay nil)) - (sly-mrepl--keep-eli-input-maybe)) - - -;;; Interactive commands -;;; -(defun sly-mrepl-indent-and-complete-symbol (arg) - "Indent the current line, perform symbol completion or show arglist. -Completion performed by `completion-at-point' or -`company-complete'. If there's no symbol at the point, show the -arglist for the most recently enclosed macro or function." - (interactive "P") - (let ((pos (point)) - (fn (if (bound-and-true-p company-mode) - 'company-complete - 'completion-at-point))) - (indent-for-tab-command arg) - (when (= pos (point)) - (cond ((save-excursion (re-search-backward "[^() \n\t\r]+\\=" nil t)) - (funcall fn)) - ((memq (char-before) '(?\t ?\ )) - (sly-show-arglist)))))) - -(defun sly-mrepl-return (&optional end-of-input) - "If the input is a whole expression, evaluate it and return the result." - (interactive "P") - (cl-assert (sly-connection)) - (cl-assert (process-live-p (sly-mrepl--process)) nil - "No local live process, cannot use this REPL") - (accept-process-output) - (cond ((and - (not sly-mrepl--read-mark) - (sly-mrepl--busy-p)) - (sly-message "REPL is busy")) - ((and (not sly-mrepl--read-mark) - (or (sly-input-complete-p (sly-mrepl--mark) (point-max)) - end-of-input)) - (sly-mrepl--send-input-sexp) - (sly-mrepl--catch-up)) - (sly-mrepl--read-mark - (unless end-of-input - (goto-char (point-max)) - (newline)) - (let ((comint-input-filter (lambda (_s) nil))) - (comint-send-input 'no-newline)) - (sly-mrepl--catch-up)) - (t - (newline-and-indent) - (sly-message "Input not complete")))) - -(defun sly-mrepl-previous-input-or-button (n) - (interactive "p") - (if (>= (point) (sly-mrepl--mark)) - (progn - (unless (memq last-command - '(sly-mrepl-previous-input-or-button - sly-mrepl-next-input-or-button)) - (sly-mrepl--set-eli-input)) - (comint-previous-input n) - (sly-mrepl--keep-eli-input-maybe)) - (sly-button-backward n))) - -(defun sly-mrepl-next-input-or-button (n) - (interactive "p") - (sly-mrepl-previous-input-or-button (- n))) - -(put 'sly-mrepl-next-input-or-button 'sly-button-navigation-command t) -(put 'sly-mrepl-previous-input-or-button 'sly-button-navigation-command t) - -;;;###autoload -(defun sly-mrepl (&optional display-action) - "Find or create the first useful REPL for the default connection. -If supplied, DISPLAY-ACTION is called on the -buffer. Interactively, DISPLAY-ACTION defaults to using -`switch-to-buffer' unless the intended buffer is already visible -in some window, in which case that window is selected." - (interactive (list (lambda (buf) - (let ((w (get-buffer-window buf))) - (if w (select-window w) (switch-to-buffer buf)))))) - (let* ((buffer - (sly-mrepl--find-create (sly-current-connection)))) - (when display-action - (funcall display-action buffer)) - buffer)) - -(defun sly-mrepl-on-connection () - (let* ((inferior-buffer - (and (sly-process) (process-buffer (sly-process)))) - (inferior-window - (and inferior-buffer (get-buffer-window inferior-buffer t)))) - (let ((sly-mrepl-pop-sylvester - (or (eq sly-mrepl-pop-sylvester 'on-connection) - sly-mrepl-pop-sylvester))) - (sly-mrepl 'pop-to-buffer)) - (when inferior-window - (bury-buffer inferior-buffer) - (delete-window inferior-window)) - (goto-char (point-max)))) - -(defun sly-mrepl-new (connection &optional handle) - "Create and setup a new REPL buffer for CONNECTION. -CONNECTION defaults to the current SLY connection. If such a -buffer already exists, or a prefix arg is given, prompt for a -handle to distinguish the new buffer from the existing." - (interactive - ;; FIXME: Notice a subtle bug/feature than when calling - ;; interactively in a buffer which has a connection, but not the - ;; default connection, the new REPL will be for that connection. - (let ((connection (sly-connection))) - (list connection - (if (or (get-buffer (sly-mrepl--buffer-name connection)) - current-prefix-arg) - (sly-read-from-minibuffer - "Nickname for this new REPL? "))))) - (let* ((name (sly-mrepl--buffer-name connection handle)) - (existing (get-buffer name))) - (when (and handle existing) - (sly-user-error "A REPL with that handle already exists")) - ;; Take this oportunity to save any other REPL histories so that - ;; the new REPL will see them. - (sly-mrepl--save-all-histories) - (let* ((local (sly-make-channel sly-listener-channel-methods)) - (buffer (pop-to-buffer name)) - (default-directory (if (file-readable-p default-directory) - default-directory - (expand-file-name "~/")))) - (with-current-buffer buffer - (sly-mrepl-mode) - (when (and (not existing) - (eq sly-mrepl-pop-sylvester t)) - (sly-mrepl--insert-note - (concat "\n" (sly-mrepl-random-sylvester) "\n\n") - 'sly-mrepl-output-face)) - (setq sly-buffer-connection connection) - (start-process (format "sly-pty-%s-%s" - (process-get connection - 'sly--net-connect-counter) - (sly-channel.id local)) - (current-buffer) - nil) - (set-process-query-on-exit-flag (sly-mrepl--process) nil) - (setq header-line-format - (format "Waiting for REPL creation ack for channel %d..." - (sly-channel.id local))) - (sly-channel-put local 'buffer (current-buffer)) - (add-hook 'kill-buffer-hook 'sly-mrepl--teardown nil 'local) - (set (make-local-variable 'sly-mrepl--local-channel) local)) - (sly-eval-async - `(slynk-mrepl:create-mrepl ,(sly-channel.id local)) - (lambda (result) - (cl-destructuring-bind (remote thread-id) result - (with-current-buffer buffer - (sly-mrepl--read-input-ring) - (setq header-line-format nil) - (setq sly-current-thread thread-id) - (set (make-local-variable 'sly-mrepl--remote-channel) remote) - (unwind-protect - (run-hooks 'sly-mrepl-hook 'sly-mrepl-runonce-hook) - (set-default 'sly-mrepl-runonce-hook nil)))))) - buffer))) - -(defun sly-mrepl-insert-input (pos) - (interactive (list (if (mouse-event-p last-input-event) - (posn-point (event-end last-input-event)) - (point)))) - (sly-mrepl--assert-mrepl) - (let* ((pos (if (eq (field-at-pos pos) 'sly-mrepl-input) - pos - (1+ pos))) - (new-input (and - (eq (field-at-pos (1+ pos)) 'sly-mrepl-input) - (field-string-no-properties pos))) - (offset (and new-input - (- (point) (field-beginning pos))))) - (cond (new-input - (goto-char (sly-mrepl--mark)) - (delete-region (point) (point-max)) - (insert (sly-trim-whitespace new-input)) - (goto-char (+ (sly-mrepl--mark) offset))) - (t - (sly-user-error "No input at point"))))) - -(defun sly-mrepl-guess-package (&optional point interactive) - (interactive (list (point) t)) - (let* ((point (or point (point))) - (probe - (previous-single-property-change point - 'sly-mrepl--prompt)) - (package (and probe - (or (get-text-property probe 'sly-mrepl--prompt) - (let ((probe2 - (previous-single-property-change - probe 'sly-mrepl--prompt))) - (and probe2 - (get-text-property probe2 - 'sly-mrepl--prompt))))))) - (when interactive - (sly-message "Guessed package \"%s\"" package)) - package)) - -(define-obsolete-function-alias - 'sly-mrepl-sync-package-and-default-directory 'sly-mrepl-sync - "1.0.0-alpha-3") - -(defun sly-mrepl-sync (&optional package directory expression) - "Go to the REPL, and set Slynk's PACKAGE and DIRECTORY. -Also yank EXPRESSION into the prompt. Interactively gather -PACKAGE and DIRECTORY these values from the current buffer, if -available. In this scenario EXPRESSION is only set if a C-u -prefix argument is given." - (interactive (list (sly-current-package) - (and buffer-file-name - default-directory) - (and current-prefix-arg - (sly-last-expression)))) - (sly-mrepl--with-repl (sly-mrepl--find-create (sly-connection)) - (when directory - (cd directory)) - (sly-mrepl--eval-for-repl - `(slynk-mrepl:sync-package-and-default-directory - :package-name ,package - :directory ,(and directory - (sly-to-lisp-filename directory))) - :insert-p nil - :before-prompt - #'(lambda (results) - (cl-destructuring-bind (package-2 directory-2) results - (sly-mrepl--insert-note - (cond ((and package directory) - (format "Synched package to %s and directory to %s" - package-2 directory-2)) - (directory - (format "Synched directory to %s" directory-2)) - (package - (format "Synched package to %s" package-2)) - (t - (format "Remaining in package %s and directory %s" - package-2 directory-2)))))) - :after-prompt - #'(lambda (_results) - (when expression - (goto-char (point-max)) - (let ((saved (point))) - (insert expression) - (when (string-match "\n" expression) - (indent-region saved (point-max))))))))) - -(defun sly-mrepl-clear-repl () - "Clear all this REPL's output history. -Doesn't clear input history." - (interactive) - (sly-mrepl--assert-mrepl) - (sly-mrepl--send `(:clear-repl-history))) - -(defun sly-mrepl-clear-recent-output () - "Clear this REPL's output between current and last prompt." - (interactive) - (sly-mrepl--assert-mrepl) - (cl-loop for search-start = - (set-marker (make-marker) - (1+ (overlay-start sly-mrepl--last-prompt-overlay))) - then pos - for pos = (set-marker - search-start - (previous-single-property-change search-start 'field)) - while (and (marker-position pos) - ;; FIXME: fragile (1- pos), use narrowing - (not (get-text-property (1- pos) 'sly-mrepl--prompt)) - (> pos (point-min))) - when (eq (field-at-pos pos) 'sly-mrepl--output) - do (let ((inhibit-read-only t)) - (delete-region (field-beginning pos) - (+ - (if (eq ?\n (char-before (field-end pos))) 0 1) - (field-end pos))) - (sly-mrepl--insert-output "; Cleared last output" - 'sly-mrepl-note-face)) - and return nil) - (sly-message "Cleared last output")) - -(defun sly-mrepl-next-prompt () - "Go to the beginning of the next REPL prompt." - (interactive) - (let ((pos (next-single-char-property-change (line-beginning-position 2) - 'sly-mrepl--prompt))) - (goto-char pos)) - (end-of-line)) - -(defun sly-mrepl-previous-prompt () - "Go to the beginning of the previous REPL prompt." - (interactive) - ;; This has two wrinkles around the first prompt: (1) when going to - ;; the first prompt it leaves point at column 0 (1) when called from - ;; frist prompt goes to beginning of buffer. The correct fix is to - ;; patch comint.el's comint-next-prompt and comint-previous-prompt - ;; anyway... - (let* ((inhibit-field-text-motion t) - (pos (previous-single-char-property-change (1- (line-beginning-position)) - 'sly-mrepl--prompt))) - (goto-char pos) - (goto-char (line-beginning-position))) - (end-of-line)) - - -;;; "External" non-interactive functions for plugging into -;;; other parts of SLY -;;; -(defun sly-inspector-copy-part-to-repl (number) - "Evaluate the inspector slot at point via the REPL (to set `*')." - (sly-mrepl--save-and-copy-for-repl - ;; FIXME: Using SLYNK:EVAL-FOR-INSPECTOR here repeats logic from - ;; sly.el's `sly-eval-for-inspector', but we can't use that here - ;; because we're already using `sly-mrepl--save-and-copy-for-repl'. - ;; Investigate if these functions could maybe be macros instead. - `(slynk:eval-for-inspector - ,sly--this-inspector-name - nil - 'slynk:inspector-nth-part-or-lose - ,number) - :before (format "Returning inspector slot %s" number))) - -(defun sly-db-copy-part-to-repl (frame-id var-id) - "Evaluate the frame var at point via the REPL (to set `*')." - (sly-mrepl--save-and-copy-for-repl - `(slynk-backend:frame-var-value ,frame-id ,var-id) - :repl (sly-mrepl--find-buffer (sly-current-connection) sly-current-thread) - :before (format "Returning var %s of frame %s" var-id frame-id))) - -(defun sly-apropos-copy-symbol-to-repl (name _type) - (sly-mrepl--save-and-copy-for-repl - `(common-lisp:identity ',(car (read-from-string name))) - :before (format "Returning symbol %s" name))) - -(defun sly-trace-dialog-copy-part-to-repl (id part-id type) - "Eval the Trace Dialog entry under point in the REPL (to set *)" - (sly-mrepl--save-and-copy-for-repl - `(slynk-trace-dialog:trace-part-or-lose ,id ,part-id ,type) - :before (format "Returning part %s (%s) of trace entry %s" part-id type id))) - -(defun sly-db-copy-call-to-repl (frame-id spec) - (sly-mrepl--save-and-copy-for-repl - `(slynk-backend:frame-arguments ,frame-id) - :before (format "The actual arguments passed to frame %s" frame-id) - :after #'(lambda (objects) - (sly-mrepl--insert-call spec objects)))) - -(defun sly-trace-dialog-copy-call-to-repl (trace-id spec) - (sly-mrepl--save-and-copy-for-repl - `(slynk-trace-dialog:trace-arguments-or-lose ,trace-id) - :before (format "The actual arguments passed to trace %s" trace-id) - :after #'(lambda (objects) - (sly-mrepl--insert-call spec objects)))) - -(defun sly-mrepl-inside-string-or-comment-p () - (let ((mark (and (process-live-p (sly-mrepl--process)) - (sly-mrepl--mark)))) - (when (and mark (> (point) mark)) - (let ((ppss (parse-partial-sexp mark (point)))) - (or (nth 3 ppss) (nth 4 ppss)))))) - - -;;; The comma shortcut -;;; -(defvar sly-mrepl-shortcut-history nil "History for sly-mrepl-shortcut.") - -(defun sly-mrepl-reset-shortcut (key-sequence) - "Set `sly-mrepl-shortcut' and reset REPL keymap accordingly." - (interactive "kNew shortcut key sequence? ") - (when (boundp 'sly-mrepl-shortcut) - (define-key sly-mrepl-mode-map sly-mrepl-shortcut nil)) - (set-default 'sly-mrepl-shortcut key-sequence) - (define-key sly-mrepl-mode-map key-sequence - '(menu-item "" sly-mrepl-shortcut - :filter (lambda (cmd) - (if (and (eq major-mode 'sly-mrepl-mode) - (sly-mrepl--shortcut-location-p)) - cmd))))) - -(defcustom sly-mrepl-shortcut (kbd ",") - "Keybinding string used for the REPL shortcut commands. -When setting this variable outside of the Customize interface, -`sly-mrepl-reset-shortcut' must be used." - :group 'sly - :type 'key-sequence - :set (lambda (_sym value) - (sly-mrepl-reset-shortcut value))) - -(defun sly-mrepl--shortcut-location-p () - (or (< (point) (sly-mrepl--mark)) - (and (not (let ((state (syntax-ppss))) - (or (nth 3 state) (nth 4 state)))) - (or (not (equal sly-mrepl-shortcut ",")) - (not (save-excursion - (search-backward "`" (sly-mrepl--mark) 'noerror))))))) - -(defvar sly-mrepl-shortcut-alist - ;; keep this alist ordered by the key value, in order to make it easier to see - ;; the identifying prefixes and keep them short - '(("cd" . sly-mrepl-set-directory) - ("clear repl" . sly-mrepl-clear-repl) - ("disconnect" . sly-disconnect) - ("disconnect all" . sly-disconnect-all) - ("in-package" . sly-mrepl-set-package) - ("restart lisp" . sly-restart-inferior-lisp) - ("quit lisp" . sly-quit-lisp) - ("sayoonara" . sly-quit-lisp) - ("set directory" . sly-mrepl-set-directory) - ("set package" . sly-mrepl-set-package))) - - -(defun sly-mrepl-set-package () - (interactive) - (let ((package (sly-read-package-name "New package: "))) - (sly-mrepl--eval-for-repl `(slynk-mrepl:guess-and-set-package ,package)))) - -(defun sly-mrepl-set-directory () - (interactive) - (let ((dir (read-directory-name "New directory: " - default-directory nil t))) - ;; repeats logic in `sly-cd'. - (sly-mrepl--eval-for-repl - `(slynk:set-default-directory - (slynk-backend:filename-to-pathname - ,(sly-to-lisp-filename dir)))) - (sly-mrepl--insert-note (format "Setting directory to %s" dir)) - (cd dir))) - -(advice-add - 'sly-cd :around - (lambda (oldfun r) - (interactive (lambda (oldspec) - (if (or (not (eq major-mode 'sly-mrepl-mode)) - (sly-y-or-n-p - (substitute-command-keys - "This won't set the REPL's directory (use \ - \\[sly-mrepl-set-directory] for that). Proceed?"))) - (list (advice-eval-interactive-spec oldspec)) - (keyboard-quit)))) - (apply oldfun r)) - '((name . sly-mrepl--be-aware-of-sly-cd))) - -(defun sly-mrepl-shortcut () - (interactive) - (let* ((string (completing-read "Command: " - (mapcar #'car sly-mrepl-shortcut-alist) - nil 'require-match nil - 'sly-mrepl-shortcut-history - (car sly-mrepl-shortcut-history))) - (command (and string - (cdr (assoc string sly-mrepl-shortcut-alist))))) - (call-interactively command))) - - -;;; Backreference highlighting -;;; -(defvar sly-mrepl--backreference-overlays nil - "List of overlays on top of REPL result buttons.") -(make-variable-buffer-local 'sly-mrepl--backreference-overlays) - -(defun sly-mrepl-highlight-results (&optional entry-idx value-idx) - "Highlight REPL results for ENTRY-IDX and VALUE-IDX. -If VALUE-IDX is nil or `all', highlight all results for entry -ENTRY-IDX. If ENTRY-IDX is nil, highlight all results. Returns -a list of result buttons thus highlighted" - (interactive) - (cl-loop - for button in (sly-button-buttons-in (point-min) (point-max)) - for e-idx = (car (button-get button 'part-args)) - for v-idx = (cadr (button-get button 'part-args)) - when (and (button-type-subtype-p (button-type button) 'sly-mrepl-part) - (eq (button-get button 'sly-connection) (sly-current-connection)) - (not (button-get button 'sly-mrepl--highlight-overlay)) - (and (or (not entry-idx) - (= e-idx entry-idx)) - (or (not value-idx) - (eq value-idx 'all) - (= v-idx value-idx)))) - collect button and - do (let ((overlay (make-overlay (button-start button) (button-end button)))) - (push overlay sly-mrepl--backreference-overlays) - (overlay-put overlay 'before-string - (concat - (propertize - (format "%s:%s" - (car (button-get button 'part-args)) - (cadr (button-get button 'part-args))) - 'face 'highlight) - " "))))) - -(defun sly-mrepl-unhighlight-results () - "Unhighlight all repl results" - (interactive) - (mapc #'delete-overlay sly-mrepl--backreference-overlays) - (setq sly-mrepl--backreference-overlays nil)) - -(defvar sly-mrepl--backreference-overlay nil) -(defvar sly-mrepl--backreference-prefix "#v") - -(defun sly-mrepl--highlight-backreferences-maybe () - "Intended to be placed in `post-command-hook'." - (sly-mrepl-unhighlight-results) - (when sly-mrepl--backreference-overlay - (delete-overlay sly-mrepl--backreference-overlay)) - (let* ((match (save-excursion - (sly-beginning-of-symbol) - (looking-at - (format "%s\\([[:digit:]]+\\)?\\(:\\([[:digit:]]+\\)\\|:\\)?" - sly-mrepl--backreference-prefix)))) - (m0 (and match (match-string 0))) - (m1 (and m0 (match-string 1))) - (m2 (and m1 (match-string 2))) - (m3 (and m2 (match-string 3))) - (entry-idx (and m1 (string-to-number m1))) - (value-idx (and match - (or (and m3 (string-to-number m3)) - (and (not m2) - 'all))))) - (if (null match) - (set (make-local-variable 'sly-autodoc-preamble) nil) - (let ((buttons (sly-mrepl-highlight-results entry-idx value-idx)) - (overlay - (or sly-mrepl--backreference-overlay - (set (make-local-variable 'sly-mrepl--backreference-overlay) - (make-overlay 0 0)))) - (message-log-max nil) - (message-text)) - (move-overlay sly-mrepl--backreference-overlay - (match-beginning 0) (match-end 0)) - (cond - ((null buttons) - (overlay-put overlay 'face 'font-lock-warning-face) - (setq message-text (format "No history references for backreference `%s'" m0))) - ((and buttons - entry-idx - value-idx) - (overlay-put overlay 'face 'sly-action-face) - (let* ((prefix (if (numberp value-idx) - (format "Matched history value %s of entry %s: " - value-idx - entry-idx) - (format "Matched history entry %s%s: " - entry-idx - (if (cl-rest buttons) - (format " (%s values)" (length buttons)) - "")))) - (hint (propertize - (truncate-string-to-width - (replace-regexp-in-string "\n" " " - (button-label - (cl-first buttons))) - (- (window-width (minibuffer-window)) - (length prefix) 10) - nil - nil - "...") - 'face - 'sly-action-face))) - (setq message-text (format "%s" (format "%s%s" prefix hint))))) - (buttons - (setq message-text (format "Ambiguous backreference `%s', %s values possible" - m0 (length buttons))) - (overlay-put overlay 'face 'font-lock-warning-face)) - (t - (overlay-put overlay 'face 'font-lock-warning-face) - (setq message-text (format "Invalid backreference `%s'" m0)))) - (sly-message "%s" message-text) - (set (make-local-variable 'sly-autodoc-preamble) message-text))))) - - -;;;; Menu -;;;; -(easy-menu-define sly-mrepl--shortcut-menu nil - "Menu for accessing the mREPL anywhere in sly." - (let* ((C '(sly-connected-p))) - `("mREPL" - ["Go to default REPL" sly-mrepl ,C] - ["New REPL" sly-mrepl-new ,C] - ["Sync Package & Directory" sly-mrepl-sync - (and sly-editing-mode ,C)]))) - -(easy-menu-add-item sly-menu nil sly-mrepl--shortcut-menu "Documentation") - -(easy-menu-define sly-mrepl--menu sly-mrepl-mode-map - "Menu for SLY's MREPL" - (let* ((C '(sly-connected-p))) - `("SLY-mREPL" - [ " Complete symbol at point " sly-mrepl-indent-and-complete-symbol ,C ] - [ " Interrupt " sly-interrupt ,C ] - [ " Isearch history backward " isearch-backward ,C] - "----" - [ " Clear REPL" sly-mrepl-clear-repl ,C ] - [ " Clear last output" sly-mrepl-clear-recent-output ,C ]))) - - -(defvar sly-mrepl--debug-overlays nil) - -(defun sly-mrepl--debug (&rest ignored) - (interactive) - (mapc #'delete-overlay sly-mrepl--debug-overlays) - (let ((overlay (make-overlay sly-mrepl--output-mark - (sly-mrepl--mark))) - (color (if (< sly-mrepl--output-mark (sly-mrepl--mark)) - "green" - "orange")) - (marker-color (if (= sly-mrepl--output-mark (sly-mrepl--mark)) - "red" - "purple"))) - (overlay-put overlay - 'face `(:background ,color)) - (overlay-put overlay - 'after-string (propertize "F" 'face - `(:background ,marker-color))) - (push overlay sly-mrepl--debug-overlays))) - -(defun sly-mrepl--turn-on-debug () - (interactive) - (add-hook 'after-change-functions 'sly-mrepl--debug nil 'local) - (add-hook 'post-command-hook 'sly-mrepl--debug nil 'local)) - -(defun sly-mrepl--turn-off-debug () - (interactive) - (remove-hook 'after-change-functions 'sly-mrepl--debug 'local) - (remove-hook 'post-command-hook 'sly-mrepl--debug 'local)) - - -;;; A hack for Emacs Bug#32014 (Sly gh#165) -;;; -(when (version<= "26.1" emacs-version) - (advice-add - #'lisp-indent-line - :around - (lambda (&rest args) - (let ((beg (save-excursion (progn (beginning-of-line) (point))))) - (cl-letf (((symbol-function #'indent-line-to) - (lambda (indent) - (let ((shift-amt (- indent (current-column)))) - (if (zerop shift-amt) - nil - (delete-region beg (point)) - (indent-to indent)))))) - ;; call original - (apply args)))) - '((name . sly-workaround-for-emacs-bug-32014)))) - - -;;; Sylvesters -;;; -(defvar sly-mrepl--sylvesters - (with-temp-buffer - (insert-file-contents-literally - (expand-file-name "sylvesters.txt" - (file-name-directory load-file-name))) - (cl-loop while (< (point) (point-max)) - for start = (point) - do (search-forward "\n\n" nil 'noerror) - collect (buffer-substring-no-properties start (- (point) 2))))) - -(defun sly-mrepl-random-sylvester () - (let* ((sylvester (nth (random (length sly-mrepl--sylvesters)) - sly-mrepl--sylvesters)) - (woe (sly-random-words-of-encouragement)) - (uncommented - (replace-regexp-in-string "@@@@" woe sylvester))) - uncommented)) - -(provide 'sly-mrepl) blob - 3ac8c05b128187744e0f26c9c93e355ea2da0ff5 (mode 644) blob + /dev/null --- elpa/sly-20231213.1030/contrib/sly-package-fu.el +++ /dev/null @@ -1,448 +0,0 @@ -;; -*- lexical-binding: t; -*- -(require 'sly) -(require 'sly-parse "lib/sly-parse") - -(define-sly-contrib sly-package-fu - "Exporting/Unexporting symbols at point." - (:authors "Tobias C. Rittweiler ") - (:license "GPL") - (:slynk-dependencies slynk/package-fu) - (:on-load - (define-key sly-mode-map "\C-cx" 'sly-export-symbol-at-point) - (define-key sly-mode-map "\C-ci" 'sly-import-symbol-at-point)) - (:on-unload - ;; FIXME: To properly support unloading, this contrib should be - ;; made a minor mode with it's own keymap. The minor mode - ;; activation function should be added to the proper sly-* hooks. - ;; - )) - -(defvar sly-package-file-candidates - (mapcar #'file-name-nondirectory - '("package.lisp" "packages.lisp" "pkgdcl.lisp" - "defpackage.lisp"))) - -(defvar sly-export-symbol-representation-function - #'(lambda (n) (format "#:%s" n))) - -(defvar sly-import-symbol-package-transform-function - 'identity - "String transformation used by `sly-import-symbol-at-point'. - -This function is applied to a package name before it is inserted -into the defpackage form. By default, it is `identity' but you -may wish redefine it to do some tranformations, for example, to -replace dots with slashes to conform to a package-inferred ASDF -system-definition style.") - -(defvar sly-export-symbol-representation-auto t - "Determine automatically which style is used for symbols, #: or : -If it's mixed or no symbols are exported so far, -use `sly-export-symbol-representation-function'.") - -(define-obsolete-variable-alias 'sly-export-save-file - 'sly-package-fu-save-file "1.0.0-beta-3") - -(defvar sly-package-fu-save-file nil - "Save the package file after each automatic modification") - -(defvar sly-defpackage-regexp - "^(\\(cl:\\|common-lisp:\\|uiop:\\|\\uiop/package:\\)?\\(defpackage\\|define-package\\)\\>[ \t']*") - -(put 'uiop:define-package 'sly-common-lisp-indent-function '(as defpackage)) - -(defun sly-find-package-definition-rpc (package) - (sly-eval `(slynk:find-definition-for-thing - (slynk::guess-package ,package)))) - -(defun sly-find-package-definition-regexp (package) - (save-excursion - (save-match-data - (goto-char (point-min)) - (cl-block nil - (while (re-search-forward sly-defpackage-regexp nil t) - (when (sly-package-equal package (sly-sexp-at-point)) - (backward-sexp) - (cl-return (make-sly-file-location (buffer-file-name) - (1- (point)))))))))) - -(defun sly-package-equal (designator1 designator2) - ;; First try to be lucky and compare the strings themselves (for the - ;; case when one of the designated packages isn't loaded in the - ;; image.) Then try to do it properly using the inferior Lisp which - ;; will also resolve nicknames for us &c. - (or (cl-equalp (sly-cl-symbol-name designator1) - (sly-cl-symbol-name designator2)) - (sly-eval `(slynk:package= ,designator1 ,designator2)))) - -(defun sly-export-symbol (symbol package) - "Unexport `symbol' from `package' in the Lisp image." - (sly-eval `(slynk:export-symbol-for-emacs ,symbol ,package))) - -(defun sly-unexport-symbol (symbol package) - "Export `symbol' from `package' in the Lisp image." - (sly-eval `(slynk:unexport-symbol-for-emacs ,symbol ,package))) - - -(defun sly-find-possible-package-file (buffer-file-name) - (cl-labels ((file-name-subdirectory (dirname) - (expand-file-name - (concat (file-name-as-directory (sly-to-lisp-filename dirname)) - (file-name-as-directory "..")))) - (try (dirname) - (cl-dolist (package-file-name sly-package-file-candidates) - (let ((f (sly-to-lisp-filename - (concat dirname package-file-name)))) - (when (file-readable-p f) - (cl-return f)))))) - (when buffer-file-name - (let ((buffer-cwd (file-name-directory buffer-file-name))) - (or (try buffer-cwd) - (try (file-name-subdirectory buffer-cwd)) - (try (file-name-subdirectory - (file-name-subdirectory buffer-cwd)))))))) - -(defun sly-goto-package-source-definition (package) - "Tries to find the DEFPACKAGE form of `package'. If found, -places the cursor at the start of the DEFPACKAGE form." - (cl-labels ((try (location) - (when (sly-location-p location) - (sly-move-to-source-location location) - t))) - (or (try (sly-find-package-definition-rpc package)) - (try (sly-find-package-definition-regexp package)) - (try (sly--when-let - (package-file (sly-find-possible-package-file - (buffer-file-name))) - (with-current-buffer (find-file-noselect package-file t) - (sly-find-package-definition-regexp package)))) - (sly-error "Couldn't find source definition of package: %s" package)))) - -(defun sly-at-expression-p (pattern) - (when (ignore-errors - ;; at a list? - (= (point) (progn (down-list 1) - (backward-up-list 1) - (point)))) - (save-excursion - (down-list 1) - (sly-in-expression-p pattern)))) - -(defun sly-goto-next-export-clause () - ;; Assumes we're inside the beginning of a DEFPACKAGE form. - (let ((point)) - (save-excursion - (cl-block nil - (while (ignore-errors (sly-forward-sexp) t) - (skip-chars-forward " \n\t") - (when (sly-at-expression-p '(:export *)) - (setq point (point)) - (cl-return))))) - (if point - (goto-char point) - (error "No next (:export ...) clause found")))) - -(defun sly-search-exports-in-defpackage (symbol-name) - "Look if `symbol-name' is mentioned in one of the :EXPORT clauses." - ;; Assumes we're inside the beginning of a DEFPACKAGE form. - (cl-labels ((target-symbol-p (symbol) - (string-match-p (format "^\\(\\(#:\\)\\|:\\)?%s$" - (regexp-quote symbol-name)) - symbol))) - (save-excursion - (cl-block nil - (while (ignore-errors (sly-goto-next-export-clause) t) - (let ((clause-end (save-excursion (forward-sexp) (point)))) - (save-excursion - (while (search-forward symbol-name clause-end t) - (when (target-symbol-p (sly-symbol-at-point)) - (cl-return (if (sly-inside-string-p) - ;; Include the following " - (1+ (point)) - (point)))))))))))) - - -(defun sly-package-fu--read-symbols () - "Reads sexps as strings from the point to end of sexp. - -For example, in this situation. - - (for bar minor (again 123)) - -this will return (\"bar\" \"minor\" \"(again 123)\")" - (cl-labels ((read-sexp () - (ignore-errors - (forward-comment (point-max)) - (buffer-substring-no-properties - (point) (progn (forward-sexp) (point)))))) - (save-excursion - (cl-loop for sexp = (read-sexp) while sexp collect sexp)))) - -(defun sly-package-fu--normalize-name (name) - (if (string-prefix-p "\"" name) - (read name) - (replace-regexp-in-string "^\\(\\(#:\\)\\|:\\)" - "" name))) - -(defun sly-defpackage-exports () - "Return a list of symbols inside :export clause of a defpackage." - ;; Assumes we're inside the beginning of a DEFPACKAGE form. - (save-excursion - (mapcar #'sly-package-fu--normalize-name - (cl-loop while (ignore-errors (sly-goto-next-export-clause) t) - do (down-list) (forward-sexp) - append (sly-package-fu--read-symbols) - do (up-list) (backward-sexp))))) - -(defun sly-symbol-exported-p (name symbols) - (cl-member name symbols :test 'cl-equalp)) - -(defun sly-frob-defpackage-form (current-package do-what symbols) - "Adds/removes `symbol' from the DEFPACKAGE form of `current-package' -depending on the value of `do-what' which can either be `:export', -or `:unexport'. - -Returns t if the symbol was added/removed. Nil if the symbol was -already exported/unexported." - (save-excursion - (sly-goto-package-source-definition current-package) - (down-list 1) ; enter DEFPACKAGE form - (forward-sexp) ; skip DEFPACKAGE symbol - ;; Don't or will fail if (:export ...) is immediately following - ;; (forward-sexp) ; skip package name - (let ((exported-symbols (sly-defpackage-exports)) - (symbols (if (consp symbols) - symbols - (list symbols))) - (number-of-actions 0)) - (cl-ecase do-what - (:export - (sly-add-export) - (dolist (symbol symbols) - (let ((symbol-name (sly-cl-symbol-name symbol))) - (unless (sly-symbol-exported-p symbol-name exported-symbols) - (cl-incf number-of-actions) - (sly-package-fu--insert-symbol symbol-name))))) - (:unexport - (dolist (symbol symbols) - (let ((symbol-name (sly-cl-symbol-name symbol))) - (when (sly-symbol-exported-p symbol-name exported-symbols) - (sly-remove-export symbol-name) - (cl-incf number-of-actions)))))) - (when sly-package-fu-save-file - (save-buffer)) - (cons number-of-actions - (current-buffer))))) - -(defun sly-add-export () - (let (point) - (save-excursion - (while (ignore-errors (sly-goto-next-export-clause) t) - (setq point (point)))) - (cond (point - (goto-char point) - (down-list) - (sly-end-of-list)) - (t - (sly-end-of-list) - (unless (looking-back "^\\s-*" (line-beginning-position) nil) - (newline-and-indent)) - (insert "(:export ") - (save-excursion (insert ")")))))) - -(defun sly-determine-symbol-style () - ;; Assumes we're inside :export - (save-excursion - (sly-beginning-of-list) - (sly-forward-sexp) - (let ((symbols (sly-package-fu--read-symbols))) - (cond ((null symbols) - sly-export-symbol-representation-function) - ((cl-every (lambda (x) - (string-match "^:" x)) - symbols) - (lambda (n) (format ":%s" n))) - ((cl-every (lambda (x) - (string-match "^#:" x)) - symbols) - (lambda (n) (format "#:%s" n))) - ((cl-every (lambda (x) - (string-prefix-p "\"" x)) - symbols) - (lambda (n) (prin1-to-string (upcase (substring-no-properties n))))) - (t - sly-export-symbol-representation-function))))) - -(defun sly-format-symbol-for-defpackage (symbol-name) - (funcall (if sly-export-symbol-representation-auto - (sly-determine-symbol-style) - sly-export-symbol-representation-function) - symbol-name)) - -(defun sly-package-fu--insert-symbol (symbol-name) - ;; Assumes we're at the inside :export or :import-from form - ;; after the last symbol - (let ((symbol-name (sly-format-symbol-for-defpackage symbol-name))) - (unless (looking-back "^\\s-*" (line-beginning-position) nil) - (newline-and-indent)) - (insert symbol-name) - (when (looking-at "\\s_") (insert " ")))) - -(defun sly-remove-export (symbol-name) - ;; Assumes we're inside the beginning of a DEFPACKAGE form. - (let ((point)) - (while (setq point (sly-search-exports-in-defpackage symbol-name)) - (save-excursion - (goto-char point) - (backward-sexp) - (delete-region (point) point) - (beginning-of-line) - (when (looking-at "^\\s-*$") - (join-line) - (delete-trailing-whitespace (point) (line-end-position))))))) - -(defun sly-export-symbol-at-point () - "Add the symbol at point to the defpackage source definition -belonging to the current buffer-package. With prefix-arg, remove -the symbol again. Additionally performs an EXPORT/UNEXPORT of the -symbol in the Lisp image if possible." - (interactive) - (let* ((symbol (sly-symbol-at-point)) - (package (or (and (string-match "^\\([^:]+\\):.*" symbol) - (match-string 1 symbol)) - (sly-current-package)))) - (unless symbol (error "No symbol at point.")) - (cond (current-prefix-arg - (let* ((attempt (sly-frob-defpackage-form package :unexport symbol)) - (howmany (car attempt)) - (where (buffer-file-name (cdr attempt)))) - (if (cl-plusp howmany) - (sly-message "Symbol `%s' no longer exported from `%s' in %s" - symbol package where) - (sly-message "Symbol `%s' is not exported from `%s' in %s" - symbol package where))) - (sly-unexport-symbol symbol package)) - (t - (let* ((attempt (sly-frob-defpackage-form package :export symbol)) - (howmany (car attempt)) - (where (buffer-file-name (cdr attempt)))) - (if (cl-plusp howmany) - (sly-message "Symbol `%s' now exported from `%s' in %s" - symbol package where) - (sly-message "Symbol `%s' already exported from `%s' in %s" - symbol package where))) - (sly-export-symbol symbol package))))) - -(defun sly-export-class (name) - "Export acessors, constructors, etc. associated with a structure or a class" - (interactive (list (sly-read-from-minibuffer "Export structure named: " - (sly-symbol-at-point)))) - (let* ((package (sly-current-package)) - (symbols (sly-eval `(slynk:export-structure ,name ,package)))) - (sly-message "%s symbols exported from `%s'" - (car (sly-frob-defpackage-form package :export symbols)) - package))) - -(defalias 'sly-export-structure 'sly-export-class) - -;; -;; Dealing with import-from -;; - -(defun sly-package-fu--search-import-from (package) - (let* ((normalized-package (sly-package-fu--normalize-name package)) - (regexp (format "(:import-from[ \t']*\\(:\\|#:\\)?%s" - (regexp-quote normalized-package)))) - (re-search-forward regexp nil t))) - - -(defun sly-package-fu--create-new-import-from (package symbol) - "Add new :IMPORT-FROM subform for PACKAGE. Add SYMBOL. -Assumes point just before start of DEFPACKAGE form" - (forward-sexp) - ;; Now, search last :import-from or :use form - (cond - ((or (re-search-backward "(:\\(use\\|import-from\\)" nil t) - (and (re-search-backward "def[[:alnum:]]*package" nil t) - (progn (forward-sexp) t))) - ;; Skip found expression - (forward-sexp) - ;; and insert a new (:import-from ) form. - (newline-and-indent) - (let ((symbol-name (sly-format-symbol-for-defpackage symbol)) - (package-name (sly-format-symbol-for-defpackage package))) - (insert "(:import-from )") - (backward-char) - (insert package-name) - (newline-and-indent) - (insert symbol-name))) - (t (error "Can't find suitable place for :import-from defpackage form.")))) - - -(defun sly-package-fu--add-or-update-import-from-form (symbol) - "Do the heavy-lifting for `sly-import-symbol-at-point'. - -Accept a string or a symbol like \"alexandria:with-gensyms\", -and add it to existing (import-from #:alexandria ...) form, or -create a new one. Return name of the given symbol inside of its -package. For example above, return \"with-gensyms\"." - (let* ((package (or (funcall sly-import-symbol-package-transform-function - (sly-cl-symbol-package symbol)) - ;; We only process symbols in fully qualified form like - ;; weblocks/request:get-parameter - (user-error "`%s' is not a package-qualified symbol." - symbol))) - (simple-symbol (sly-cl-symbol-name symbol))) - (save-excursion - ;; First go to just before relevant DEFPACKAGE form - ;; - (sly-goto-package-source-definition (sly-current-package)) - - ;; Ask CL to actually import the symbol (a synchronized eval - ;; makes sure an error aborts the rest of the command) - ;; - (sly-eval `(slynk:import-symbol-for-emacs ,symbol - ,(sly-current-package) - ,package)) - (if (sly-package-fu--search-import-from package) - ;; If specific (:IMPORT-FROM PACKAGE... ) subform exists, - ;; attempt to insert package-less SYMBOL there. - (let ((imported-symbols (mapcar #'sly-package-fu--normalize-name - (sly-package-fu--read-symbols)))) - (unless (cl-member simple-symbol - imported-symbols - :test 'cl-equalp) - (sly-package-fu--insert-symbol simple-symbol) - (when sly-package-fu-save-file (save-buffer)))) - ;; Else, point is unmoved. Add a new (:IMPORT-FROM PACKAGE) - ;; subform after any other existing :IMPORT-FROM or :USE - ;; subforms. - (sly-package-fu--create-new-import-from package - simple-symbol) - (when sly-package-fu-save-file (save-buffer))) - ;; Always return symbol-without-package, because it is useful - ;; to replace symbol at point and change it from fully qualified - ;; form to a simple-form - simple-symbol))) - - -(defun sly-import-symbol-at-point () - "Add a qualified symbol to package's :import-from subclause. - -Takes a package-qualified symbol at point, adds it to the current -package's defpackage form (under its :import-form subclause) and -replaces with a symbol name without the package designator." - (interactive) - (let* ((bounds (sly-bounds-of-symbol-at-point)) - (beg (set-marker (make-marker) (car bounds))) - (end (set-marker (make-marker) (cdr bounds)))) - (when bounds - (let ((non-qualified-name - (sly-package-fu--add-or-update-import-from-form - (buffer-substring-no-properties beg end)))) - (when non-qualified-name - (delete-region beg end) - (insert non-qualified-name)))))) - - -(provide 'sly-package-fu) blob - 9b0a98ff5f520891af948804cb750437f70c687e (mode 644) blob + /dev/null --- elpa/sly-20231213.1030/contrib/sly-profiler.el +++ /dev/null @@ -1,155 +0,0 @@ -;;; -*- coding: utf-8; lexical-binding: t -*- -;;; -;;; sly-profiler.el -- a navigable dialog of inspectable timing entries -;;; -(eval-and-compile - (require 'sly) - (require 'sly-parse "lib/sly-parse")) - -(define-sly-contrib sly-profiler - "Provide an interfactive timing dialog buffer for managing and -inspecting details of timing functions. Invoke this dialog with C-c Y." - (:authors "João Távora ") - (:license "GPL") - (:slynk-dependencies slynk/profiler) - (:on-load (add-hook 'sly-mode-hook 'sly-profiler-enable)) - (:on-unload (remove-hook 'sly-mode-hook 'sly-profiler-enable))) - - -;;;; Modes and mode maps -;;; -(defvar sly-profiler-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "G") 'sly-profiler-fetch-timings) - (define-key map (kbd "C-k") 'sly-profiler-clear-fetched-timings) - (define-key map (kbd "g") 'sly-profiler-fetch-status) - (define-key map (kbd "q") 'quit-window) - map)) - -(define-derived-mode sly-profiler-mode fundamental-mode - "SLY Timing Dialog" "Mode for controlling SLY's Timing Dialog" - (set-syntax-table lisp-mode-syntax-table) - (read-only-mode 1)) - -(defvar sly-profiler-shortcut-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "C-c Y") 'sly-profiler) - (define-key map (kbd "C-c C-y") 'sly-profiler-toggle-timing) - map)) - -(define-minor-mode sly-profiler-shortcut-mode - "Add keybindings for accessing SLY's Profiler.") - -(defun sly-profiler-enable () (sly-profiler-shortcut-mode 1)) - - -;;;; Helpers -;;; -(defun sly-profiler--get-buffer () - (let* ((name (format "*profiler for %s*" - (sly-connection-name sly-default-connection))) - (existing (get-buffer name))) - (cond ((and existing - (buffer-live-p existing) - (with-current-buffer existing - (memq sly-buffer-connection sly-net-processes))) - existing) - (t - (if existing (kill-buffer existing)) - (with-current-buffer (get-buffer-create name) - (sly-profiler-mode) - (setq sly-buffer-connection sly-default-connection) - (pop-to-buffer (current-buffer))))))) - -(defun sly-profiler--clear-local-tree () - (erase-buffer) - (insert "Cleared timings!")) - -(defun sly-profiler--render-timings (timing-specs) - (let ((inhibit-read-only t)) - (erase-buffer) - (let ((standard-output (current-buffer))) - (cl-loop for spec in timing-specs - do (princ spec) (terpri))))) - -;;;; Interactive functions -;;; -;; (defun sly-profiler-fetch-specs () -;; "Refresh just list of timing specs." -;; (interactive) -;; (sly-eval-async `(slynk-profiler:report-specs) -;; #'sly-profiler--open-specs)) - -(defun sly-profiler-clear-fetched-timings (&optional interactive) - "Clear local and remote timings collected so far" - (interactive "p") - (when (or (not interactive) - (y-or-n-p "Clear all collected and fetched timings?")) - (sly-eval-async - '(slynk-profiler:clear-timing-tree) - #'sly-profiler--clear-local-tree))) - -(defun sly-profiler-fetch-timings () - (interactive) - (sly-eval-async `(slynk-profiler:report-latest-timings) - #'sly-profiler--render-timings)) - -(defun sly-profiler-fetch-status () - (interactive) - (sly-profiler-fetch-timings)) - -(defun sly-profiler-toggle-timing (&optional using-context-p) - "Toggle the dialog-timing of the spec at point. - -When USING-CONTEXT-P, attempt to decipher lambdas. methods and -other complicated function specs." - (interactive "P") - ;; Notice the use of "spec strings" here as opposed to the - ;; proper cons specs we use on the slynk side. - ;; - ;; Notice the conditional use of `sly-trace-query' found in - ;; slynk-fancy-trace.el - ;; - (let* ((spec-string (if using-context-p - (sly-extract-context) - (sly-symbol-at-point))) - (spec-string (read-from-minibuffer "(Un)time: " (format "%s" spec-string)))) - (message "%s" (sly-eval `(slynk-profiler:toggle-timing - (slynk::from-string ,spec-string)))))) - -(defun sly-profiler (&optional refresh) - "Show timing dialog and refresh timing collection status. - -With optional CLEAR-AND-FETCH prefix arg, clear the current tree -and fetch a first batch of timings." - (interactive "P") - (sly-with-popup-buffer ((sly-buffer-name :profiler :connection sly-default-connection) - :mode 'sly-profiler-mode - :select t) - (when refresh (sly-profiler-fetch-timings)))) - - -;;;; Menu -;;; -(easy-menu-define sly-profiler--shortcut-menu nil - "Menu setting traces from anywhere in SLY." - (let* ((in-dialog '(eq major-mode 'sly-profiler-mode)) - (_dialog-live `(and ,in-dialog - (memq sly-buffer-connection sly-net-processes))) - (connected '(sly-connected-p))) - `("Profiling" - ["(Un)Profile definition" sly-profiler-toggle-timing ,connected] - ["Open Profiler Dialog" sly-profiler (and ,connected (not ,in-dialog))]))) - -(easy-menu-add-item sly-menu nil sly-profiler--shortcut-menu "Documentation") - -(defvar sly-profiler--easy-menu - (let ((condition '(memq sly-buffer-connection sly-net-processes))) - `("Timing" - [ "Clear fetched timings" sly-profiler-clear-fetched-timings ,condition] - [ "Fetch timings" sly-profiler-fetch-timings ,condition]))) - -(easy-menu-define my-menu sly-profiler-mode-map "Timing" - sly-profiler--easy-menu) - -(provide 'sly-profiler) blob - 9ddf2378cf848ddb95d8945ee91801284c894cc7 (mode 644) blob + /dev/null --- elpa/sly-20231213.1030/contrib/sly-retro.el +++ /dev/null @@ -1,22 +0,0 @@ -;; -*- lexical-binding: t; -*- -(require 'sly) - -(define-sly-contrib sly-retro - "Enable SLIME to connect to a SLY-started SLYNK" - (:slynk-dependencies slynk/retro) - (:on-load (setq sly-net-send-translator #'sly-retro-slynk-to-swank)) - (:on-unload (setq sly-net-send-translator nil))) - -(defun sly-retro-slynk-to-swank (sexp) - (cond ((and sexp - (symbolp sexp) - (string-match "^slynk\\(.*\\)$" (symbol-name sexp))) - (intern (format "swank%s" (match-string 1 (symbol-name sexp))))) - ((and sexp (listp sexp)) - (cl-loop for (x . rest) on sexp - append (list (sly-retro-slynk-to-swank x)) into foo - finally (return (append foo (sly-retro-slynk-to-swank rest))))) - (t - sexp))) - -(provide 'sly-retro) blob - 4db0ca56846f5a0b62b39a6a2105656c73593809 (mode 644) blob + /dev/null --- elpa/sly-20231213.1030/contrib/sly-scratch.el +++ /dev/null @@ -1,45 +0,0 @@ -;;; sly-scratch.el -*- lexical-binding: t; -*- - -(require 'sly) -(require 'cl-lib) - -(define-sly-contrib sly-scratch - "Imitate Emacs' *scratch* buffer" - (:authors "Helmut Eller ") - (:on-load - (define-key sly-selector-map (kbd "s") 'sly-scratch)) - (:license "GPL")) - - -;;; Code - -(defvar sly-scratch-mode-map - (let ((map (make-sparse-keymap))) - (set-keymap-parent map lisp-mode-map) - (define-key map "\C-j" 'sly-eval-print-last-expression) - map)) - -(defun sly-scratch () - (interactive) - (sly-switch-to-scratch-buffer)) - -(defun sly-switch-to-scratch-buffer () - (set-buffer (sly-scratch-buffer)) - (unless (eq (current-buffer) (window-buffer)) - (pop-to-buffer (current-buffer) t))) - -(defvar sly-scratch-file nil) - -(defun sly-scratch-buffer () - "Return the scratch buffer, create it if necessary." - (or (get-buffer (sly-buffer-name :scratch)) - (with-current-buffer (if sly-scratch-file - (find-file sly-scratch-file) - (get-buffer-create (sly-buffer-name :scratch))) - (rename-buffer (sly-buffer-name :scratch)) - (lisp-mode) - (use-local-map sly-scratch-mode-map) - (sly-mode t) - (current-buffer)))) - -(provide 'sly-scratch) blob - bd58e4e3ec0a9cf0ce78f0dd69db3bad68791989 (mode 644) blob + /dev/null --- elpa/sly-20231213.1030/contrib/sly-stickers.el +++ /dev/null @@ -1,1359 +0,0 @@ -;;; sly-stickers.el --- Live-code annotations for SLY -*- lexical-binding: t; -*- - -;; Copyright (C) 2014 João Távora - -;; Author: João Távora -;; Keywords: convenience, languages, lisp, tools - -;; 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: -;;; -;;; There is much in this library that would merit comment. Just some points: -;;; -;;; * Stickers are just overlays that exist on the Emacs side. A lot -;;; of the code is managing overlay nesting levels so that faces -;;; are chosen suitably for making sticker inside stickers -;;; visually recognizable. -;;; -;;; The main entry-point here is the interactive command -;;; `sly-sticker-dwim', which places and removes stickers. -;;; -;;; Stickers are also indexed by an integer and placed in a -;;; connection-global hash-table, `sly-stickers--stickers'. It can -;;; be connection-global because the same sticker with the same id -;;; might eventually be sent, multiple times, to many -;;; connections. It's the Slynk side that has to be able to tell -;;; whence the stickers comes from (this is not done currently). -;;; -;;; * The gist of stickers is instrumenting top-level forms. This is -;;; done by hooking onto `sly-compile-region-function'. Two separate -;;; compilations are performed: one for the uninstrumented form and -;;; another for the intrumented form. This is so that warnings and -;;; compilations errors that are due to stickers exclusively can be -;;; sorted out. If the second compilation fails, the stickers dont -;;; "stick", i.e. they are not armed. -;;; -;;; * File compilation is also hooked onto via -;;; `sly-compilation-finished-hook'. The idea here is to first -;;; compile the whole file, then traverse any top-level forms that -;;; contain stickers and instrument those. -;;; -;;; * On the emacs-side, the sticker overlays are very ephemeral -;;; objects. They are not persistently saved in any way. Deleting or -;;; modifying text inside them automatically deletes them. -;;; -;;; The slynk side eventually must be told to let go of deleted -;;; stickers. Before this happens these stickers are known as -;;; zombies. Reaping happens on almost every SLY -> Slynk call. -;;; Killing the buffer they live in doesn't automatically delete -;;; them, but reaping eventually happens anyway via -;;; `sly-stickers--sticker-by-id'. -;;; -;;; Before a zombie sticker is reaped, some code may still be -;;; running that adds recordings to these stickers, and some of -;;; these recordings make it to the Emacs side. The user can ignore -;;; them in `sly-stickers-replay', being notified that a deleted -;;; sticker is being referenced. -;;; -;;; This need to communicate dead stickers to Slynk is only here -;;; because using weak-hash-tables is impractical for stickers -;;; indexed by integers. Perhaps this could be fixed if the -;;; instrumented forms could reference sticker objects directly. -;;; -;;; * To see the results of sticker-instrumented code, there are the -;;; interactive commands `sly-stickers-replay' and -;;; `sly-stickers-fetch'. If "breaking stickers" is enabled, the -;;; debugger is also invoked before a sticker is reached and after a -;;; sticker returns (if it returns). Auxiliary data-structures like -;;; `sly-stickers--recording' are used here. -;;; -;;; * `sly-stickers--replay-state' and `sly-stickers--replay-map' are -;;; great big hacks just for handling the `sly-stickers-replay' -;;; interactive loop. Should look into recursive minibuffers or -;;; something more akin to `ediff', for example. -;;; -;;; Code: - - -(require 'sly) -(require 'sly-parse "lib/sly-parse") -(require 'sly-buttons "lib/sly-buttons") - -(eval-when-compile - (when (version< emacs-version "26") - ;; Using `cl-defstruct' needs `cl' on older Emacsen. See issue - ;; https://github.com/joaotavora/sly/issues/54 - (require 'cl))) - -(require 'cl-lib) -(require 'hi-lock) ; for the faces -(require 'color) -(require 'pulse) ; pulse-momentary-highlight-overlay - -(define-sly-contrib sly-stickers - "Mark expressions in source buffers and annotate return values." - (:authors "João Távora ") - (:license "GPL") - (:slynk-dependencies slynk/stickers) - (:on-load (add-hook 'sly-editing-mode-hook 'sly-stickers-mode) - (add-hook 'sly-mode-hook 'sly-stickers-shortcut-mode) - (setq sly-compile-region-function - 'sly-stickers-compile-region-aware-of-stickers) - (add-hook 'sly-compilation-finished-hook - 'sly-stickers-after-buffer-compilation t) - (add-hook 'sly-db-extras-hooks 'sly-stickers--handle-break)) - (:on-unload (remove-hook 'sly-editing-mode-hook 'sly-stickers-mode) - (remove-hook 'sly-mode-hook 'sly-stickers-shortcut-mode) - (setq sly-compile-region-function 'sly-compile-region-as-string) - (remove-hook 'sly-compilation-finished-hook - 'sly-stickers-after-buffer-compilation) - (remove-hook 'sly-db-extras-hooks 'sly-stickers--handle-break))) - - - -;;;; Bookeeping for local stickers -;;;; -(defvar sly-stickers--counter 0) - -(defvar sly-stickers--stickers (make-hash-table)) - -(defvar sly-stickers--zombie-sticker-ids nil - "Sticker ids that might exist in Slynk but no longer in Emacs.") - -(defun sly-stickers--zombies () sly-stickers--zombie-sticker-ids) - -(defun sly-stickers--reset-zombies () (setq sly-stickers--zombie-sticker-ids nil)) - - - -;;;; Sticker display and UI logic -;;;; -(defgroup sly-stickers nil - "Mark expressions in source buffers and annotate return values." - :prefix "sly-stickers-" - :group 'sly) - -(when nil - (cl-loop for sym in '(sly-stickers-placed-face - sly-stickers-armed-face - sly-stickers-empty-face - sly-stickers-recordings-face - sly-stickers-exited-non-locally-face) - do - (put sym 'face-defface-spec nil))) - -(defface sly-stickers-placed-face - '((((background dark)) (:background "light grey" :foreground "black")) - (t (:background "light grey"))) - "Face for sticker just set") - -(defface sly-stickers-armed-face - '((t (:strike-through nil :inherit hi-blue))) - "Face for stickers that have been armed") - -(defface sly-stickers-recordings-face - '((t (:strike-through nil :inherit hi-green))) - "Face for stickers that have new recordings") - -(defface sly-stickers-empty-face - '((t (:strike-through nil :inherit hi-pink))) - "Face for stickers that have no recordings.") - -(defface sly-stickers-exited-non-locally-face - '((t (:strike-through t :inherit sly-stickers-empty-face))) - "Face for stickers that have exited non-locally.") - -(defvar sly-stickers-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "C-c C-s C-s") 'sly-stickers-dwim) - (define-key map (kbd "C-c C-s C-d") 'sly-stickers-clear-defun-stickers) - (define-key map (kbd "C-c C-s C-k") 'sly-stickers-clear-buffer-stickers) - map)) - -(defvar sly-stickers-shortcut-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "C-c C-s S") 'sly-stickers-fetch) - (define-key map (kbd "C-c C-s F") 'sly-stickers-forget) - (define-key map (kbd "C-c C-s C-r") 'sly-stickers-replay) - map)) - -(define-minor-mode sly-stickers-mode - "Mark expression in source buffers and annotate return values.") - -(define-minor-mode sly-stickers-shortcut-mode - "Shortcuts for navigating sticker recordings.") - -(defvar sly-stickers--sticker-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "M-RET") 'sly-mrepl-copy-part-to-repl) - (define-key map [down-mouse-3] 'sly-button-popup-part-menu) - (define-key map [mouse-3] 'sly-button-popup-part-menu) - map)) - -(define-button-type 'sly-stickers-sticker :supertype 'sly-part - 'sly-button-inspect 'sly-stickers--inspect-recording - 'sly-button-echo 'sly-stickers--echo-sticker - 'keymap sly-stickers--sticker-map) - -(defun sly-stickers--set-tooltip (sticker &optional info) - (let* ((help-base (button-get sticker 'sly-stickers--base-help-echo)) - (text (if info - (concat "[sly] Sticker:" info "\n" help-base) - help-base))) - (button-put sticker 'help-echo text) - (button-put sticker 'sly-stickers--info info))) - -(defun sly-stickers--echo-sticker (sticker &rest more) - (cl-assert (null more) "Apparently two stickers at exact same location") - (sly-message (button-get sticker 'sly-stickers--info)) - (sly-button-flash sticker)) - -(defcustom sly-stickers-max-nested-stickers 4 - "The maximum expected level expected of sticker nesting. -If you nest more than this number of stickers inside other -stickers, the overlay face will be very dark, and probably -render the underlying text unreadable." - :type :integer) - -(defvar sly-stickers-color-face-attribute :background - "Color-capable attribute of sticker faces that represents nesting.") - -(gv-define-setter sly-stickers--level (level sticker) - `(prog1 - (setf (sly-button--level ,sticker) ,level) - (when (button-get ,sticker 'sly-stickers--base-face) - (sly-stickers--set-face ,sticker)))) - -(defun sly-stickers--level (sticker) (sly-button--level sticker)) - -(defun sly-stickers--guess-face-color (face) - (face-attribute-specified-or - (face-attribute face sly-stickers-color-face-attribute nil t) - nil)) - -(defun sly-stickers--set-face (sticker &optional face) - (let* ((face (or face - (button-get sticker 'sly-stickers--base-face))) - (guessed-color (sly-stickers--guess-face-color face))) - (button-put sticker 'sly-stickers--base-face face) - (unless guessed-color - (sly-error "sorry, can't guess color for face %s for sticker %s")) - (button-put sticker 'face - `(:inherit ,face - ,sly-stickers-color-face-attribute - ,(color-darken-name - guessed-color - (* 25 - (/ (sly-stickers--level sticker) - sly-stickers-max-nested-stickers - 1.0))))))) - -(defun sly-stickers--stickers-in (beg end) - (sly-button--overlays-in beg end 'sly-stickers--sticker-id)) -(defun sly-stickers--stickers-at (pos) - (sly-button--overlays-at pos 'sly-stickers--sticker-id)) -(defun sly-stickers--stickers-between (beg end) - (sly-button--overlays-between beg end 'sly-stickers--sticker-id)) -(defun sly-stickers--stickers-exactly-at (beg end) - (sly-button--overlays-exactly-at beg end 'sly-stickers--sticker-id)) - - -(defun sly-stickers--sticker (from to) - "Place a new sticker from FROM to TO" - (let* ((intersecting (sly-stickers--stickers-in from to)) - (contained (sly-stickers--stickers-between from to)) - (not-contained (cl-set-difference intersecting contained)) - (containers nil)) - (unless (cl-every #'(lambda (e) - (and (< (button-start e) from) - (> (button-end e) to))) - not-contained) - (sly-error "Cannot place a sticker that partially overlaps other stickers")) - (when (sly-stickers--stickers-exactly-at from to) - (sly-error "There is already a sticker at those very coordinates")) - ;; by now we know that other intersecting, non-contained stickers - ;; are our containers. - ;; - (setq containers not-contained) - (let* ((label "Brand new sticker") - (sticker - ;;; FIXME: We aren't using sly--make-text-button here - ;;; because it doesn't allow overlay button s - (make-button from to :type 'sly-stickers-sticker - 'sly-connection (sly-current-connection) - 'part-args (list -1 nil) - 'part-label label - 'sly-button-search-id (sly-button-next-search-id) - 'modification-hooks '(sly-stickers--sticker-modified) - 'sly-stickers-id (cl-incf sly-stickers--counter) - 'sly-stickers--base-help-echo - "mouse-3: Context menu"))) - ;; choose a suitable level for ourselves and increase the - ;; level of those contained by us - ;; - (setf (sly-stickers--level sticker) - (1+ (cl-reduce #'max containers - :key #'sly-stickers--level - :initial-value -1))) - (mapc (lambda (s) (cl-incf (sly-stickers--level s))) contained) - ;; finally, set face - ;; - (sly-stickers--set-tooltip sticker label) - (sly-stickers--set-face sticker 'sly-stickers-placed-face) - sticker))) - -(defun sly-stickers--sticker-id (sticker) - (button-get sticker 'sly-stickers-id)) - -(defun sly-stickers--arm-sticker (sticker) - (let* ((id (sly-stickers--sticker-id sticker)) - (label (format "Sticker %d is armed" id))) - (button-put sticker 'part-args (list id nil)) - (button-put sticker 'part-label label) - (button-put sticker 'sly-stickers--last-known-recording nil) - (sly-stickers--set-tooltip sticker label) - (sly-stickers--set-face sticker 'sly-stickers-armed-face) - (puthash id sticker sly-stickers--stickers))) - -(defun sly-stickers--disarm-sticker (sticker) - (let* ((id (sly-stickers--sticker-id sticker)) - (label (format "Sticker %d failed to stick" id))) - (button-put sticker 'part-args (list -1 nil)) - (button-put sticker 'part-label label) - (sly-stickers--set-tooltip sticker label) - (sly-stickers--set-face sticker 'sly-stickers-placed-face))) - -(define-button-type 'sly-stickers--recording-part :supertype 'sly-part - 'sly-button-inspect - 'sly-stickers--inspect-recording - ;; 'sly-button-pretty-print - ;; #'(lambda (id) ...) - ;; 'sly-button-describe - ;; #'(lambda (id) ...) - ;; 'sly-button-show-source - ;; #'(lambda (id) ...) - ) - -(defun sly-stickers--recording-part (label sticker-id recording vindex - &rest props) - (apply #'sly--make-text-button - label nil - :type 'sly-stickers--recording-part - 'part-args (list sticker-id recording vindex) - 'part-label "Recorded value" - props)) - -(cl-defun sly-stickers--describe-recording-values (recording &key - (indent 0) - (prefix "=> ")) - (cl-flet ((indent (str) - (concat (make-string indent ? )str)) - (prefix (str) - (concat prefix str))) - (let ((descs (sly-stickers--recording-value-descriptions recording))) - (cond ((sly-stickers--recording-exited-non-locally-p recording) - (indent (propertize "exited non locally" 'face 'sly-action-face))) - ((null descs) - (indent (propertize "no values" 'face 'sly-action-face))) - (t - (cl-loop for (value-desc . rest) on descs - for vindex from 0 - concat - (indent (prefix - (sly-stickers--recording-part - value-desc - (sly-stickers--recording-sticker-id recording) - recording - vindex))) - when rest - concat "\n")))))) - -(defconst sly-stickers--newline "\n" - "Work around bug #63, actually Emacs bug #21839. -\"25.0.50; can't use newlines in defaults in cl functions\"") - -(cl-defun sly-stickers--pretty-describe-recording - (recording &key (separator sly-stickers--newline)) - (let* ((recording-sticker-id (sly-stickers--recording-sticker-id recording)) - (sticker (gethash recording-sticker-id - sly-stickers--stickers)) - (nvalues (length (sly-stickers--recording-value-descriptions recording)))) - (format "%s%s:%s%s" - (if sticker - (format "Sticker %s on line %s of %s" - (sly-stickers--sticker-id sticker) - (with-current-buffer (overlay-buffer sticker) - (line-number-at-pos (overlay-start sticker))) - (overlay-buffer sticker)) - (format "Deleted or unknown sticker %s" - recording-sticker-id)) - (if (cl-plusp nvalues) - (format " returned %s values" nvalues) "") - separator - (sly-stickers--describe-recording-values recording - :indent 2)))) - -(defun sly-stickers--populate-sticker (sticker recording) - (let* ((id (sly-stickers--sticker-id sticker)) - (total (sly-stickers--recording-sticker-total recording))) - (cond ((cl-plusp total) - (button-put sticker 'part-label - (format "Sticker %d has %d recordings" id total)) - (unless (sly-stickers--recording-void-p recording) - (button-put sticker 'sly-stickers--last-known-recording recording) - (button-put sticker 'part-args (list id recording)) - (sly-stickers--set-tooltip - sticker - (format "Newest of %s sticker recordings:\n%s" - total - (sly-stickers--describe-recording-values recording :prefix ""))) - (sly-stickers--set-face - sticker - (if (sly-stickers--recording-exited-non-locally-p recording) - 'sly-stickers-exited-non-locally-face - 'sly-stickers-recordings-face)))) - (t - (let ((last-known-recording - (button-get sticker 'sly-stickers--last-known-recording))) - (button-put sticker 'part-label - (format "Sticker %d has no recordings" id)) - (when last-known-recording - (sly-stickers--set-tooltip - sticker - (format "No new recordings. Last known:\n%s" - (sly-stickers--describe-recording-values - last-known-recording)))) - (sly-stickers--set-tooltip sticker "No new recordings") - (sly-stickers--set-face sticker 'sly-stickers-empty-face)))))) - -(defun sly-stickers--sticker-substickers (sticker) - (let* ((retval - (remove sticker - (sly-stickers--stickers-between (button-start sticker) - (button-end sticker)))) - ;; To verify an important invariant, and warn (don't crash) - ;; - (exactly-at - (sly-stickers--stickers-exactly-at (button-start sticker) - (button-end sticker)))) - (cond - ((remove sticker exactly-at) - (sly-warning "Something's fishy. More than one sticker at same position") - (cl-set-difference retval exactly-at)) - (t - retval)))) - -(defun sly-stickers--briefly-describe-sticker (sticker) - (let ((beg (button-start sticker)) - (end (button-end sticker))) - (if (< (- end beg) 20) - (format "sticker around %s" (buffer-substring-no-properties beg end)) - (cl-labels ((word (point direction) - (apply #'buffer-substring-no-properties - (sort (list - point - (save-excursion (goto-char point) - (forward-word direction) - (point))) - #'<)))) - (format "sticker from \"%s...\" to \"...%s\"" - (word beg 1) - (word end -1)))))) - -(defun sly-stickers--delete (sticker) - "Ensure that sticker is deleted." - ;; Delete the overlay and take care of levels for contained and - ;; containers, but note that a sticker might have no buffer anymore - ;; if that buffer was killed, for example... - ;; - (when (and (overlay-buffer sticker) - (buffer-live-p (overlay-buffer sticker))) - (mapc (lambda (s) (cl-decf (sly-stickers--level s))) - (sly-stickers--sticker-substickers sticker)) - (delete-overlay sticker)) - ;; We also want to deregister it from the hashtable in case it's - ;; there (it's not there if it has never been armed) - ;; - (let ((id (sly-stickers--sticker-id sticker))) - (when (gethash (sly-stickers--sticker-id sticker) - sly-stickers--stickers) - (remhash id sly-stickers--stickers) - (add-to-list 'sly-stickers--zombie-sticker-ids id)))) - -(defun sly-stickers--sticker-modified (sticker _after? beg end - &optional _pre-change-len) - (unless (save-excursion - (goto-char beg) - (skip-chars-forward "\t\n\s") - (>= (point) end)) - (let ((inhibit-modification-hooks t)) - (sly-message "Deleting %s" - (sly-stickers--briefly-describe-sticker sticker)) - (sly-stickers--delete sticker)))) - -(defun sly-stickers-next-sticker (&optional n) - (interactive "p") - (sly-button-search n 'sly-stickers--sticker-id)) - -(defun sly-stickers-prev-sticker (&optional n) - (interactive "p") - (sly-button-search (- n) 'sly-stickers--sticker-id)) - -(put 'sly-stickers-next-sticker 'sly-button-navigation-command t) -(put 'sly-stickers-prev-sticker 'sly-button-navigation-command t) - -(defun sly-stickers-clear-defun-stickers () - "Clear all stickers in the current top-level form." - (interactive) - (let* ((region (sly-region-for-defun-at-point))) - (sly-stickers-clear-region-stickers (car region) (cadr region)))) - -(defun sly-stickers-clear-buffer-stickers () - "Clear all the stickers in the current buffer." - (interactive) - (sly-stickers-clear-region-stickers (point-min) (point-max))) - -(defun sly-stickers-clear-region-stickers (&optional from to) - "Clear all the stickers between FROM and TO." - (interactive "r") - (let* ((from (or from (region-beginning))) - (to (or to (region-end))) - (stickers (sly-stickers--stickers-in from to))) - (cond (stickers - (mapc #'sly-stickers--delete stickers) - (sly-message "%s stickers cleared" (length stickers))) - (t - (sly-message "no stickers to clear"))))) - -(defun sly-stickers-delete-sticker-at-point (&optional point) - "Delete the topmost sticker at point." - (interactive "d") - (let ((stickers (sly-stickers--stickers-at (or point (point))))) - (cond - (stickers - (sly-stickers--delete (car stickers)) - (if (cdr stickers) - (sly-message "Deleted topmost sticker (%d remain at point)" - (length (cdr stickers))) - (sly-message "Deleted sticker %s" - (sly-stickers--briefly-describe-sticker (car stickers))))) - (t - (sly-user-error "No stickers at point"))))) - -(defun sly-stickers-maybe-add-sticker (&optional point) - "Add of remove a sticker at POINT. -If point is currently at a sticker boundary, delete that sticker, -otherwise, add a sticker to the sexp at point." - (interactive "d") - (save-excursion - (goto-char (or point (point))) - (let* ((bounds (sly-bounds-of-sexp-at-point)) - (beg (car bounds)) - (end (cdr bounds)) - (matching (and bounds - (sly-stickers--stickers-exactly-at beg end)))) - (cond - ((not bounds) - (sly-message "Nothing here to place sticker on, apparently")) - (matching - (sly-stickers--delete (car matching)) - (sly-message "Deleted sticker")) - (t - (let ((sticker (sly-stickers--sticker beg end))) - (sly-message "Added %s" - (sly-stickers--briefly-describe-sticker sticker)))))))) - -(defun sly-stickers-dwim (prefix) - "Set or remove stickers at point. -Set a sticker for the current sexp at point, or delete it if it -already exists. - -If the region is active set a sticker in the current region. - -With interactive prefix arg PREFIX always delete stickers. - -- One C-u means delete the current top-level form's stickers. -- Two C-u's means delete the current buffer's stickers" - (interactive "p") - (cond - ((= prefix 4) - (if (region-active-p) - (sly-stickers-clear-region-stickers) - (sly-stickers-clear-defun-stickers))) - ((>= prefix 16) - (sly-stickers-clear-buffer-stickers)) - ((region-active-p) - (sly-stickers--sticker (region-beginning) (region-end)) - (deactivate-mark t)) - ((not (sly-inside-string-or-comment-p)) - (sly-stickers-maybe-add-sticker)) - (t - (sly-message "No point placing stickers in string literals or comments")))) - -(defun sly-stickers--sticker-by-id (sticker-id) - "Return the sticker for STICKER-ID, or return NIL. -Perform some housecleaning tasks for stickers that have been -properly deleted or brutally killed with the buffer they were in." - (let* ((sticker (gethash sticker-id sly-stickers--stickers))) - (cond ((and sticker (overlay-buffer sticker) - (buffer-live-p (overlay-buffer sticker))) - sticker) - (sticker - ;; `sticker-id' references a sticker that hasn't been - ;; deleted but whose overlay can't be found. One reason for - ;; this is that the buffer it existed in was killed. So - ;; delete it now and mark it a zombie. - (sly-stickers--delete sticker) - nil) - (t - ;; The sticker isn't in the `sly-stickers--stickers' hash - ;; table, so it has probably already been marked zombie, - ;; and possibly already deleted. We're probably just seeing - ;; it because recording playback and breaking stickers may - ;; not filtering these out by user option. - ;; - ;; To be on the safe side, add the id to the table anyway, - ;; so it'll get killed on the Slynk side on the next - ;; request. - ;; - (add-to-list 'sly-stickers--zombie-sticker-ids sticker-id) - nil)))) - -(defvar sly-stickers--flashing-sticker nil - "The sticker currently being flashed.") - -(cl-defun sly-stickers--find-and-flash (sticker-id &key (otherwise nil)) - "Find and flash the sticker referenced by STICKER-ID. -otherwise call OTHERWISE with a single argument, a string stating -the reason why the sticker couldn't be found" - (let ((sticker (sly-stickers--sticker-by-id sticker-id))) - (cond (sticker - (let ((buffer (overlay-buffer sticker))) - (when buffer - (with-current-buffer buffer - (let* ((window (display-buffer buffer t))) - (when window - (with-selected-window window - (push-mark nil t) - (goto-char (overlay-start sticker)) - (sly-recenter (point)) - (setq sly-stickers--flashing-sticker sticker) - (pulse-momentary-highlight-overlay sticker 'highlight) - (run-with-timer - 2 nil - (lambda () - (when (eq sly-stickers--flashing-sticker sticker) - (pulse-momentary-highlight-overlay - sticker 'highlight))))))))))) - (otherwise - (funcall otherwise "Can't find sticker (probably deleted!)"))))) - -;; Work around an Emacs bug, probably won't be needed in Emacs 27.1 -(advice-add 'pulse-momentary-unhighlight - :before (lambda (&rest _args) - (let ((o pulse-momentary-overlay)) - (when (and o (overlay-get o 'sly-stickers-id)) - (overlay-put o 'priority nil)))) - '((name . fix-pulse-momentary-unhighlight-bug))) - - -;;;; Recordings -;;;; -(cl-defstruct (sly-stickers--recording - (:constructor sly-stickers--make-recording-1) - (:conc-name sly-stickers--recording-) - (:copier sly-stickers--copy-recording)) - (sticker-id nil) - (sticker-total nil) - (id nil) - (value-descriptions nil) - (exited-non-locally-p nil) - (sly-connection nil)) - -(defun sly-stickers--recording-void-p (recording) - (not (sly-stickers--recording-id recording))) - -(defun sly-stickers--make-recording (description) - "Make a `sly-stickers--recording' from DESCRIPTION. -A DESCRIPTION is how the Lisp side describes a sticker and -usually its most recent recording. If it doesn't, a recording -veryfying `sly-stickers--recording-void-p' is created." - (cl-destructuring-bind (sticker-id sticker-total . recording-description) - description - (let ((recording (sly-stickers--make-recording-1 - :sticker-id sticker-id - :sticker-total sticker-total - :sly-connection (sly-current-connection)))) - (when recording-description - (cl-destructuring-bind (recording-id _recording-ctime - value-descriptions - exited-non-locally-p) - recording-description - (setf - (sly-stickers--recording-id recording) - recording-id - (sly-stickers--recording-value-descriptions recording) - value-descriptions - (sly-stickers--recording-exited-non-locally-p recording) - exited-non-locally-p))) - recording))) - - -;;;; Replaying sticker recordings -;;;; -(defvar sly-stickers--replay-help nil) - -(defvar sly-stickers--replay-mode-map - (let ((map (make-sparse-keymap))) - (cl-flet - ((def - (key binding &optional desc) - (define-key map (kbd key) binding) - (setf - (cl-getf sly-stickers--replay-help binding) - (cons (cons key (car (cl-getf sly-stickers--replay-help binding))) - (or desc - (cdr (cl-getf sly-stickers--replay-help binding))))))) - (def "n" 'sly-stickers-replay-next - "Scan recordings forward") - (def "SPC" 'sly-stickers-replay-next) - (def "N" 'sly-stickers-replay-next-for-sticker - "Scan recordings forward for this sticker") - (def "DEL" 'sly-stickers-replay-prev - "Scan recordings backward") - (def "p" 'sly-stickers-replay-prev) - (def "P" 'sly-stickers-replay-prev-for-sticker - "Scan recordings backward for this sticker") - (def "j" 'sly-stickers-replay-jump - "Jump to a recording") - (def ">" 'sly-stickers-replay-jump-to-end - "Go to last recording") - (def "<" 'sly-stickers-replay-jump-to-beginning - "Go to first recording") - (def "h" 'sly-stickers-replay-toggle-help - "Toggle help") - (def "v" 'sly-stickers-replay-pop-to-current-sticker - "Pop to current sticker") - (def "V" 'sly-stickers-replay-toggle-pop-to-stickers - "Toggle popping to stickers") - (def "q" 'quit-window - "Quit") - (def "x" 'sly-stickers-replay-toggle-ignore-sticker - "Toggle ignoring a sticker") - (def "z" 'sly-stickers-replay-toggle-ignore-zombies - "Toggle ignoring deleted stickers") - (def "R" 'sly-stickers-replay-reset-ignore-list - "Reset ignore list") - (def "F" 'sly-stickers-forget - "Forget about sticker recordings") - (def "g" 'sly-stickers-replay-refresh - "Refresh current recording") - map))) - -(define-derived-mode sly-stickers--replay-mode fundamental-mode - "SLY Stickers Replay" "Mode for controlling sticker replay sessions Dialog" - (set-syntax-table lisp-mode-syntax-table) - (read-only-mode 1) - (sly-mode 1) - (add-hook 'post-command-hook - 'sly-stickers--replay-postch t t)) - -(defun sly-stickers--replay-postch () - (let ((win (get-buffer-window (current-buffer)))) - (when (and win - (window-live-p win)) - (ignore-errors - (set-window-text-height win (line-number-at-pos (point-max))))))) - -(defvar sly-stickers--replay-expanded-help nil) - -(defun sly-stickers-replay-toggle-help () - (interactive) - (set (make-local-variable 'sly-stickers--replay-expanded-help) - (not sly-stickers--replay-expanded-help)) - (sly-stickers--replay-refresh-1)) - -(sly-def-connection-var sly-stickers--replay-data nil - "Data structure for information related to recordings") - -(defvar sly-stickers--replay-key nil - "A symbol identifying a particular replaying session in the - Slynk server.") - -(defvar sly-stickers--replay-pop-to-stickers t) - -(defun sly-stickers--replay-refresh-1 () - "Insert a description of the current recording into the current -buffer" - (cl-assert (eq major-mode 'sly-stickers--replay-mode) - nil - "%s must be run in a stickers replay buffer" - this-command) - (cl-labels - ((paragraph () (if sly-stickers--replay-expanded-help "\n\n" "\n")) - (describe-ignored-stickers - () - (let ((ignored-ids (cl-getf (sly-stickers--replay-data) - :ignored-ids)) - (ignore-zombies-p (cl-getf (sly-stickers--replay-data) - :ignore-zombies-p))) - (if (or ignored-ids ignore-zombies-p) - (format "%s%s%s" - (paragraph) - (if ignore-zombies-p - "Skipping recordings of deleted stickers. " "") - (if ignored-ids - (format "Skipping recordings of sticker%s %s." - (if (cl-rest ignored-ids) "s" "") - (concat (mapconcat #'pp-to-string - (butlast ignored-ids) - ", ") - (and (cl-rest ignored-ids) " and ") - (pp-to-string - (car (last ignored-ids))))) - "")) - ""))) - (describe-help - () - (format "%s%s" - (paragraph) - (if sly-stickers--replay-expanded-help - (substitute-command-keys "\\{sly-stickers--replay-mode-map}") - "n => next, p => previous, x => ignore, h => help, q => quit"))) - (describe-number-of-recordings - (new-total) - (let* ((old-total (cl-getf (sly-stickers--replay-data) :old-total)) - (diff (and old-total (- new-total old-total)))) - (format "%s total recordings%s" - new-total - (cond ((and diff - (cl-plusp diff)) - (propertize (format ", %s new in the meantime" - diff) - 'face 'bold)) - (t - ""))))) - (describe-playhead - (recording) - (let ((new-total (cl-getf (sly-stickers--replay-data) :total)) - (index (cl-getf (sly-stickers--replay-data) :index))) - (cond - ((and new-total - recording) - (format "Playhead at recording %s of %s" - (ignore-errors (1+ index)) - (describe-number-of-recordings new-total))) - (new-total - (format "Playhead detached (ignoring too many stickers?) on %s" - (describe-number-of-recordings new-total))) - (recording - (substitute-command-keys - "Playhead confused (perhaps hit \\[sly-stickers-replay-refresh])")) - (t - (format - "No recordings! Perhaps you need to run some sticker-aware code first")))))) - (sly-refreshing () - (let ((rec (cl-getf (sly-stickers--replay-data) :recording))) - (insert (describe-playhead rec) (paragraph)) - (when rec - (insert (sly-stickers--pretty-describe-recording - rec - :separator (paragraph))))) - (insert (describe-ignored-stickers)) - (insert (describe-help))))) - -(defun sly-stickers-replay () - "Start interactive replaying of known sticker recordings." - (interactive) - (let* ((buffer-name (sly-buffer-name :stickers-replay - :connection (sly-current-connection))) - (existing-buffer (get-buffer buffer-name))) - (let ((split-width-threshold nil) - (split-height-threshold 0)) - (sly-with-popup-buffer (buffer-name - :mode 'sly-stickers--replay-mode - :select t) - (setq existing-buffer standard-output))) - (with-current-buffer existing-buffer - (setf (cl-getf (sly-stickers--replay-data) :replay-key) - (cl-gensym "stickers-replay-")) - (let ((old-total (cl-getf (sly-stickers--replay-data) :total)) - (new-total (sly-eval '(slynk-stickers:total-recordings)))) - (setf (cl-getf (sly-stickers--replay-data) :old-total) old-total) - (when (and - old-total - (cl-plusp old-total) - (> new-total old-total) - (sly-y-or-n-p - "Looks like there are %s new recordings since last replay.\n -Forget about old ones before continuing?" (- new-total old-total))) - (sly-stickers-forget old-total))) - - (sly-stickers-replay-refresh 0 - (if existing-buffer nil t) - t) - (set-window-dedicated-p nil 'soft) - (with-current-buffer existing-buffer - (sly-stickers--replay-postch))))) - -(defun sly-stickers-replay-refresh (n command &optional interactive) - "Refresh the current sticker replay session. -N and COMMAND are passed to the Slynk server and instruct what -recording to fetch: - -If COMMAND is nil, navigate to Nth next sticker recording, -skipping ignored stickers. - -If COMMAND is a number, navigate to the Nth next sticker -recording for the sticker with that numeric sticker id. - -If COMMAND is any other value, jump directly to the recording -index N. - -Interactively, N is 0 and and COMMAND is nil, meaning that the -playhead should stay put and the buffer should be refreshed. - -Non-interactively signal an error if no recording was fetched and -INTERACTIVE is the symbol `sly-error'. - -Non-interactively, set the `:recording' slot of -`sly-stickers--replay-data' to nil if no recording was fetched." - (interactive (list 0 nil t)) - (let ((result (sly-eval - `(slynk-stickers:search-for-recording - ',(cl-getf (sly-stickers--replay-data) :replay-key) - ',(cl-getf (sly-stickers--replay-data) :ignored-ids) - ',(cl-getf (sly-stickers--replay-data) :ignore-zombies-p) - ',(sly-stickers--zombies) - ,n - ',command)))) - ;; presumably, Slynk cleaned up the zombies we passed it. - ;; - (sly-stickers--reset-zombies) - (cond ((car result) - (cl-destructuring-bind (total index &rest sticker-description) - result - (let ((rec (sly-stickers--make-recording sticker-description)) - (old-index (cl-getf (sly-stickers--replay-data) :index))) - (setf (cl-getf (sly-stickers--replay-data) :index) index - (cl-getf (sly-stickers--replay-data) :total) total - (cl-getf (sly-stickers--replay-data) :recording) rec) - (if old-index - (if (cl-plusp n) - (if (> old-index index) (sly-message "Rolled over to start")) - (if (< old-index index) (sly-message "Rolled over to end")))) - ;; Assert that the recording isn't void - ;; - (when (sly-stickers--recording-void-p rec) - (sly-error "Attempt to visit a void recording described by %s" - sticker-description)) - (when sly-stickers--replay-pop-to-stickers - (sly-stickers--find-and-flash - (sly-stickers--recording-sticker-id rec)))))) - (interactive - ;; If we were called interactively and got an error, it's - ;; probably because there aren't any recordings, so reset - ;; data - ;; - (setf (sly-stickers--replay-data) nil) - (when (eq interactive 'sly-error) - (sly-error "%s for %s reported an error: %s" - 'slynk-stickers:search-for-recording - n - (cadr result))) - (setf (cl-getf (sly-stickers--replay-data) :recording) nil))) - (if interactive - (sly-stickers--replay-refresh-1) - (cl-getf (sly-stickers--replay-data) :recording )))) - -(defun sly-stickers-replay-next (n) - "Navigate to Nth next sticker recording, skipping ignored stickers" - (interactive "p") - (sly-stickers-replay-refresh n nil 'sly-error)) - -(defun sly-stickers-replay-prev (n) - "Navigate to Nth prev sticker recording, skipping ignored stickers" - (interactive "p") - (sly-stickers-replay-refresh (- n) nil 'sly-error)) - -(defun sly-stickers-replay--current-sticker-interactive (prompt) - (if current-prefix-arg - (read-number (format "[sly] %s " prompt)) - (sly-stickers--recording-sticker-id - (cl-getf (sly-stickers--replay-data) :recording)))) - -(defun sly-stickers-replay-next-for-sticker (n sticker-id) - "Navigate to Nth next sticker recording for STICKER-ID" - (interactive (list - (if (numberp current-prefix-arg) - current-prefix-arg - 1) - (sly-stickers-replay--current-sticker-interactive - "Which sticker?"))) - (sly-stickers-replay-refresh n sticker-id 'sly-error)) - -(defun sly-stickers-replay-prev-for-sticker (n sticker-id) - "Navigate to Nth prev sticker recording for STICKER-ID" - (interactive (list - (- (if (numberp current-prefix-arg) - current-prefix-arg - 1)) - (sly-stickers-replay--current-sticker-interactive - "Which sticker?"))) - (sly-stickers-replay-refresh n sticker-id 'sly-error)) - -(defun sly-stickers-replay-jump (n) - "Fetch and jump to Nth sticker recording" - (interactive (read-number "[sly] jump to which recording? ")) - (sly-stickers-replay-refresh n 'absolute-p 'sly-error)) - -(defun sly-stickers-replay-jump-to-beginning () - "Fetch and jump to the first sticker recording" - (interactive) - (sly-stickers-replay-refresh 0 'absolute-p 'sly-error)) - -(defun sly-stickers-replay-jump-to-end () - "Fetch and jump to the last sticker recording" - (interactive) - (sly-stickers-replay-refresh -1 'absolute-p 'sly-error)) - -(defun sly-stickers-replay-toggle-ignore-sticker (sticker-id) - "Toggle ignoring recordings of sticker with STICKER-ID" - (interactive (list - (sly-stickers-replay--current-sticker-interactive - "Toggle ignoring which sticker id?"))) - (let* ((ignored (cl-getf (sly-stickers--replay-data) :ignored-ids)) - (ignored-p (memq sticker-id ignored))) - (cond (ignored-p - (setf (cl-getf (sly-stickers--replay-data) :ignored-ids) - (delq sticker-id (cdr ignored))) - (sly-message "No longer ignoring sticker %s" sticker-id)) - (t - (setf (cl-getf (sly-stickers--replay-data) :ignored-ids) - (delete-dups ; stupid but safe - (cons sticker-id ignored))) - (sly-message "Now ignoring sticker %s" sticker-id))) - (sly-stickers-replay-refresh (if ignored-p ; was ignored, now isn't - 0 - 1) - nil - t))) - -(defun sly-stickers-replay-toggle-ignore-zombies () - "Toggle ignoring recordings of zombie stickers." - (interactive) - (let ((switch - (setf - (cl-getf (sly-stickers--replay-data) :ignore-zombies-p) - (not (cl-getf (sly-stickers--replay-data) :ignore-zombies-p))))) - (if switch - (sly-message "Now ignoring zombie stickers") - (sly-message "No longer ignoring zombie stickers"))) - (sly-stickers-replay-refresh 0 nil t)) - -(defun sly-stickers-replay-pop-to-current-sticker (sticker-id) - "Pop to sticker with STICKER-ID" - (interactive (list - (sly-stickers-replay--current-sticker-interactive - "Pop to which sticker id?"))) - (sly-stickers--find-and-flash sticker-id - :otherwise #'sly-error)) - -(defun sly-stickers-replay-toggle-pop-to-stickers () - "Toggle popping to stickers when replaying sticker recordings." - (interactive) - (set (make-local-variable 'sly-stickers--replay-pop-to-stickers) - (not sly-stickers--replay-pop-to-stickers)) - (if sly-stickers--replay-pop-to-stickers - (sly-message "Auto-popping to stickers ON") - (sly-message "Auto-popping to stickers OFF"))) - -(defun sly-stickers-replay-reset-ignore-list () - "Reset the sticker ignore specs" - (interactive) - (setf (cl-getf (sly-stickers--replay-data) :ignored-ids) nil) - (sly-stickers-replay-refresh 0 nil t)) - -(defun sly-stickers-fetch () - "Fetch recordings from Slynk and update stickers accordingly. -See also `sly-stickers-replay'." - (interactive) - (sly-eval-async `(slynk-stickers:fetch ',(sly-stickers--zombies)) - #'(lambda (result) - (sly-stickers--reset-zombies) - (let ((message - (format "Fetched recordings for %s armed stickers" - (length result)))) - (cl-loop for sticker-description in result - ;; Although we are analysing sticker descriptions - ;; here, recordings are made to pass to - ;; `sly-stickers--sticker-by-id', even if they are - ;; are `sly-stickers--recording-void-p', which is - ;; the case if the sticker has never been - ;; traversed. - ;; - for recording = - (sly-stickers--make-recording sticker-description) - for sticker = - (sly-stickers--sticker-by-id - (sly-stickers--recording-sticker-id recording)) - when sticker - do (sly-stickers--populate-sticker sticker recording)) - (sly-message message))) - "CL_USER")) - -(defun sly-stickers-forget (&optional howmany interactive) - "Forget about sticker recordings in the Slynk side. -If HOWMANY is non-nil it must be a number stating how many -recordings to forget about. In this cases Because 0 is an index, -in the `nth' sense, the HOWMANYth recording survives." - (interactive (list (and (numberp current-prefix-arg) - current-prefix-arg) - t)) - (when (or (not interactive) - (sly-y-or-n-p "Really forget about sticker recordings?")) - (sly-eval `(slynk-stickers:forget ',(sly-stickers--zombies) ,howmany)) - (sly-stickers--reset-zombies) - (setf (cl-getf (sly-stickers--replay-data) :rec) nil - (cl-getf (sly-stickers--replay-data) :old-total) nil) - (when interactive - (sly-message "Forgot all about sticker recordings.")) - (when (eq major-mode 'sly-stickers--replay-mode) - (sly-stickers-replay-refresh 0 t t)))) - - -;;;; Breaking stickers -(defun sly-stickers--handle-break (extra) - (sly-dcase extra - ((:slynk-after-sticker description) - (let ((sticker-id (cl-first description)) - (recording (sly-stickers--make-recording description))) - (sly-stickers--find-and-flash sticker-id - :otherwise 'sly-message) - (insert - "\n\n" - (sly-stickers--pretty-describe-recording recording - )))) - ((:slynk-before-sticker sticker-id) - (sly-stickers--find-and-flash sticker-id - :otherwise 'sly-message)) - (;; don't do anything if we don't know this "extra" info - t - nil))) - - -(defun sly-stickers-toggle-break-on-stickers () - (interactive) - (let ((break-p (sly-eval '(slynk-stickers:toggle-break-on-stickers)))) - (sly-message "Breaking on stickers is %s" (if break-p "ON" "OFF")))) - - -;;;; Functions for examining recordings -;;;; - - -(eval-after-load "sly-mrepl" - `(progn - (button-type-put 'sly-stickers-sticker - 'sly-mrepl-copy-part-to-repl - 'sly-stickers--copy-recording-to-repl) - (button-type-put 'sly-stickers--recording-part - 'sly-mrepl-copy-part-to-repl - 'sly-stickers--copy-recording-to-repl))) - - -;;; shoosh byte-compiler -(declare-function sly-mrepl--save-and-copy-for-repl nil) -(cl-defun sly-stickers--copy-recording-to-repl - (_sticker-id recording &optional (vindex 0)) - (check-recording recording) - (sly-mrepl--save-and-copy-for-repl - `(slynk-stickers:find-recording-or-lose - ,(sly-stickers--recording-id recording) - ,vindex) - :before (format "Returning values of recording %s of sticker %s" - (sly-stickers--recording-id recording) - (sly-stickers--recording-sticker-id recording)))) - -(defun check-recording (recording) - (cond ((null recording) - (sly-error "This sticker doesn't seem to have any recordings")) - ((not (eq (sly-stickers--recording-sly-connection recording) - (sly-current-connection))) - (sly-error "Recording is for a different connection (%s)" - (sly-connection-name - (sly-stickers--recording-sly-connection recording)))))) - -(cl-defun sly-stickers--inspect-recording - (_sticker-id recording &optional (vindex 0)) - (check-recording recording) - (sly-eval-for-inspector - `(slynk-stickers:inspect-sticker-recording - ,(sly-stickers--recording-id recording) - ,vindex))) - -;;;; Sticker-aware compilation -;;;; - -(cl-defun sly-stickers--compile-region-aware-of-stickers-1 - (start end callback &key sync fallback flash) - "Compile from START to END considering stickers. -After compilation call CALLBACK with the stickers and the -compilation result. If SYNC, use `sly-eval' other wise use -`sly-eval-async'. If FALLBACK, send the uninstrumneted region as -a fallback. If FLASH, flash the compiled region." - (let* ((uninstrumented (buffer-substring-no-properties start end)) - (stickers (sly-stickers--stickers-between start end)) - (original-buffer (current-buffer))) - (cond (stickers - (when flash - (sly-flash-region start end :face 'sly-stickers-armed-face)) - (sly-with-popup-buffer ((sly-buffer-name :stickers :hidden t) - :select :hidden) - (mapc #'delete-overlay (overlays-in (point-min) (point-max))) - (insert uninstrumented) - ;; Use a second set of overlays placed just in the - ;; pre-compilation buffer. We need this to correctly keep - ;; track of the markers because in this buffer we are going - ;; to change actual text - ;; - (cl-loop for sticker in stickers - for overlay = - (make-overlay (- (button-start sticker) (1- start)) - (- (button-end sticker) (1- start))) - do (overlay-put overlay 'sly-stickers--sticker sticker)) - (cl-loop for overlay in (overlays-in (point-min) (point-max)) - for sticker = (overlay-get overlay 'sly-stickers--sticker) - do - (sly-stickers--arm-sticker sticker) - (goto-char (overlay-start overlay)) - (insert (format "(slynk-stickers:record %d " - (sly-stickers--sticker-id sticker))) - (goto-char (overlay-end overlay)) - (insert ")")) - ;; Now send both the instrumented and uninstrumented - ;; string to the Lisp - ;; - (let ((instrumented (buffer-substring-no-properties (point-min) - (point-max))) - (new-ids (mapcar #'sly-stickers--sticker-id stickers))) - (with-current-buffer original-buffer - (let ((form `(slynk-stickers:compile-for-stickers - ',new-ids - ',(sly-stickers--zombies) - ,instrumented - ,(when fallback uninstrumented) - ,(buffer-name) - ',(sly-compilation-position start) - ,(if (buffer-file-name) - (sly-to-lisp-filename (buffer-file-name))) - ',sly-compilation-policy))) - (cond (sync - (funcall callback - stickers - (sly-eval form)) - (sly-stickers--reset-zombies)) - (t (sly-eval-async form - (lambda (result) - (sly-stickers--reset-zombies) - (funcall callback stickers result)))))))))) - (t - (sly-compile-region-as-string start end))))) - -(defun sly-stickers-compile-region-aware-of-stickers (start end) - "Compile region from START to END aware of stickers. -Intended to be placed in `sly-compile-region-function'" - (sly-stickers--compile-region-aware-of-stickers-1 - start end - (lambda (stickers result-and-stuck-p) - (cl-destructuring-bind (result &optional stuck-p) - result-and-stuck-p - (unless stuck-p - (mapc #'sly-stickers--disarm-sticker stickers)) - (sly-compilation-finished - result - nil - (if stuck-p - (format " (%d stickers armed)" (length stickers)) - " (stickers failed to stick)")))) - :fallback t - :flash t)) - -(defun sly-stickers-after-buffer-compilation (success _notes buffer loadp) - "After compilation, compile regions with stickers. -Intented to be placed in `sly-compilation-finished-hook'" - (when (and buffer loadp success) - (save-restriction - (widen) - (let* ((all-stickers (sly-stickers--stickers-between - (point-min) (point-max))) - (regions (cl-loop for sticker in all-stickers - for region = (sly-region-for-defun-at-point - (overlay-start sticker)) - unless (member region regions) - collect region into regions - finally (cl-return regions)))) - (when regions - (cl-loop - with successful - with unsuccessful - for region in regions - do - (sly-stickers--compile-region-aware-of-stickers-1 - (car region) (cadr region) - (lambda (stickers result) - (cond (result - (push (cons region stickers) successful)) - (t - (mapc #'sly-stickers--disarm-sticker stickers) - (push (cons region stickers) unsuccessful)))) - :sync t) - finally - (sly-temp-message - 3 3 - "%s stickers stuck in %s regions, %s disarmed in %s regions" - (cl-reduce #'+ successful :key (lambda (x) (length (cdr x)))) - (length successful) - (cl-reduce #'+ unsuccessful :key (lambda (x) (length (cdr x)))) - (length unsuccessful)))))))) - - -;;;; Menu -;;;; - -(easy-menu-define sly-stickers--shortcut-menu nil - "Placing stickers in `lisp-mode' buffers." - (let* ((in-source-file 'sly-stickers-mode) - (connected '(sly-connected-p))) - `("Stickers" - ["Add or remove sticker at point" - sly-stickers-dwim ,in-source-file] - ["Delete stickers from top-level form" - sly-stickers-clear-defun-stickers ,in-source-file] - ["Delete stickers from buffer" - sly-stickers-clear-buffer-stickers ,in-source-file] - "--" - ["Start sticker recording replay" - sly-stickers-replay ,connected] - ["Fetch most recent recordings" - sly-stickers-fetch ,connected] - ["Toggle breaking on stickers" - sly-stickers-toggle-break-on-stickers ,connected]))) - -(easy-menu-add-item sly-menu nil sly-stickers--shortcut-menu "Documentation") - -(provide 'sly-stickers) -;;; sly-stickers.el ends here - blob - ec36a895cd54e0e73e48926260b8151ee132f903 (mode 644) blob + /dev/null --- elpa/sly-20231213.1030/contrib/sly-trace-dialog.el +++ /dev/null @@ -1,744 +0,0 @@ -;;; -*- coding: utf-8; lexical-binding: t -*- -;;; -;;; sly-trace-dialog.el -- a navigable dialog of inspectable trace entries -;;; -;;; TODO: implement better wrap interface for sbcl method, labels and such -;;; TODO: backtrace printing is very slow -;;; -(require 'sly) -(require 'sly-parse "lib/sly-parse") -(require 'cl-lib) - -(define-sly-contrib sly-trace-dialog - "Provide an interactive trace dialog buffer for managing and -inspecting details of traced functions. Invoke this dialog with C-c T." - (:authors "João Távora ") - (:license "GPL") - (:slynk-dependencies slynk/trace-dialog) - (:on-load (add-hook 'sly-mode-hook 'sly-trace-dialog-shortcut-mode) - (define-key sly-selector-map (kbd "T") 'sly-trace-dialog)) - (:on-unload (remove-hook 'sly-mode-hook 'sly-trace-dialogn-shortcut-mode))) - - -;;;; Variables -;;; -(defvar sly-trace-dialog-flash t - "Non-nil means flash the updated region of the SLY Trace Dialog. ") - -(defvar sly-trace-dialog--specs-overlay nil) - -(defvar sly-trace-dialog--progress-overlay nil) - -(defvar sly-trace-dialog--tree-overlay nil) - -(defvar sly-trace-dialog--collapse-chars (cons "-" "+")) - - -;;;; Local trace entry model -(defvar sly-trace-dialog--traces nil) - -(cl-defstruct (sly-trace-dialog--trace - (:constructor sly-trace-dialog--make-trace)) - id - parent - spec - args - retlist - depth - beg - end - collapse-button-marker - summary-beg - children-end - collapsed-p) - -(defun sly-trace-dialog--find-trace (id) - (gethash id sly-trace-dialog--traces)) - - -;;;; Modes and mode maps -;;; -(defvar sly-trace-dialog-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "G") 'sly-trace-dialog-fetch-traces) - (define-key map (kbd "C-k") 'sly-trace-dialog-clear-fetched-traces) - (define-key map (kbd "g") 'sly-trace-dialog-fetch-status) - - (define-key map (kbd "q") 'quit-window) - - (set-keymap-parent map button-buffer-map) - map)) - -(define-derived-mode sly-trace-dialog-mode fundamental-mode - "SLY Trace Dialog" "Mode for controlling SLY's Trace Dialog" - (set-syntax-table lisp-mode-syntax-table) - (read-only-mode 1) - (sly-mode 1) - (add-to-list (make-local-variable 'sly-trace-dialog-after-toggle-hook) - 'sly-trace-dialog-fetch-status)) - -(defvar sly-trace-dialog-shortcut-mode-map - (let ((map (make-sparse-keymap))) - (define-key map (kbd "C-c T") 'sly-trace-dialog) - (define-key map (kbd "C-c C-t") 'sly-trace-dialog-toggle-trace) - (define-key map (kbd "C-c M-t") - (if (featurep 'sly-fancy-trace) - 'sly-toggle-fancy-trace - 'sly-toggle-trace-fdefinition)) - map)) - -(define-minor-mode sly-trace-dialog-shortcut-mode - "Add keybindings for accessing SLY's Trace Dialog.") - -(easy-menu-define sly-trace-dialog--shortcut-menu nil - "Menu setting traces from anywhere in SLY." - (let* ((in-dialog '(eq major-mode 'sly-trace-dialog-mode)) - (_dialog-live `(and ,in-dialog - (memq sly-buffer-connection sly-net-processes))) - (connected '(sly-connected-p))) - `("Trace" - ["Toggle trace.." sly-trace-dialog-toggle-trace ,connected] - ["Untrace all" sly-trace-dialog-untrace-all ,connected] - ["Trace complex spec" sly-trace-dialog-toggle-complex-trace ,connected] - ["Open Trace dialog" sly-trace-dialog (and ,connected (not ,in-dialog))] - "--" - [ "Regular lisp trace..." sly-toggle-fancy-trace ,connected]))) - -(easy-menu-add-item sly-menu nil sly-trace-dialog--shortcut-menu "Documentation") - -(easy-menu-define sly-trace-dialog--menu sly-trace-dialog-mode-map - "Menu for SLY's Trace Dialog" - (let* ((in-dialog '(eq major-mode 'sly-trace-dialog-mode)) - (dialog-live `(and ,in-dialog - (memq sly-buffer-connection sly-net-processes)))) - `("SLY-Trace" - [ "Refresh traces and progress" sly-trace-dialog-fetch-status - ,dialog-live] - [ "Fetch next batch" sly-trace-dialog-fetch-traces ,dialog-live] - [ "Clear all fetched traces" sly-trace-dialog-clear-fetched-traces - ,dialog-live] - [ "Toggle details" sly-trace-dialog-hide-details-mode ,in-dialog] - [ "Toggle autofollow" sly-trace-dialog-autofollow-mode ,in-dialog]))) - -(define-minor-mode sly-trace-dialog-hide-details-mode - "Hide details in `sly-trace-dialog-mode'" - nil " Brief" - :group 'sly-trace-dialog - (unless (derived-mode-p 'sly-trace-dialog-mode) - (error "Not a SLY Trace Dialog buffer")) - (sly-trace-dialog--set-hide-details-mode)) - -(define-minor-mode sly-trace-dialog-autofollow-mode - "Automatically inspect trace entries from `sly-trace-dialog-mode'" - nil " Autofollow" - :group 'sly-trace-dialog - (unless (derived-mode-p 'sly-trace-dialog-mode) - (error "Not a SLY Trace Dialog buffer"))) - - -;;;; Helper functions -;;; -(defmacro sly-trace-dialog--insert-and-overlay (string overlay) - `(save-restriction - (let ((inhibit-read-only t)) - (narrow-to-region (point) (point)) - (insert ,string "\n") - (set (make-local-variable ',overlay) - (let ((overlay (make-overlay (point-min) - (point-max) - (current-buffer) - nil - t))) - (move-overlay overlay (overlay-start overlay) - (1- (overlay-end overlay))) - overlay))))) - -(defun sly-trace-dialog--buffer-name () - (sly-buffer-name :traces :connection (sly-current-connection))) - -(defun sly-trace-dialog--live-dialog (&optional buffer-or-name) - (let ((buffer-or-name (or buffer-or-name - (sly-trace-dialog--buffer-name)))) - (and (buffer-live-p (get-buffer buffer-or-name)) - (with-current-buffer buffer-or-name - (memq sly-buffer-connection sly-net-processes)) - buffer-or-name))) - -(defun sly-trace-dialog--ensure-buffer () - (let ((name (sly-trace-dialog--buffer-name))) - (or (sly-trace-dialog--live-dialog name) - (let ((connection (sly-current-connection))) - (with-current-buffer (get-buffer-create name) - (let ((inhibit-read-only t)) - (erase-buffer)) - (sly-trace-dialog-mode) - (save-excursion - (buffer-disable-undo) - (sly-trace-dialog--insert-and-overlay - "[waiting for the traced specs to be available]" - sly-trace-dialog--specs-overlay) - (sly-trace-dialog--insert-and-overlay - "[waiting for some info on trace download progress ]" - sly-trace-dialog--progress-overlay) - (sly-trace-dialog--insert-and-overlay - "[waiting for the actual traces to be available]" - sly-trace-dialog--tree-overlay) - (current-buffer)) - (setq sly-buffer-connection connection) - (current-buffer)))))) - -(defun sly-trace-dialog--set-collapsed (collapsed-p trace button) - (save-excursion - (setf (sly-trace-dialog--trace-collapsed-p trace) collapsed-p) - (sly-trace-dialog--go-replace-char-at - button - (if collapsed-p - (cdr sly-trace-dialog--collapse-chars) - (car sly-trace-dialog--collapse-chars))) - (sly-trace-dialog--hide-unhide - (sly-trace-dialog--trace-summary-beg trace) - (sly-trace-dialog--trace-end trace) - (if collapsed-p 1 -1)) - (sly-trace-dialog--hide-unhide - (sly-trace-dialog--trace-end trace) - (sly-trace-dialog--trace-children-end trace) - (if collapsed-p 1 -1)))) - -(defun sly-trace-dialog--hide-unhide (start-pos end-pos delta) - (cl-loop with inhibit-read-only = t - for pos = start-pos then next - for next = (next-single-property-change - pos - 'sly-trace-dialog--hidden-level - nil - end-pos) - for hidden-level = (+ (or (get-text-property - pos - 'sly-trace-dialog--hidden-level) - 0) - delta) - do (add-text-properties pos next - (list 'sly-trace-dialog--hidden-level - hidden-level - 'invisible - (cl-plusp hidden-level))) - while (< next end-pos))) - -(defun sly-trace-dialog--set-hide-details-mode () - (cl-loop for trace being the hash-values of sly-trace-dialog--traces - do (sly-trace-dialog--hide-unhide - (sly-trace-dialog--trace-summary-beg trace) - (sly-trace-dialog--trace-end trace) - (if sly-trace-dialog-hide-details-mode 1 -1)))) - -(defun sly-trace-dialog--format (fmt-string &rest args) - (let* ((string (apply #'format fmt-string args)) - (indent (make-string (max 2 - (- 50 (length string))) ? ))) - (format "%s%s" string indent))) - -(defun sly-trace-dialog--call-maintaining-properties (pos fn) - (save-excursion - (goto-char pos) - (let* ((saved-props (text-properties-at pos)) - (saved-point (point)) - (inhibit-read-only t) - (inhibit-point-motion-hooks t)) - (funcall fn) - (add-text-properties saved-point (point) saved-props) - (if (markerp pos) (set-marker pos saved-point))))) - -(cl-defmacro sly-trace-dialog--maintaining-properties (pos - &body body) - (declare (indent 1)) - `(sly-trace-dialog--call-maintaining-properties ,pos #'(lambda () ,@body))) - -(defun sly-trace-dialog--go-replace-char-at (pos char) - (sly-trace-dialog--maintaining-properties pos - (delete-char 1) - (insert char))) - - -;;;; Handlers for the *trace-dialog* buffer -;;; -(defun sly-trace-dialog--open-specs (traced-specs) - (let ((make-report-spec-fn-fn - (lambda (&optional form) - (lambda (_button) - (sly-eval-async - `(cl:progn - ,form - (slynk-trace-dialog:report-specs)) - #'(lambda (results) - (sly-trace-dialog--open-specs results))))))) - (sly-refreshing - (:overlay sly-trace-dialog--specs-overlay - :recover-point-p t) - (insert - (sly-trace-dialog--format "Traced specs (%s)" (length traced-specs)) - (sly-make-action-button "[refresh]" - (funcall make-report-spec-fn-fn)) - "\n" (make-string 50 ? ) - (sly-make-action-button - "[untrace all]" - (funcall make-report-spec-fn-fn `(slynk-trace-dialog:dialog-untrace-all))) - "\n\n") - (cl-loop for (spec-pretty . spec) in traced-specs - do (insert - " " - (sly-make-action-button - "[untrace]" - (funcall make-report-spec-fn-fn - `(slynk-trace-dialog:dialog-untrace ',spec))) - (format " %s" spec-pretty) - "\n"))))) - -(defvar sly-trace-dialog--fetch-key nil) - -(defvar sly-trace-dialog--stop-fetching nil) - -(defun sly-trace-dialog--update-progress (total &optional show-stop-p remaining-p) - ;; `remaining-p' indicates `total' is the number of remaining traces. - (sly-refreshing - (:overlay sly-trace-dialog--progress-overlay - :recover-point-p t) - (let* ((done (hash-table-count sly-trace-dialog--traces)) - (total (if remaining-p (+ done total) total))) - (insert - (sly-trace-dialog--format "Trace collection status (%d/%s)" - done - (or total "0")) - (sly-make-action-button "[refresh]" - #'(lambda (_button) - (sly-trace-dialog-fetch-progress)))) - - (when (and total (cl-plusp (- total done))) - (insert "\n" (make-string 50 ? ) - (sly-make-action-button - "[fetch next batch]" - #'(lambda (_button) - (sly-trace-dialog-fetch-traces nil))) - "\n" (make-string 50 ? ) - (sly-make-action-button - "[fetch all]" - #'(lambda (_button) - (sly-trace-dialog-fetch-traces t))))) - (when total - (insert "\n" (make-string 50 ? ) - (sly-make-action-button - "[clear]" - #'(lambda (_button) - (sly-trace-dialog-clear-fetched-traces))))) - (when show-stop-p - (insert "\n" (make-string 50 ? ) - (sly-make-action-button - "[stop]" - #'(lambda (_button) - (setq sly-trace-dialog--stop-fetching t))))) - (insert "\n\n")))) - - -;;;; Rendering traces -;;; - -(define-button-type 'sly-trace-dialog-part :supertype 'sly-part - 'sly-button-inspect - #'(lambda (trace-id part-id type) - (sly-eval-for-inspector - `(slynk-trace-dialog:inspect-trace-part ,trace-id ,part-id ,type) - :inspector-name (sly-maybe-read-inspector-name))) - 'sly-button-pretty-print - #'(lambda (trace-id part-id type) - (sly-eval-describe - `(slynk-trace-dialog:pprint-trace-part ,trace-id ,part-id ,type))) - 'sly-button-describe - #'(lambda (trace-id part-id type) - (sly-eval-describe - `(slynk-trace-dialog:describe-trace-part ,trace-id ,part-id ,type)))) - -(defun sly-trace-dialog-part-button (part-id part-text trace-id type) - (sly--make-text-button part-text nil - :type 'sly-trace-dialog-part - 'part-args (list trace-id part-id type) - 'part-label (format "%s %s" - (capitalize - (substring (symbol-name type) 1)) - part-id))) - -(define-button-type 'sly-trace-dialog-spec :supertype 'sly-part - 'action 'sly-button-show-source - 'sly-button-inspect - #'(lambda (trace-id _spec) - (sly-eval-for-inspector `(slynk-trace-dialog:inspect-trace ,trace-id) - :inspector-name "trace-entries")) - 'sly-button-show-source - #'(lambda (trace-id _spec) - (sly-eval-async - `(slynk-trace-dialog:trace-location ,trace-id) - #'(lambda (location) - (sly--display-source-location location 'noerror)))) - 'point-entered - #'(lambda (before after) - (let ((button (sly-button-at after nil 'no-error))) - (when (and (not (sly-button-at before nil 'no-error)) - button - sly-trace-dialog-autofollow-mode) - ;; we can't quite `push-button' here, because - ;; of the need for `save-selected-window' - ;; - (let ((id (button-get button 'trace-id))) - (sly-eval-for-inspector - `(slynk-trace-dialog:inspect-trace ,id) - :inspector-name "trace-entries" - :save-selected-window t)))))) - -(defun sly-trace-dialog-spec-button (label trace &rest props) - (let ((id (sly-trace-dialog--trace-id trace))) - (apply #'sly--make-text-button label nil - :type 'sly-trace-dialog-spec - 'trace-id id - 'part-args (list id - (cdr (sly-trace-dialog--trace-spec trace))) - 'part-label (format "Trace entry: %s" id) - props))) - -(defun sly-trace-dialog--draw-tree-lines (start offset direction) - (save-excursion - (let ((inhibit-point-motion-hooks t)) - (goto-char start) - (cl-loop with replace-set = (if (eq direction 'down) - '(? ) - '(? ?`)) - for line-beginning = (line-beginning-position - (if (eq direction 'down) - 2 0)) - for pos = (+ line-beginning offset) - while (and (< (point-min) line-beginning) - (< line-beginning (point-max)) - (memq (char-after pos) replace-set)) - do - (sly-trace-dialog--go-replace-char-at pos "|") - (goto-char pos))))) - -(defun sly-trace-dialog--make-indent (depth suffix) - (concat (make-string (* 3 (max 0 (1- depth))) ? ) - (if (cl-plusp depth) suffix))) - -(defun sly-trace-dialog--make-collapse-button (trace) - (sly-make-action-button (if (sly-trace-dialog--trace-collapsed-p trace) - (cdr sly-trace-dialog--collapse-chars) - (car sly-trace-dialog--collapse-chars)) - #'(lambda (button) - (sly-trace-dialog--set-collapsed - (not (sly-trace-dialog--trace-collapsed-p - trace)) - trace - button)))) - -(defun sly-trace-dialog--insert-trace (trace) - (let* ((id (sly-trace-dialog--trace-id trace)) - (parent (sly-trace-dialog--trace-parent trace)) - (has-children-p (sly-trace-dialog--trace-children-end trace)) - (indent-spec (sly-trace-dialog--make-indent - (sly-trace-dialog--trace-depth trace) - "`--")) - (indent-summary (sly-trace-dialog--make-indent - (sly-trace-dialog--trace-depth trace) - " ")) - (id-string - (sly-trace-dialog-spec-button - (format "%4s" id) trace 'skip t 'action 'sly-button-inspect)) - (spec-button (sly-trace-dialog-spec-button - (format "%s" (car (sly-trace-dialog--trace-spec trace))) - trace)) - (summary (cl-loop for (type objects marker) in - `((:arg ,(sly-trace-dialog--trace-args trace) - " > ") - (:retval ,(sly-trace-dialog--trace-retlist trace) - " < ")) - concat (cl-loop for object in objects - concat " " - concat indent-summary - concat marker - concat (sly-trace-dialog-part-button - (cl-first object) - (cl-second object) - id - type) - concat "\n")))) - (puthash id trace sly-trace-dialog--traces) - ;; insert and propertize the text - ;; - (setf (sly-trace-dialog--trace-beg trace) (point-marker)) - (insert id-string " ") - (insert indent-spec) - (if has-children-p - (insert (sly-trace-dialog--make-collapse-button trace)) - (setf (sly-trace-dialog--trace-collapse-button-marker trace) - (point-marker)) - (insert "-")) - (insert " " spec-button "\n") - (setf (sly-trace-dialog--trace-summary-beg trace) (point-marker)) - (insert summary) - (setf (sly-trace-dialog--trace-end trace) (point-marker)) - (set-marker-insertion-type (sly-trace-dialog--trace-beg trace) t) - - ;; respect brief mode and collapsed state - ;; - (cl-loop for condition in (list sly-trace-dialog-hide-details-mode - (sly-trace-dialog--trace-collapsed-p trace)) - when condition - do (sly-trace-dialog--hide-unhide - (sly-trace-dialog--trace-summary-beg - trace) - (sly-trace-dialog--trace-end trace) - 1)) - (cl-loop for tr = trace then parent - for parent = (sly-trace-dialog--trace-parent tr) - while parent - when (sly-trace-dialog--trace-collapsed-p parent) - do (sly-trace-dialog--hide-unhide - (sly-trace-dialog--trace-beg trace) - (sly-trace-dialog--trace-end trace) - (+ 1 - (or (get-text-property (sly-trace-dialog--trace-beg parent) - 'sly-trace-dialog--hidden-level) - 0))) - (cl-return)) - ;; maybe add the collapse-button to the parent in case it didn't - ;; have one already - ;; - (when (and parent - (sly-trace-dialog--trace-collapse-button-marker parent)) - (sly-trace-dialog--maintaining-properties - (sly-trace-dialog--trace-collapse-button-marker parent) - (delete-char 1) - (insert (sly-trace-dialog--make-collapse-button parent)) - (setf (sly-trace-dialog--trace-collapse-button-marker parent) - nil))) - ;; draw the tree lines - ;; - (when parent - (sly-trace-dialog--draw-tree-lines (sly-trace-dialog--trace-beg trace) - (+ 2 (length indent-spec)) - 'up)) - (when has-children-p - (sly-trace-dialog--draw-tree-lines (sly-trace-dialog--trace-beg trace) - (+ 5 (length indent-spec)) - 'down)) - ;; set the "children-end" slot - ;; - (unless (sly-trace-dialog--trace-children-end trace) - (cl-loop for parent = trace - then (sly-trace-dialog--trace-parent parent) - while parent - do - (setf (sly-trace-dialog--trace-children-end parent) - (sly-trace-dialog--trace-end trace)))))) - -(defun sly-trace-dialog--render-trace (trace) - ;; Render the trace entry in the appropriate place. - ;; - ;; A trace becomes a few lines of slightly propertized text in the - ;; buffer, inserted by `sly-trace-dialog--insert-trace', bound by - ;; point markers that we use here. - ;; - ;; The new trace might be replacing an existing one, or otherwise - ;; must be placed under its existing parent which might or might not - ;; be the last entry inserted. - ;; - (let ((existing (sly-trace-dialog--find-trace - (sly-trace-dialog--trace-id trace))) - (parent (sly-trace-dialog--trace-parent trace))) - (cond (existing - ;; Other traces might already reference `existing' and with - ;; need to maintain that eqness. Best way to do that is - ;; destructively modify `existing' with the new retlist... - ;; - (setf (sly-trace-dialog--trace-retlist existing) - (sly-trace-dialog--trace-retlist trace)) - ;; Now, before deleting and re-inserting `existing' at an - ;; arbitrary point in the tree, note that it's - ;; "children-end" marker is already non-nil, and informs us - ;; about its parenthood status. We want to 1. leave it - ;; alone if it's already a parent, or 2. set it to nil if - ;; it's a leaf, thus forcing the needed update of the - ;; parents' "children-end" marker. - ;; - (when (= (sly-trace-dialog--trace-children-end existing) - (sly-trace-dialog--trace-end existing)) - (setf (sly-trace-dialog--trace-children-end existing) nil)) - (delete-region (sly-trace-dialog--trace-beg existing) - (sly-trace-dialog--trace-end existing)) - (goto-char (sly-trace-dialog--trace-end existing)) - ;; Remember to set `trace' to be `existing' - ;; - (setq trace existing)) - (parent - (goto-char (1+ (sly-trace-dialog--trace-children-end parent)))) - (;; top level trace - t - (goto-char (point-max)))) - (goto-char (line-beginning-position)) - (sly-trace-dialog--insert-trace trace))) - -(defun sly-trace-dialog--update-tree (tuples) - (save-excursion - (sly-refreshing - (:overlay sly-trace-dialog--tree-overlay - :dont-erase t) - (cl-loop for tuple in tuples - for parent = (sly-trace-dialog--find-trace (cl-second tuple)) - for trace = (sly-trace-dialog--make-trace - :id (cl-first tuple) - :parent parent - :spec (cl-third tuple) - :args (cl-fourth tuple) - :retlist (cl-fifth tuple) - :depth (if parent - (1+ (sly-trace-dialog--trace-depth - parent)) - 0)) - do (sly-trace-dialog--render-trace trace))))) - -(defun sly-trace-dialog--clear-local-tree () - (set (make-local-variable 'sly-trace-dialog--fetch-key) - (cl-gensym "sly-trace-dialog-fetch-key-")) - (set (make-local-variable 'sly-trace-dialog--traces) - (make-hash-table)) - (sly-refreshing - (:overlay sly-trace-dialog--tree-overlay)) - (sly-trace-dialog--update-progress nil)) - -(defun sly-trace-dialog--on-new-results (results &optional recurse) - (cl-destructuring-bind (tuples remaining reply-key) - results - (cond ((and sly-trace-dialog--fetch-key - (string= (symbol-name sly-trace-dialog--fetch-key) - (symbol-name reply-key))) - (sly-trace-dialog--update-tree tuples) - (sly-trace-dialog--update-progress - remaining - (and recurse - (cl-plusp remaining)) - t) - (when (and recurse - (not (prog1 sly-trace-dialog--stop-fetching - (setq sly-trace-dialog--stop-fetching nil))) - (cl-plusp remaining)) - (sly-eval-async `(slynk-trace-dialog:report-partial-tree - ',reply-key) - #'(lambda (results) (sly-trace-dialog--on-new-results - results - recurse)))))))) - - -;;;; Interactive functions -;;; -(defun sly-trace-dialog-fetch-specs () - "Refresh just list of traced specs." - (interactive) - (sly-eval-async `(slynk-trace-dialog:report-specs) - #'sly-trace-dialog--open-specs)) - -(defun sly-trace-dialog-fetch-progress () - (interactive) - (sly-eval-async - '(slynk-trace-dialog:report-total) - #'(lambda (total) - (sly-trace-dialog--update-progress - total)))) - -(defun sly-trace-dialog-fetch-status () - "Refresh just the status part of the SLY Trace Dialog" - (interactive) - (sly-trace-dialog-fetch-specs) - (sly-trace-dialog-fetch-progress)) - -(defun sly-trace-dialog-clear-fetched-traces (&optional interactive) - "Clear local and remote traces collected so far" - (interactive "p") - (when (or (not interactive) - (y-or-n-p "Clear all collected and fetched traces?")) - (sly-eval-async - '(slynk-trace-dialog:clear-trace-tree) - #'(lambda (_ignored) - (sly-trace-dialog--clear-local-tree))))) - -(defun sly-trace-dialog-fetch-traces (&optional recurse) - (interactive "P") - (setq sly-trace-dialog--stop-fetching nil) - (sly-eval-async `(slynk-trace-dialog:report-partial-tree - ',sly-trace-dialog--fetch-key) - #'(lambda (results) (sly-trace-dialog--on-new-results results - recurse)))) - -(defvar sly-trace-dialog-after-toggle-hook nil - "Hooks run after toggling a dialog-trace") - -(defun sly-trace-dialog-toggle-trace (&optional using-context-p) - "Toggle the dialog-trace of the spec at point. - -When USING-CONTEXT-P, attempt to decipher lambdas. methods and -other complicated function specs." - (interactive "P") - ;; Notice the use of "spec strings" here as opposed to the - ;; proper cons specs we use on the slynk side. - ;; - ;; Notice the conditional use of `sly-trace-query' found in - ;; slynk-fancy-trace.el - ;; - (let* ((spec-string (if using-context-p - (sly-extract-context) - (sly-symbol-at-point))) - (spec-string (if (fboundp 'sly-trace-query) - (sly-trace-query spec-string) - spec-string))) - (sly-message "%s" (sly-eval `(slynk-trace-dialog:dialog-toggle-trace - (slynk::from-string ,spec-string)))) - (run-hooks 'sly-trace-dialog-after-toggle-hook))) - -(defun sly-trace-dialog-untrace-all () - "Untrace all specs traced for the Trace Dialog." - (interactive) - (sly-eval-async `(slynk-trace-dialog:dialog-untrace-all) - #'(lambda (results) - (sly-message "%s dialog specs and %s regular specs untraced" - (cdr results) (car results) ))) - (run-hooks 'sly-trace-dialog-after-toggle-hook)) - -(defun sly-trace-dialog--update-existing-dialog () - (let ((existing (sly-trace-dialog--live-dialog))) - (when existing - (with-current-buffer existing - (sly-trace-dialog-fetch-status))))) - -(add-hook 'sly-trace-dialog-after-toggle-hook - 'sly-trace-dialog--update-existing-dialog) - -(defun sly-trace-dialog-toggle-complex-trace () - "Toggle the dialog-trace of the complex spec at point. - -See `sly-trace-dialog-toggle-trace'." - (interactive) - (sly-trace-dialog-toggle-trace t)) - -(defun sly-trace-dialog (&optional clear-and-fetch) - "Show trace dialog and refresh trace collection status. - -With optional CLEAR-AND-FETCH prefix arg, clear the current tree -and fetch a first batch of traces." - (interactive "P") - (with-current-buffer - ;; FIXME: refactor with `sly-with-popup-buffer' - (pop-to-buffer - (sly-trace-dialog--ensure-buffer) - `(display-buffer-reuse-window . ((inhibit-same-window . t)))) - (sly-trace-dialog-fetch-status) - (when (or clear-and-fetch - (null sly-trace-dialog--fetch-key)) - (sly-trace-dialog--clear-local-tree)) - (when clear-and-fetch - (sly-trace-dialog-fetch-traces nil)))) - -(provide 'sly-trace-dialog) blob - 8767cd5506062db2268dba0271d9ecfb93e545a7 (mode 644) blob + /dev/null --- elpa/sly-20231213.1030/contrib/sly-tramp.el +++ /dev/null @@ -1,123 +0,0 @@ -;; -*- lexical-binding: t; -*- -(require 'sly) -(require 'tramp) -(require 'cl-lib) - -(define-sly-contrib sly-tramp - "Filename translations for tramp" - (:authors "Marco Baringer ") - (:license "GPL") - (:on-load - (setq sly-to-lisp-filename-function #'sly-tramp-to-lisp-filename) - (setq sly-from-lisp-filename-function #'sly-tramp-from-lisp-filename))) - -(defcustom sly-filename-translations nil - "Assoc list of hostnames and filename translation functions. -Each element is of the form (HOSTNAME-REGEXP TO-LISP FROM-LISP). - -HOSTNAME-REGEXP is a regexp which is applied to the connection's -sly-machine-instance. If HOSTNAME-REGEXP maches then the -corresponding TO-LISP and FROM-LISP functions will be used to -translate emacs filenames and lisp filenames. - -TO-LISP will be passed the filename of an emacs buffer and must -return a string which the underlying lisp understandas as a -pathname. FROM-LISP will be passed a pathname as returned by the -underlying lisp and must return something that emacs will -understand as a filename (this string will be passed to -find-file). - -This list will be traversed in order, so multiple matching -regexps are possible. - -Example: - -Assuming you run emacs locally and connect to sly running on -the machine 'soren' and you can connect with the username -'animaliter': - - (push (list \"^soren$\" - (lambda (emacs-filename) - (subseq emacs-filename (length \"/ssh:animaliter@soren:\"))) - (lambda (lisp-filename) - (concat \"/ssh:animaliter@soren:\" lisp-filename))) - sly-filename-translations) - -See also `sly-create-filename-translator'." - :type '(repeat (list :tag "Host description" - (regexp :tag "Hostname regexp") - (function :tag "To lisp function") - (function :tag "From lisp function"))) - :group 'sly-lisp) - -(defun sly-find-filename-translators (hostname) - (cond ((cdr (cl-assoc-if (lambda (regexp) (string-match regexp hostname)) - sly-filename-translations))) - (t (list #'identity #'identity)))) - -(defun sly-make-tramp-file-name (username remote-host lisp-filename) - "Tramp compatability function. - -Handles the signature of `tramp-make-tramp-file-name' changing -over time." - (cond - ((>= emacs-major-version 26) - ;; Emacs 26 requires the method to be provided and the signature of - ;; `tramp-make-tramp-file-name' has changed. - (tramp-make-tramp-file-name (tramp-find-method nil username remote-host) - username - nil - remote-host - nil - lisp-filename)) - ((boundp 'tramp-multi-methods) - (tramp-make-tramp-file-name nil nil - username - remote-host - lisp-filename)) - (t - (tramp-make-tramp-file-name nil - username - remote-host - lisp-filename)))) - -(cl-defun sly-create-filename-translator (&key machine-instance - remote-host - username) - "Creates a three element list suitable for push'ing onto -sly-filename-translations which uses Tramp to load files on -hostname using username. MACHINE-INSTANCE is a required -parameter, REMOTE-HOST defaults to MACHINE-INSTANCE and USERNAME -defaults to (user-login-name). - -MACHINE-INSTANCE is the value returned by sly-machine-instance, -which is just the value returned by cl:machine-instance on the -remote lisp. REMOTE-HOST is the fully qualified domain name (or -just the IP) of the remote machine. USERNAME is the username we -should login with. -The functions created here expect your tramp-default-method or - tramp-default-method-alist to be setup correctly." - (let ((remote-host (or remote-host machine-instance)) - (username (or username (user-login-name)))) - (list (concat "^" machine-instance "$") - (lambda (emacs-filename) - (tramp-file-name-localname - (tramp-dissect-file-name emacs-filename))) - `(lambda (lisp-filename) - (sly-make-tramp-file-name - ,username - ,remote-host - lisp-filename))))) - -(defun sly-tramp-to-lisp-filename (filename) - (funcall (if (let ((conn (sly-current-connection))) - (and conn (process-live-p conn))) - (cl-first (sly-find-filename-translators (sly-machine-instance))) - 'identity) - (expand-file-name filename))) - -(defun sly-tramp-from-lisp-filename (filename) - (funcall (cl-second (sly-find-filename-translators (sly-machine-instance))) - filename)) - -(provide 'sly-tramp) blob - bb78d688d2aef10c32dc955e3e9fee2d23443db0 (mode 644) blob + /dev/null --- elpa/sly-20231213.1030/contrib/slynk-arglists.lisp +++ /dev/null @@ -1,1606 +0,0 @@ -;;; slynk-arglists.lisp --- arglist related code ?? -;; -;; Authors: Matthias Koeppe -;; Tobias C. Rittweiler -;; and others -;; -;; License: Public Domain -;; - -(in-package :slynk) - -;;;; Utilities - -(defun compose (&rest functions) - "Compose FUNCTIONS right-associatively, returning a function" - #'(lambda (x) - (reduce #'funcall functions :initial-value x :from-end t))) - -(defun length= (seq n) - "Test for whether SEQ contains N number of elements. I.e. it's equivalent - to (= (LENGTH SEQ) N), but besides being more concise, it may also be more - efficiently implemented." - (etypecase seq - (list (do ((i n (1- i)) - (list seq (cdr list))) - ((or (<= i 0) (null list)) - (and (zerop i) (null list))))) - (sequence (= (length seq) n)))) - -(declaim (inline memq)) -(defun memq (item list) - (member item list :test #'eq)) - -(defun exactly-one-p (&rest values) - "If exactly one value in VALUES is non-NIL, this value is returned. -Otherwise NIL is returned." - (let ((found nil)) - (dolist (v values) - (when v (if found - (return-from exactly-one-p nil) - (setq found v)))) - found)) - -(defun valid-operator-symbol-p (symbol) - "Is SYMBOL the name of a function, a macro, or a special-operator?" - (or (fboundp symbol) - (macro-function symbol) - (special-operator-p symbol) - (member symbol '(declare declaim)))) - -(defun function-exists-p (form) - (and (valid-function-name-p form) - (fboundp form) - t)) - -(defmacro multiple-value-or (&rest forms) - (if (null forms) - nil - (let ((first (first forms)) - (rest (rest forms))) - `(let* ((values (multiple-value-list ,first)) - (primary-value (first values))) - (if primary-value - (values-list values) - (multiple-value-or ,@rest)))))) - -(defun arglist-available-p (arglist) - (not (eql arglist :not-available))) - -(defmacro with-available-arglist ((var &rest more-vars) form &body body) - `(multiple-value-bind (,var ,@more-vars) ,form - (if (eql ,var :not-available) - :not-available - (progn ,@body)))) - - -;;;; Arglist Definition - -(defstruct (arglist (:conc-name arglist.) (:predicate arglist-p)) - provided-args ; list of the provided actual arguments - required-args ; list of the required arguments - optional-args ; list of the optional arguments - key-p ; whether &key appeared - keyword-args ; list of the keywords - rest ; name of the &rest or &body argument (if any) - body-p ; whether the rest argument is a &body - allow-other-keys-p ; whether &allow-other-keys appeared - aux-args ; list of &aux variables - any-p ; whether &any appeared - any-args ; list of &any arguments [*] - known-junk ; &whole, &environment - unknown-junk) ; unparsed stuff - -;;; -;;; [*] The &ANY lambda keyword is an extension to ANSI Common Lisp, -;;; and is only used to describe certain arglists that cannot be -;;; described in another way. -;;; -;;; &ANY is very similiar to &KEY but while &KEY is based upon -;;; the idea of a plist (key1 value1 key2 value2), &ANY is a -;;; cross between &OPTIONAL, &KEY and *FEATURES* lists: -;;; -;;; a) (&ANY :A :B :C) means that you can provide any (non-null) -;;; set consisting of the keywords `:A', `:B', or `:C' in -;;; the arglist. E.g. (:A) or (:C :B :A). -;;; -;;; (This is not restricted to keywords only, but any self-evaluating -;;; expression is allowed.) -;;; -;;; b) (&ANY (key1 v1) (key2 v2) (key3 v3)) means that you can -;;; provide any (non-null) set consisting of lists where -;;; the CAR of the list is one of `key1', `key2', or `key3'. -;;; E.g. ((key1 100) (key3 42)), or ((key3 66) (key2 23)) -;;; -;;; -;;; For example, a) let us describe the situations of EVAL-WHEN as -;;; -;;; (EVAL-WHEN (&ANY :compile-toplevel :load-toplevel :execute) &BODY body) -;;; -;;; and b) let us describe the optimization qualifiers that are valid -;;; in the declaration specifier `OPTIMIZE': -;;; -;;; (DECLARE (OPTIMIZE &ANY (compilation-speed 1) (safety 1) ...)) -;;; - -;; This is a wrapper object around anything that came from Slime and -;; could not reliably be read. -(defstruct (arglist-dummy - (:conc-name #:arglist-dummy.) - (:constructor make-arglist-dummy (string-representation))) - string-representation) - -(defun empty-arg-p (dummy) - (and (arglist-dummy-p dummy) - (zerop (length (arglist-dummy.string-representation dummy))))) - -(eval-when (:compile-toplevel :load-toplevel :execute) - (defparameter +lambda-list-keywords+ - '(&provided &required &optional &rest &key &any))) - -(defmacro do-decoded-arglist (decoded-arglist &body clauses) - (assert (loop for clause in clauses - thereis (member (car clause) +lambda-list-keywords+))) - (flet ((parse-clauses (clauses) - (let* ((size (length +lambda-list-keywords+)) - (initial (make-hash-table :test #'eq :size size)) - (main (make-hash-table :test #'eq :size size)) - (final (make-hash-table :test #'eq :size size))) - (loop for clause in clauses - for lambda-list-keyword = (first clause) - for clause-parameter = (second clause) - do - (case clause-parameter - (:initially - (setf (gethash lambda-list-keyword initial) clause)) - (:finally - (setf (gethash lambda-list-keyword final) clause)) - (t - (setf (gethash lambda-list-keyword main) clause))) - finally - (return (values initial main final))))) - (generate-main-clause (clause arglist) - (destructure-case clause - ((&provided (&optional arg) . body) - (let ((gensym (gensym "PROVIDED-ARG+"))) - `(dolist (,gensym (arglist.provided-args ,arglist)) - (declare (ignorable ,gensym)) - (let (,@(when arg `((,arg ,gensym)))) - ,@body)))) - ((&required (&optional arg) . body) - (let ((gensym (gensym "REQUIRED-ARG+"))) - `(dolist (,gensym (arglist.required-args ,arglist)) - (declare (ignorable ,gensym)) - (let (,@(when arg `((,arg ,gensym)))) - ,@body)))) - ((&optional (&optional arg init) . body) - (let ((optarg (gensym "OPTIONAL-ARG+"))) - `(dolist (,optarg (arglist.optional-args ,arglist)) - (declare (ignorable ,optarg)) - (let (,@(when arg - `((,arg (optional-arg.arg-name ,optarg)))) - ,@(when init - `((,init (optional-arg.default-arg ,optarg))))) - ,@body)))) - ((&key (&optional keyword arg init) . body) - (let ((keyarg (gensym "KEY-ARG+"))) - `(dolist (,keyarg (arglist.keyword-args ,arglist)) - (declare (ignorable ,keyarg)) - (let (,@(when keyword - `((,keyword (keyword-arg.keyword ,keyarg)))) - ,@(when arg - `((,arg (keyword-arg.arg-name ,keyarg)))) - ,@(when init - `((,init (keyword-arg.default-arg ,keyarg))))) - ,@body)))) - ((&rest (&optional arg body-p) . body) - `(when (arglist.rest ,arglist) - (let (,@(when arg `((,arg (arglist.rest ,arglist)))) - ,@(when body-p `((,body-p (arglist.body-p ,arglist))))) - ,@body))) - ((&any (&optional arg) . body) - (let ((gensym (gensym "REQUIRED-ARG+"))) - `(dolist (,gensym (arglist.any-args ,arglist)) - (declare (ignorable ,gensym)) - (let (,@(when arg `((,arg ,gensym)))) - ,@body))))))) - (let ((arglist (gensym "DECODED-ARGLIST+"))) - (multiple-value-bind (initially-clauses main-clauses finally-clauses) - (parse-clauses clauses) - `(let ((,arglist ,decoded-arglist)) - (block do-decoded-arglist - ,@(loop for keyword in '(&provided &required - &optional &rest &key &any) - append (cddr (gethash keyword initially-clauses)) - collect (let ((clause (gethash keyword main-clauses))) - (when clause - (generate-main-clause clause arglist))) - append (cddr (gethash keyword finally-clauses))))))))) - -;;;; Arglist Printing - -(defun undummy (x) - (if (typep x 'arglist-dummy) - (arglist-dummy.string-representation x) - (prin1-to-string x))) - -(defun print-decoded-arglist (arglist &key operator provided-args highlight) - (let ((first-space-after-operator (and operator t))) - (macrolet ((space () - ;; Kludge: When OPERATOR is not given, we don't want to - ;; print a space for the first argument. - `(if (not operator) - (setq operator t) - (progn (write-char #\space) - (if first-space-after-operator - (setq first-space-after-operator nil) - (pprint-newline :fill))))) - (with-highlighting ((&key index) &body body) - `(if (eql ,index (car highlight)) - (progn (princ "===> ") ,@body (princ " <===")) - (progn ,@body))) - (print-arglist-recursively (argl &key index) - `(if (eql ,index (car highlight)) - (print-decoded-arglist ,argl :highlight (cdr highlight)) - (print-decoded-arglist ,argl)))) - (let ((index 0)) - (pprint-logical-block (nil nil :prefix "(" :suffix ")") - (when operator - (print-arg operator) - (pprint-indent :current 1)) ; 1 due to possibly added space - (do-decoded-arglist (remove-given-args arglist provided-args) - (&provided (arg) - (space) - (print-arg arg :literal-strings t) - (incf index)) - (&required (arg) - (space) - (if (arglist-p arg) - (print-arglist-recursively arg :index index) - (with-highlighting (:index index) - (print-arg arg))) - (incf index)) - (&optional :initially - (when (arglist.optional-args arglist) - (space) - (princ '&optional))) - (&optional (arg init-value) - (space) - (if (arglist-p arg) - (print-arglist-recursively arg :index index) - (with-highlighting (:index index) - (if (null init-value) - (print-arg arg) - (format t "~:@<~A ~A~@:>" - (undummy arg) (undummy init-value))))) - (incf index)) - (&key :initially - (when (arglist.key-p arglist) - (space) - (princ '&key))) - (&key (keyword arg init) - (space) - (if (arglist-p arg) - (pprint-logical-block (nil nil :prefix "(" :suffix ")") - (prin1 keyword) (space) - (print-arglist-recursively arg :index keyword)) - (with-highlighting (:index keyword) - (cond ((and init (keywordp keyword)) - (format t "~:@<~A ~A~@:>" keyword (undummy init))) - (init - (format t "~:@<(~A ..) ~A~@:>" - (undummy keyword) (undummy init))) - ((not (keywordp keyword)) - (format t "~:@<(~S ..)~@:>" keyword)) - (t - (princ keyword)))))) - (&key :finally - (when (arglist.allow-other-keys-p arglist) - (space) - (princ '&allow-other-keys))) - (&any :initially - (when (arglist.any-p arglist) - (space) - (princ '&any))) - (&any (arg) - (space) - (print-arg arg)) - (&rest (args bodyp) - (space) - (princ (if bodyp '&body '&rest)) - (space) - (if (arglist-p args) - (print-arglist-recursively args :index index) - (with-highlighting (:index index) - (print-arg args)))) - ;; FIXME: add &UNKNOWN-JUNK? - )))))) - -(defun print-arg (arg &key literal-strings) - (let ((arg (if (arglist-dummy-p arg) - (arglist-dummy.string-representation arg) - arg))) - (if (or - (and literal-strings - (stringp arg)) - (keywordp arg)) - (prin1 arg) - (princ arg)))) - -(defun print-decoded-arglist-as-template (decoded-arglist &key - (prefix "(") (suffix ")")) - (let ((first-p t)) - (flet ((space () - (unless first-p - (write-char #\space)) - (setq first-p nil)) - (print-arg-or-pattern (arg) - (etypecase arg - (symbol (if (keywordp arg) (prin1 arg) (princ arg))) - (string (princ arg)) - (list (princ arg)) - (arglist-dummy (princ - (arglist-dummy.string-representation arg))) - (arglist (print-decoded-arglist-as-template arg))) - (pprint-newline :fill))) - (pprint-logical-block (nil nil :prefix prefix :suffix suffix) - (do-decoded-arglist decoded-arglist - (&provided ()) ; do nothing; provided args are in the buffer already. - (&required (arg) - (space) (print-arg-or-pattern arg)) - (&optional (arg) - (space) (princ "[") (print-arg-or-pattern arg) (princ "]")) - (&key (keyword arg) - (space) - (prin1 (if (keywordp keyword) keyword `',keyword)) - (space) - (print-arg-or-pattern arg) - (pprint-newline :linear)) - (&any (arg) - (space) (print-arg-or-pattern arg)) - (&rest (args) - (when (or (not (arglist.keyword-args decoded-arglist)) - (arglist.allow-other-keys-p decoded-arglist)) - (space) - (format t "~A..." args)))))))) - -(defvar *arglist-pprint-bindings* - '((*print-case* . :downcase) - (*print-pretty* . t) - (*print-circle* . nil) - (*print-readably* . nil) - (*print-level* . 10) - (*print-length* . 20) - (*print-escape* . nil))) - -(defvar *arglist-show-packages* t) - -(defmacro with-arglist-io-syntax (&body body) - (let ((package (gensym))) - `(let ((,package *package*)) - (with-standard-io-syntax - (let ((*package* (if *arglist-show-packages* - *package* - ,package))) - (with-bindings *arglist-pprint-bindings* - ,@body)))))) - -(defun decoded-arglist-to-string (decoded-arglist - &key operator highlight - print-right-margin) - (with-output-to-string (*standard-output*) - (with-arglist-io-syntax - (let ((*print-right-margin* print-right-margin)) - (print-decoded-arglist decoded-arglist - :operator operator - :highlight highlight))))) - -(defun decoded-arglist-to-template-string (decoded-arglist - &key (prefix "(") (suffix ")")) - (with-output-to-string (*standard-output*) - (with-arglist-io-syntax - (print-decoded-arglist-as-template decoded-arglist - :prefix prefix - :suffix suffix)))) - -;;;; Arglist Decoding / Encoding - -(defun decode-required-arg (arg) - "ARG can be a symbol or a destructuring pattern." - (etypecase arg - (symbol arg) - (arglist-dummy arg) - (list (decode-arglist arg)) - (number arg))) - -(defun encode-required-arg (arg) - (etypecase arg - (symbol arg) - (arglist (encode-arglist arg)))) - -(defstruct (keyword-arg - (:conc-name keyword-arg.) - (:constructor %make-keyword-arg)) - keyword - arg-name - default-arg) - -(defun canonicalize-default-arg (form) - (if (equalp ''nil form) - nil - form)) - -(defun make-keyword-arg (keyword arg-name default-arg) - (%make-keyword-arg :keyword keyword - :arg-name arg-name - :default-arg (canonicalize-default-arg default-arg))) - -(defun decode-keyword-arg (arg) - "Decode a keyword item of formal argument list. -Return three values: keyword, argument name, default arg." - (flet ((intern-as-keyword (arg) - (intern (etypecase arg - (symbol (symbol-name arg)) - (arglist-dummy (arglist-dummy.string-representation arg))) - +keyword-package+))) - (cond ((or (symbolp arg) (arglist-dummy-p arg)) - (make-keyword-arg (intern-as-keyword arg) arg nil)) - ((and (consp arg) - (consp (car arg))) - (make-keyword-arg (caar arg) - (decode-required-arg (cadar arg)) - (cadr arg))) - ((consp arg) - (make-keyword-arg (intern-as-keyword (car arg)) - (car arg) (cadr arg))) - (t - (error "Bad keyword item of formal argument list"))))) - -(defun encode-keyword-arg (arg) - (cond - ((arglist-p (keyword-arg.arg-name arg)) - ;; Destructuring pattern - (let ((keyword/name (list (keyword-arg.keyword arg) - (encode-required-arg - (keyword-arg.arg-name arg))))) - (if (keyword-arg.default-arg arg) - (list keyword/name - (keyword-arg.default-arg arg)) - (list keyword/name)))) - ((eql (intern (symbol-name (keyword-arg.arg-name arg)) - +keyword-package+) - (keyword-arg.keyword arg)) - (if (keyword-arg.default-arg arg) - (list (keyword-arg.arg-name arg) - (keyword-arg.default-arg arg)) - (keyword-arg.arg-name arg))) - (t - (let ((keyword/name (list (keyword-arg.keyword arg) - (keyword-arg.arg-name arg)))) - (if (keyword-arg.default-arg arg) - (list keyword/name - (keyword-arg.default-arg arg)) - (list keyword/name)))))) - -(progn - (assert (equalp (decode-keyword-arg 'x) - (make-keyword-arg :x 'x nil))) - (assert (equalp (decode-keyword-arg '(x t)) - (make-keyword-arg :x 'x t))) - (assert (equalp (decode-keyword-arg '((:x y))) - (make-keyword-arg :x 'y nil))) - (assert (equalp (decode-keyword-arg '((:x y) t)) - (make-keyword-arg :x 'y t)))) - -;;; FIXME suppliedp? -(defstruct (optional-arg - (:conc-name optional-arg.) - (:constructor %make-optional-arg)) - arg-name - default-arg) - -(defun make-optional-arg (arg-name default-arg) - (%make-optional-arg :arg-name arg-name - :default-arg (canonicalize-default-arg default-arg))) - -(defun decode-optional-arg (arg) - "Decode an optional item of a formal argument list. -Return an OPTIONAL-ARG structure." - (etypecase arg - (symbol (make-optional-arg arg nil)) - (arglist-dummy (make-optional-arg arg nil)) - (list (make-optional-arg (decode-required-arg (car arg)) - (cadr arg))))) - -(defun encode-optional-arg (optional-arg) - (if (or (optional-arg.default-arg optional-arg) - (arglist-p (optional-arg.arg-name optional-arg))) - (list (encode-required-arg - (optional-arg.arg-name optional-arg)) - (optional-arg.default-arg optional-arg)) - (optional-arg.arg-name optional-arg))) - -(progn - (assert (equalp (decode-optional-arg 'x) - (make-optional-arg 'x nil))) - (assert (equalp (decode-optional-arg '(x t)) - (make-optional-arg 'x t)))) - -(define-modify-macro nreversef () nreverse "Reverse the list in PLACE.") - -(defun decode-arglist (arglist) - "Parse the list ARGLIST and return an ARGLIST structure." - (if (eq arglist :not-available) (return-from decode-arglist arglist)) - (loop - with mode = nil - with result = (make-arglist) - for arg = (if (consp arglist) - (pop arglist) - (progn - (prog1 arglist - (setf mode '&rest - arglist nil)))) - do (cond - ((eql mode '&unknown-junk) - ;; don't leave this mode -- we don't know how the arglist - ;; after unknown lambda-list keywords is interpreted - (push arg (arglist.unknown-junk result))) - ((eql arg '&allow-other-keys) - (setf (arglist.allow-other-keys-p result) t)) - ((eql arg '&key) - (setf (arglist.key-p result) t - mode arg)) - ((memq arg '(&optional &rest &body &aux)) - (setq mode arg)) - ((memq arg '(&whole &environment)) - (setq mode arg) - (push arg (arglist.known-junk result))) - ((and (symbolp arg) - (string= (symbol-name arg) (string '#:&any))) ; may be interned - (setf (arglist.any-p result) t) ; in any *package*. - (setq mode '&any)) - ((memq arg lambda-list-keywords) - (setq mode '&unknown-junk) - (push arg (arglist.unknown-junk result))) - (t - (ecase mode - (&key - (push (decode-keyword-arg arg) - (arglist.keyword-args result))) - (&optional - (push (decode-optional-arg arg) - (arglist.optional-args result))) - (&body - (setf (arglist.body-p result) t - (arglist.rest result) arg)) - (&rest - (setf (arglist.rest result) arg)) - (&aux - (push (decode-optional-arg arg) - (arglist.aux-args result))) - ((nil) - (push (decode-required-arg arg) - (arglist.required-args result))) - ((&whole &environment) - (setf mode nil) - (push arg (arglist.known-junk result))) - (&any - (push arg (arglist.any-args result)))))) - until (null arglist) - finally (nreversef (arglist.required-args result)) - finally (nreversef (arglist.optional-args result)) - finally (nreversef (arglist.keyword-args result)) - finally (nreversef (arglist.aux-args result)) - finally (nreversef (arglist.any-args result)) - finally (nreversef (arglist.known-junk result)) - finally (nreversef (arglist.unknown-junk result)) - finally (assert (or (and (not (arglist.key-p result)) - (not (arglist.any-p result))) - (exactly-one-p (arglist.key-p result) - (arglist.any-p result)))) - finally (return result))) - -(defun encode-arglist (decoded-arglist) - (append (mapcar #'encode-required-arg - (arglist.required-args decoded-arglist)) - (when (arglist.optional-args decoded-arglist) - '(&optional)) - (mapcar #'encode-optional-arg - (arglist.optional-args decoded-arglist)) - (when (arglist.key-p decoded-arglist) - '(&key)) - (mapcar #'encode-keyword-arg - (arglist.keyword-args decoded-arglist)) - (when (arglist.allow-other-keys-p decoded-arglist) - '(&allow-other-keys)) - (when (arglist.any-args decoded-arglist) - `(&any ,@(arglist.any-args decoded-arglist))) - (cond ((not (arglist.rest decoded-arglist)) - '()) - ((arglist.body-p decoded-arglist) - `(&body ,(arglist.rest decoded-arglist))) - (t - `(&rest ,(arglist.rest decoded-arglist)))) - (when (arglist.aux-args decoded-arglist) - `(&aux ,(arglist.aux-args decoded-arglist))) - (arglist.known-junk decoded-arglist) - (arglist.unknown-junk decoded-arglist))) - -;;;; Arglist Enrichment - -(defun arglist-keywords (lambda-list) - "Return the list of keywords in ARGLIST. -As a secondary value, return whether &allow-other-keys appears." - (let ((decoded-arglist (decode-arglist lambda-list))) - (values (arglist.keyword-args decoded-arglist) - (arglist.allow-other-keys-p decoded-arglist)))) - - -(defun methods-keywords (methods) - "Collect all keywords in the arglists of METHODS. -As a secondary value, return whether &allow-other-keys appears somewhere." - (let ((keywords '()) - (allow-other-keys nil)) - (dolist (method methods) - (multiple-value-bind (kw aok) - (arglist-keywords - (slynk-mop:method-lambda-list method)) - (setq keywords (remove-duplicates (append keywords kw) - :key #'keyword-arg.keyword) - allow-other-keys (or allow-other-keys aok)))) - (values keywords allow-other-keys))) - -(defun generic-function-keywords (generic-function) - "Collect all keywords in the methods of GENERIC-FUNCTION. -As a secondary value, return whether &allow-other-keys appears somewhere." - (methods-keywords - (slynk-mop:generic-function-methods generic-function))) - -(defun applicable-methods-keywords (generic-function arguments) - "Collect all keywords in the methods of GENERIC-FUNCTION that are -applicable for argument of CLASSES. As a secondary value, return -whether &allow-other-keys appears somewhere." - (methods-keywords - (multiple-value-bind (amuc okp) - (slynk-mop:compute-applicable-methods-using-classes - generic-function (mapcar #'class-of arguments)) - (if okp - amuc - (compute-applicable-methods generic-function arguments))))) - -(defgeneric extra-keywords (operator args) - (:documentation "Return a list of extra keywords of OPERATOR (a -symbol) when applied to the (unevaluated) ARGS. -As a secondary value, return whether other keys are allowed. -As a tertiary value, return the initial sublist of ARGS that was needed -to determine the extra keywords.")) - -;;; We make sure that symbol-from-KEYWORD-using keywords come before -;;; symbol-from-arbitrary-package-using keywords. And we sort the -;;; latter according to how their home-packages relate to *PACKAGE*. -;;; -;;; Rationale is to show those key parameters first which make most -;;; sense in the current context. And in particular: to put -;;; implementation-internal stuff last. -;;; -;;; This matters tremendeously on Allegro in combination with -;;; AllegroCache as that does some evil tinkering with initargs, -;;; obfuscating the arglist of MAKE-INSTANCE. -;;; - -(defmethod extra-keywords :around (op args) - (declare (ignorable op args)) - (multiple-value-bind (keywords aok enrichments) (call-next-method) - (values (sort-extra-keywords keywords) aok enrichments))) - -(defun make-package-comparator (reference-packages) - "Returns a two-argument test function which compares packages -according to their used-by relation with REFERENCE-PACKAGES. Packages -will be sorted first which appear first in the PACKAGE-USE-LIST of the -reference packages." - (let ((package-use-table (make-hash-table :test 'eq))) - ;; Walk the package dependency graph breadth-fist, and fill - ;; PACKAGE-USE-TABLE accordingly. - (loop with queue = (copy-list reference-packages) - with bfn = 0 ; Breadth-First Number - for p = (pop queue) - unless (gethash p package-use-table) - do (setf (gethash p package-use-table) (shiftf bfn (1+ bfn))) - and do (setf queue (nconc queue (copy-list (package-use-list p)))) - while queue) - #'(lambda (p1 p2) - (let ((bfn1 (gethash p1 package-use-table)) - (bfn2 (gethash p2 package-use-table))) - (cond ((and bfn1 bfn2) (<= bfn1 bfn2)) - (bfn1 bfn1) - (bfn2 nil) ; p2 is used, p1 not - (t (string<= (package-name p1) (package-name p2)))))))) - -(defun sort-extra-keywords (kwds) - (stable-sort kwds (make-package-comparator (list +keyword-package+ *package*)) - :key (compose #'symbol-package #'keyword-arg.keyword))) - -(defun keywords-of-operator (operator) - "Return a list of KEYWORD-ARGs that OPERATOR accepts. -This function is useful for writing EXTRA-KEYWORDS methods for -user-defined functions which are declared &ALLOW-OTHER-KEYS and which -forward keywords to OPERATOR." - (with-available-arglist (arglist) (arglist-from-form (ensure-list operator)) - (values (arglist.keyword-args arglist) - (arglist.allow-other-keys-p arglist)))) - -(defmethod extra-keywords (operator args) - ;; default method - (declare (ignore args)) - (let ((symbol-function (symbol-function operator))) - (if (typep symbol-function 'generic-function) - (generic-function-keywords symbol-function) - nil))) - -(defun class-from-class-name-form (class-name-form) - (when (and (listp class-name-form) - (= (length class-name-form) 2) - (eq (car class-name-form) 'quote)) - (let* ((class-name (cadr class-name-form)) - (class (find-class class-name nil))) - (when (and class - (not (slynk-mop:class-finalized-p class))) - ;; Try to finalize the class, which can fail if - ;; superclasses are not defined yet - (ignore-errors (slynk-mop:finalize-inheritance class))) - class))) - -(defun extra-keywords/slots (class) - (multiple-value-bind (slots allow-other-keys-p) - (if (slynk-mop:class-finalized-p class) - (values (slynk-mop:class-slots class) nil) - (values (slynk-mop:class-direct-slots class) t)) - (let ((slot-init-keywords - (loop for slot in slots append - (mapcar (lambda (initarg) - (make-keyword-arg - initarg - (slynk-mop:slot-definition-name slot) - (and (slynk-mop:slot-definition-initfunction slot) - (slynk-mop:slot-definition-initform slot)))) - (slynk-mop:slot-definition-initargs slot))))) - (values slot-init-keywords allow-other-keys-p)))) - -(defun extra-keywords/make-instance (operator args) - (declare (ignore operator)) - (unless (null args) - (let* ((class-name-form (car args)) - (class (class-from-class-name-form class-name-form))) - (when class - (multiple-value-bind (slot-init-keywords class-aokp) - (extra-keywords/slots class) - (multiple-value-bind (allocate-instance-keywords ai-aokp) - (applicable-methods-keywords - #'allocate-instance (list class)) - (multiple-value-bind (initialize-instance-keywords ii-aokp) - (ignore-errors - (applicable-methods-keywords - #'initialize-instance - (list (slynk-mop:class-prototype class)))) - (multiple-value-bind (shared-initialize-keywords si-aokp) - (ignore-errors - (applicable-methods-keywords - #'shared-initialize - (list (slynk-mop:class-prototype class) t))) - (values (append slot-init-keywords - allocate-instance-keywords - initialize-instance-keywords - shared-initialize-keywords) - (or class-aokp ai-aokp ii-aokp si-aokp) - (list class-name-form)))))))))) - -(defun extra-keywords/change-class (operator args) - (declare (ignore operator)) - (unless (null args) - (let* ((class-name-form (car args)) - (class (class-from-class-name-form class-name-form))) - (when class - (multiple-value-bind (slot-init-keywords class-aokp) - (extra-keywords/slots class) - (declare (ignore class-aokp)) - (multiple-value-bind (shared-initialize-keywords si-aokp) - (ignore-errors - (applicable-methods-keywords - #'shared-initialize - (list (slynk-mop:class-prototype class) t))) - ;; FIXME: much as it would be nice to include the - ;; applicable keywords from - ;; UPDATE-INSTANCE-FOR-DIFFERENT-CLASS, I don't really see - ;; how to do it: so we punt, always declaring - ;; &ALLOW-OTHER-KEYS. - (declare (ignore si-aokp)) - (values (append slot-init-keywords shared-initialize-keywords) - t - (list class-name-form)))))))) - -(defmethod extra-keywords ((operator (eql 'make-instance)) - args) - (multiple-value-or (extra-keywords/make-instance operator args) - (call-next-method))) - -(defmethod extra-keywords ((operator (eql 'make-condition)) - args) - (multiple-value-or (extra-keywords/make-instance operator args) - (call-next-method))) - -(defmethod extra-keywords ((operator (eql 'error)) - args) - (multiple-value-or (extra-keywords/make-instance operator args) - (call-next-method))) - -(defmethod extra-keywords ((operator (eql 'signal)) - args) - (multiple-value-or (extra-keywords/make-instance operator args) - (call-next-method))) - -(defmethod extra-keywords ((operator (eql 'warn)) - args) - (multiple-value-or (extra-keywords/make-instance operator args) - (call-next-method))) - -(defmethod extra-keywords ((operator (eql 'cerror)) - args) - (multiple-value-bind (keywords aok determiners) - (extra-keywords/make-instance operator (cdr args)) - (if keywords - (values keywords aok - (cons (car args) determiners)) - (call-next-method)))) - -(defmethod extra-keywords ((operator (eql 'change-class)) - args) - (multiple-value-bind (keywords aok determiners) - (extra-keywords/change-class operator (cdr args)) - (if keywords - (values keywords aok - (cons (car args) determiners)) - (call-next-method)))) - -(defmethod extra-keywords ((operator symbol) args) - (declare (ignore args)) - (multiple-value-or - (let ((extra-keyword-arglist (get operator :slynk-extra-keywords))) - (when extra-keyword-arglist - (values (loop for (sym default) in extra-keyword-arglist - for keyword = (intern (symbol-name sym) :keyword) - collect (make-keyword-arg keyword - keyword - default)) - (get operator :slynk-allow-other-keywords) - nil))) - (call-next-method))) - - -(defun enrich-decoded-arglist-with-keywords (decoded-arglist keywords - allow-other-keys-p) - "Modify DECODED-ARGLIST using KEYWORDS and ALLOW-OTHER-KEYS-P." - (when keywords - (setf (arglist.key-p decoded-arglist) t) - (setf (arglist.keyword-args decoded-arglist) - (remove-duplicates - (append (arglist.keyword-args decoded-arglist) - keywords) - :key #'keyword-arg.keyword))) - (setf (arglist.allow-other-keys-p decoded-arglist) - (or (arglist.allow-other-keys-p decoded-arglist) - allow-other-keys-p))) - -(defun enrich-decoded-arglist-with-extra-keywords (decoded-arglist form) - "Determine extra keywords from the function call FORM, and modify -DECODED-ARGLIST to include them. As a secondary return value, return -the initial sublist of ARGS that was needed to determine the extra -keywords. As a tertiary return value, return whether any enrichment -was done." - (multiple-value-bind (extra-keywords extra-aok determining-args) - (extra-keywords (car form) (cdr form)) - ;; enrich the list of keywords with the extra keywords - (enrich-decoded-arglist-with-keywords decoded-arglist - extra-keywords extra-aok) - (values decoded-arglist - determining-args - (or extra-keywords extra-aok)))) - -(defgeneric compute-enriched-decoded-arglist (operator-form argument-forms) - (:documentation - "Return three values: DECODED-ARGLIST, DETERMINING-ARGS, and -ANY-ENRICHMENT, just like enrich-decoded-arglist-with-extra-keywords. -If the arglist is not available, return :NOT-AVAILABLE.")) - -(defmethod compute-enriched-decoded-arglist (operator-form argument-forms) - (with-available-arglist (decoded-arglist) - (decode-arglist (arglist operator-form)) - (enrich-decoded-arglist-with-extra-keywords decoded-arglist - (cons operator-form - argument-forms)))) - -(defmethod compute-enriched-decoded-arglist - ((operator-form (eql 'with-open-file)) argument-forms) - (declare (ignore argument-forms)) - (multiple-value-bind (decoded-arglist determining-args) - (call-next-method) - (let ((first-arg (first (arglist.required-args decoded-arglist))) - (open-arglist (compute-enriched-decoded-arglist 'open nil))) - (when (and (arglist-p first-arg) (arglist-p open-arglist)) - (enrich-decoded-arglist-with-keywords - first-arg - (arglist.keyword-args open-arglist) - nil))) - (values decoded-arglist determining-args t))) - -(defmethod compute-enriched-decoded-arglist ((operator-form (eql 'apply)) - argument-forms) - (let ((function-name-form (car argument-forms))) - (when (and (listp function-name-form) - (length= function-name-form 2) - (memq (car function-name-form) '(quote function))) - (let ((function-name (cadr function-name-form))) - (when (valid-operator-symbol-p function-name) - (let ((function-arglist - (compute-enriched-decoded-arglist function-name - (cdr argument-forms)))) - (return-from compute-enriched-decoded-arglist - (values - (make-arglist :required-args - (list 'function) - :optional-args - (append - (mapcar #'(lambda (arg) - (make-optional-arg arg nil)) - (arglist.required-args function-arglist)) - (arglist.optional-args function-arglist)) - :key-p - (arglist.key-p function-arglist) - :keyword-args - (arglist.keyword-args function-arglist) - :rest - 'args - :allow-other-keys-p - (arglist.allow-other-keys-p function-arglist)) - (list function-name-form) - t))))))) - (call-next-method)) - -(defmethod compute-enriched-decoded-arglist - ((operator-form (eql 'multiple-value-call)) argument-forms) - (compute-enriched-decoded-arglist 'apply argument-forms)) - -(defun delete-given-args (decoded-arglist args) - "Delete given ARGS from DECODED-ARGLIST." - (macrolet ((pop-or-return (list) - `(if (null ,list) - (return-from do-decoded-arglist) - (pop ,list)))) - (do-decoded-arglist decoded-arglist - (&provided () - (assert (eq (pop-or-return args) - (pop (arglist.provided-args decoded-arglist))))) - (&required () - (pop-or-return args) - (pop (arglist.required-args decoded-arglist))) - (&optional () - (pop-or-return args) - (pop (arglist.optional-args decoded-arglist))) - (&key (keyword) - ;; N.b. we consider a keyword to be given only when the keyword - ;; _and_ a value has been given for it. - (loop for (key value) on args by #'cddr - when (and (eq keyword key) value) - do (setf (arglist.keyword-args decoded-arglist) - (remove keyword (arglist.keyword-args decoded-arglist) - :key #'keyword-arg.keyword)))))) - decoded-arglist) - -(defun remove-given-args (decoded-arglist args) - ;; FIXME: We actually needa deep copy here. - (delete-given-args (copy-arglist decoded-arglist) args)) - -;;;; Arglist Retrieval - -(defun arglist-from-form (form) - (if (null form) - :not-available - (arglist-dispatch (car form) (cdr form)))) - -(eval-when (:compile-toplevel :load-toplevel :execute) - (export 'arglist-dispatch)) -(defgeneric arglist-dispatch (operator arguments) - ;; Default method - (:method (operator arguments) - (unless (and (symbolp operator) (valid-operator-symbol-p operator)) - (return-from arglist-dispatch :not-available)) - (when (equalp (package-name (symbol-package operator)) "closer-mop") - (let ((standard-symbol (or (find-symbol (symbol-name operator) :cl) - (find-symbol (symbol-name operator) :slynk-mop)))) - (when standard-symbol - (return-from arglist-dispatch - (arglist-dispatch standard-symbol arguments))))) - - (multiple-value-bind (decoded-arglist determining-args) - (compute-enriched-decoded-arglist operator arguments) - (with-available-arglist (arglist) decoded-arglist - ;; replace some formal args by determining actual args - (setf arglist (delete-given-args arglist determining-args)) - (setf (arglist.provided-args arglist) determining-args) - arglist)))) - -(defmethod arglist-dispatch ((o