▶ YouTube Summaries

← All summaries

Denote query links: rejecting regexps with doubled backslashes

2026-10-31 Sat ⏱ 1 hr 51 min protesilaos

A spontaneous live stream in which Prot handles a Denote edge case from issue 733. A user copied a regexp from re-builder, which uses Lisp string syntax with doubled backslashes, into a query link prompt, and it matched nothing. Every Emacs minibuffer reader escapes backslashes again, so \\( typed at a prompt becomes four backslashes internally. Prot explores a silent "do what I mean" fix that strips the extra backslashes, rejects it as too clever and a source of inconsistency, and instead adds a predicate that signals an error. He wires it into all query-link commands, writes ERT tests, tests interactively and commits. He then checks a suspected bug in an autoloaded Denote global minor mode, which turns out to be fine. Chat covers rx, SQL tooling, Guix/Nix, Kakoune and meow, Emacs versus the Unix philosophy, PipeWire audio, OpenBSD and his Doric themes.

Query links recap

  • Query links point to a search, not a file. denote-query-filenames-link opens a real Dired buffer of files whose names match the regexp. denote-query-contents-link opens a standard Xref buffer of grep matches. Both differ from a direct denote-link to one file.
  • The link stores the denote: type, a query specifier, the query and a description.
  • The prompt comes from a macro that uses completing-read over history or read-string.
  • With Vertico, M-RET submits the literal input instead of the selected candidate. Forgetting this caused a few false alarms during the stream.

The backslash problem

  • re-builder expects Lisp syntax, so one regexp escape is written as two backslashes. Interactive prompts expect a single backslash and escape it internally.
  • Pasting re-builder output into a prompt therefore produces a broken regexp with doubled escapes.
  • Prot tries read-string, read-regexp, read-from-minibuffer, read-minibuffer and completing-read. All behave the same, so no built-in "read it literally" option exists. A chat suggestion that format was to blame does not matter, since he will not advise something this fundamental.

Rejected approach: silently fix the input

  • replace-regexp-in-string can strip the extra backslashes, but the regexp needed to match backslashes is "borderline silly" and raises invalid-replacement errors.
  • A neat wrapper (denote-prompt-with-args: apply the reader, then thread-last the result through a cleanup) works for both read-string and completing-read.
  • He drops it because packages built on Denote that call the plain readers would behave differently, and that inconsistency is asking for trouble. consult-denote turns out not to use them, but third parties might. He prefers to teach the user with an error rather than be "too smart".

Detecting extra backslashes

  • Failed attempts: string-match-p, string-search and split-string on a single backslash all fight the escaping rules. Character classes do not help either.
  • seq-count with (eq c ?\\) (char 92) counts literal backslashes correctly, but only for trivial strings.
  • Working approach: split-string at the regexp constructs (parens, pipe) with omit-nulls, then seq-some over the pieces to find one with two backslashes before a construct.
  • New functions:
  • denote-extra-backslashes-p: its docstring explains that the regexp is valid in Lisp calls but escaped again by read-string and friends.
  • denote-extra-backslashes-with-error: returns the string or signals "String has extra backslashes".
  • Called in the interactive spec of denote-query-contents-link, denote-query-filenames-link, denote-link-to-file-with-contents and denote-link-to-all-files-with-contents (plus its alias).
  • Chat asked whether valid escapes like a\nb are affected. They pass through untouched. A doubled backslash before n also passes, because it legitimately means a literal backslash-n.
  • On Windows-style \\server paths, Emacs uses forward slashes internally anyway. He jokes that the real fix is a link to a Linux distro.

Tests and commit

  • ert-deftest cases cover the predicate with several should and should-not permutations. A keyboard macro builds the should-error cases for the erroring variant.
  • A linter warning insisted the error message start with a capital letter. He complied, puzzled.
  • Interactive checks of all four commands show the error on doubled input and correct links on single-backslash input.
  • Commit: "Error on query link input with extra backslashes", referencing issue 733 and the stream. He may extend it to all prompts later.
  • Aside: he avoids paredit because he often leaves parens open while editing elsewhere.

Autoloaded minor mode check

  • An autoloaded Denote global minor mode can run before denote is required. It uses setq to store the original value of a variable when enabled.
  • He suspected the stored value was wrong. After restarting Emacs and trying a plain call and with-eval-after-load, the value is correct.
  • Capturing it at enable time rather than in the defvar is deliberate, so the user's customised value is kept rather than the package default.

Chat topics

  • He never learned rx: regexp flavours are messy enough without another one.
  • SQL users leaving PyCharm/DataGrip: no need to switch if it works. Adopt Emacs piecemeal for Org, other languages and mail, and keep the old tool for that one job. He knows no specific replacement.
  • Denote bugs: GitHub issues are fine and visible to others; private email works too.
  • Audio static: a few-line PipeWire config in his stow-managed dotfiles (committed on the 28th, Debian 13 specific) appears to have fixed it.
  • Guix/Nix: impressive and robust. guix shell gives disposable dev environments, and reproducibility pays off across many machines. As a single-machine Elisp coder he has no use case, but would pick Guix (Lisp, GNU, GPL). FHS non-compliance means doing things "the Guix way".
  • Kakoune and meow: good editors in the Unix spirit. Emacs is not better at text editing as such. Its strength is one uniform Elisp environment, so a function works across mail, RSS, agenda and code. Making Vim, tmux, Neomutt and Newsboat share a theme script taught him how hard cross-tool consistency is.
  • OpenBSD tempts him, but he keeps postponing it.
  • Theme on screen: Doric Lilac from doric-themes, his minimalist few-colour set. He also uses spacious-padding but disables it on stream for clearer window borders.
  • Light banter: Greek coffee versus Vim users and Brazilian coffee, Balkan hot-headedness, and a Lithuanian ex-roommate.