Skip to content

Latest commit

 

History

History
955 lines (763 loc) · 41.2 KB

README.org

File metadata and controls

955 lines (763 loc) · 41.2 KB

denote-search: a simple search utility for Denote

This manual, written by Lucas Quintana, describes the customization options for the Emacs package called denote-search (or denote-search.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}}}.

1 COPYING

Copyright (C) 2024-2025 Lucas Quintana

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.”

2 Overview

This package provides a simple search utility for the excellent denote program, the simple-to-use, focused-in-scope, and effective note-taking tool for Emacs (see info:denote#Top).

denote-search allows you to search for a regular expression in the contents of your notes. Once the results are populated, you are presented with a buffer from which you can refine the search (that is, search in the matched files), jump to a certain file using imenu, exclude certain files from the search, or search only certain files. All commands provided by Xref are available as well (see info:emacs#Xref).

denote-search approach has several advantages over similar tools (consult-grep, consult-notes, grep, and so on):

  • It allows to search in the files matched by a previous search, which as far as I know is not possible with other similar packages.
  • It allows to search in the files referenced in a region, with the command denote-search-files-referenced-in-region. That is great for metanotes, shell outputs of ls, and more.
  • It also allows to search in marked Dired files, with the command denote-search-marked-dired-files.
  • It doesn’t rely on the minibuffer to output results, and it thus doesn’t need a completion stack in order to work (helm, vertico+consult, etc.).
  • In fact, denote-search doesn’t rely on any external package other than Denote.
  • Unlike grep, it allows excluding (or including only) certain files on-demand, without command-line gimmicks.
  • It uses pretty file titles by default (and can be customized to show keywords or basically any other information as well).

Furthermore, denote-search has a small and simple codebase. It is designed to do one thing and one thing only: to search in your notes.

3 Motivation

I wrote denote-search because I needed a simple tool to search my personal knowledge database, which of course is managed with Denote. Available options, such as consult-notes, almost always used the minibuffer to display results. Which is fine, it works, but it is sort of annoying on small screens. In fact, I hacked the first version of denote-search on Emacs for Android, in a time where I didn’t have a laptop with me. Don’t take those things as granted, trust me.

I was mainly inspired by Howm[fn:1], a really great note-taking tool, also made for Emacs. It has a life-changing approach to notes: it doesn’t matter where do you store your information as long as you can retrieve it later. “Write fragmentarily and read collectively”, that is. Howm has thus powerful search facilities built-in, some of which (filtering and excluding/including files) I reimplemented in denote-search. I’m very happy now ^_^

4 Points of entry

The main point of entry of this package is the denote-search command. This command will prompt for a string. You should input a valid regular expression, as understood by the tool which will actually perform the search. Which program that is depends on the value of xref-search-program, and its arguments are taken from xref-search-program-alist. Note that denote-search is really just a wrapper for Xref, albeit an useful one, so anything related to the actual search results is the matter (for bad or for good) of that library and/or the program it calls.

Once the (synchronous) search is over, a new buffer populated with the results, if any, will be made current. On that buffer, the user will be able to perform several potentially useful actions, including filtering the output (see filtering the search results) and searching in the matched files (see focused search).

History is available when searching. Press M-p (previous-history-element) to view past queries.

There are two additional commands that can start a search: denote-search-marked-dired-files and denote-search-files-referenced-in-region. They allow searching a restricted subset of files and are described in the next sections.

4.1 Searching in marked Dired files

The command denote-search-marked-dired-files acts just like denote-search, but it restricts the search to the files marked in current Dired buffer (see Marks vs Flags). This is useful if you only want to search some files, though depending on the case maybe you’d be better served by our built-in filtering capabilities; see filtering the search results.

This works well in tandem with the Denote command denote-sort-dired, which produces a Dired buffer with files matching a regexp. So, generating that buffer and then pressing t (dired-toggle-marks) will enable you to use denote-search-marked-dired-files to search on those files. Again, using the filtering functionality available for the results buffer should suit you better, but you have options.

4.2 Searching in files referenced in a region

The command denote-search-files-referenced-in-region may seem odd at first, but it’s probably the most useful one. It allows you to search in a set of notes referenced in a buffer. What does that means? Well, it means that any buffer can serve as the source for the set of files to search for; you just need Denote IDs written somewhere, and the command will recognize them as files and search in them.

But let’s look at an example. Probably, you already have a note with a section that resembles this:

* See also

- An amazing note
- Another amazing note
- Yet another amazing note

Those notes are links and are highlighted as such, so internally they look like this:

* See also

- [[denote:20231205T202124][An amazing note]]
- [[denote:20230720T154224][Another amazing note]]
- [[denote:20230719T194132][Yet another amazing note]]

That 20231205T202124 bit is the Denote ID. That’s the only thing our command needs to recognize a note. So, you just need to select the section (with the mouse or C-SPC, whichever you like the most) and call denote-search-files-referenced-in-region. It will prompt for a regexp just like denote-search, but it will only search the files selected.

This is useful for searching in notes linked in Org dynamic blocks (first mark the block with org-babel-mark-block), or for searching something in linked notes in general (first mark the whole buffer with mark-whole-buffer, bound to C-x h).

This works everywhere. If you had notes with the exact same IDs as the ones depicted previously, you could select them in this very same Info buffer (assuming you are reading this in Emacs) and search something in them right away.

As the Denote ID is included in file names, you can also use this command on Dired, on a shell output of ls from async-shell-command, and so on. It’s on those cases where you can grasp how powerful the Denote file-naming scheme is.

5 Navigating the search results

To navigate the results buffer, you can use the standard Xref commands (see Xref Commands). So, for instance, n moves you to the next hit and displays it in another window, and p does the same for the previous one. N moves you to the next file, while P moves you to the previous one.

Aside from that, denote-search by default enables outline-minor-mode in the results buffer, and so additional facilities are available. You can fold file matches with TAB (outline-cycle), in a similar fashion as an Org tree. You can also navigate all the files using imenu or an enhanced front-end such as consult-imenu. consult-outline also works here.

You can press a (outline-cycle-buffer) to fold all files. This can give you a quick overlook of all the files that matched the search. If there are many, you can proceed to filter the output (see filtering the search results).

6 Filtering the search results

On the results buffer, you can press x (denote-search-exclude-files) to exclude certain files from the search. The command will prompt for a regular expression. Once given, the last search query will be re-run, but excluding all the files that match the regular expression given. For instance, you can input _philosophy to exclude all the notes with the “philosophy” keyword. Or you can input -emacs to exclude all the notes that have “emacs” in their title. Or you can input org$ to exclude Org files. And so on.

You can press i (denote-search-only-include-files) for the opposite operation; it will prompt for a regular expression, and then re-run the search, but only on the files matched by the regular expression given.

These commands always act as if the files matched by the last search were all the Denote files in existence. This has a great advantage; you can chain them in any way you want. You can, for example, press i and input _emacs, and then press x and input _philosophy. The resulting buffer will have all the notes which have the keyword “emacs” but not the keyword “philosophy”, and will then display the matches for the search query you made originally only for those specific files. You can of course keep filtering further.

To “break the chain” and start a totally new search, you can simply call denote-search, which is bound to s in the results buffer for convenience.

It’s possible that you don’t want to start a new search, but rather to search something on the curated file list you got. See focused search.

History is available when filtering. Press M-p (previous-history-element) to view past queries. This history is kept separately from that available when searching.

7 Focused search

A “focused search” is a search which is run against a set of files matched by a previous search. There are many use-cases for this, including searching for a note that you know has two or three very specific words, probably on different lines.

On the results buffer, press f (denote-search-refine) to start a focused search. The command will prompt for a regular expression. Once given, it will be searched in the files matched by the last search. The buffer will be properly updated and will show the matches found.

Note that this feature, combined with the filtering capabilities offered by this package (see filtering the search results), can be very powerful.

Let’s look at a complex example, which would be nearly impossible with other tools: search for all the mentions of “quantum mechanics” in notes with the “science” keyword, without the “personal” keyword, and which mention “Maxwell” somewhere in the text. The procedure is as follows:

  • M-x denote-search RET maxwell RET [fn:3]
  • i _science RET
  • x _personal RET
  • f quantum mechanics RET

There it is, a really complex task is done in four straightforward steps. And all without external and platform-specific programs such as xapian!

8 Editing files

denote-search is designed only for finding information in your knowledge database, not for changing it. You can open the files (with RET, n or otherwise) and edit them as usual.

There’s a special editing feature offered by Xref, though. You can press r (xref-query-replace-in-results) to replace the search query (naturally as a regular expression) in the files matched; the interface is similar to that of query-replace-regexp. This works in the files then displayed in the results buffer, so you can filter and fine-grain as usual to come with the replace command you wish. Maybe replace all the occurences of “Vim” with “Emacs” in notes with the keyword “programming” but not in notes with the keyword “personal”, and only in files which mention “GNU”? The sky is the limit!

9 Customization

Although denote-search is designed to be simple and require no special configuration, some options are available.

9.1 Format of headings

By default, denote-search uses the pretty title found in the front-matter to format note headings in the results buffer. This is pleasent to the eye and shouldn’t impact performance (the bottleneck is always the search itself).

You can change how headings are formatted by customizing the denote-search-format-heading-function variable. It must be set to a function which takes a single argument, the file path, and returns the desired string for the heading.

By default, denote-search-extract-title is used, which produces the aforementioned behaviour. If it fails to find a suitable title for a note, it uses the value of denote-search-untitled-string. You can customize that, as well.

This package also offers an alternative function, called denote-search-format-heading-with-keywords. If used as the heading formatter, it adds keywords to the file title. This isn’t the default merely to not clutter the view, but many users may prefer it.

You can write custom functions to display pretty much everything you want in the headings. Just keep in mind that the function is called for every single matched file, so for large collection of notes, it can indeed impact performance if you use complex code.

9.2 Other options

You can customize the name of the buffer where results are put using the variable denote-search-buffer-name.

By default, the results buffer has a header line which displays information about the search and a short help string. Once you know the commands by heart, you can set the variable denote-search-help-string to nil or a void string to disable the help.

10 Working with silos

If silos (see denote#Maintain separate directory silos for notes) are set up correctly (that is, with a .dir-locals.el file that sets a value for denote-directory), then denote-search should correctly search in the contents of the silo when inside of it, without additional configuration.

11 Installation

If you are using Emacs 29.1 onwards, you can install the package by evaluating the following code:

(package-vc-install
 '(denote-search
   :url "https://github.com/lmq-10/denote-search"
   :doc "README.org"))

Alternatively, you can use the :vc keyword from use-package, as shown in the sample configuration.

Of course, you can also install it manually or use an alternative package manager such as quelpa.

11.1 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-search"
git clone https://github.com/lmq-10/denote-search denote-search

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-search")

Everything is in place to set up the package.

12 Sample configuration

(use-package denote-search
  :ensure t
  ;; Installation with VC
  :vc (:url "https://github.com/lmq-10/denote-search"
       :rev :newest)
  :bind
  ;; Customize keybindings to your liking
  (("C-c s s" . denote-search)
   ("C-c s d" . denote-search-marked-dired-files)
   ("C-c s r" . denote-search-files-referenced-in-region))
  :custom
  ;; Disable help string (set it once you learn the commands)
  ;; (denote-search-help-string "")
  ;; Display keywords in results buffer
  (denote-search-format-heading-function #'denote-search-format-heading-with-keywords))

13 Troubleshooting

Fixes for some common issues.

13.1 Search is slow

Search is not managed by denote-search, but rather by xref. Check the value of xref-search-program. Changing it to ripgrep (after installing it of course) can improve the speed.

14 Acknowledgements

denote-search, just like Denote itself, is meant to be a collective effort. Every bit of help matters.

Author/maintainer
Lucas Quintana.
Contributions to code
Grant Rettke.

If denote-search exists it’s because Protesilaos Stavrou developed the incredible Denote package. Please consider donating to him.[fn:2]

I also want to thank Richard Stallman (creator of GNU Emacs), Po Lu (who ported it to Android, allowing me to write the first version of denote-search) and Hiraoka Kazuyuki (author of Howm, from which this package borrows some ideas). This wouldn’t be possible without them, either.

15 GNU Free Documentation License

17 Footnotes

[fn:3] You’ll probably bind denote-search to something comfortable, see Sample configuration

[fn:1] https://kaorahi.github.io/howm/

[fn:2] https://protesilaos.com/donations/