My blog posts here usually have a line like:
tags: [ swift, xcode, codesigning ]
For tags I don’t use a lot, I sometimes don’t remember how to write them. So I do the only sane thing – and go to my website’s list of tags and look for a match.
Got annoyed by that now after a couple of years :)
Extract All Tags from All Posts
rg, I can match these lines and focus on group matches inside without piping the output through
sed or similar.
The regular expression that works good enough for me:
The first group here then returns
" swift, xcode, codesigning " for the example above with the right ripgrep incantation:
$ rg --context 0 \ --no-filename \ --no-heading \ --replace "\$1" \ -- <<regex here>>
This produces just the string inside the
tags: [...] brackets, without filename, and no empty lines in between.
Here’s the Emacs Lisp version to get these lines:
(defun ct/all-tag-lines () "Extract the array contents of YAML lines for `tags: [...]'." (let ((project-dir (cdr (project-current))) (regex "^tags:\\s\\[\\s*([a-zA-Z0-9 -_]+)\\s*\\]")) (shell-command-to-string (concat "rg --context 0 --no-filename --no-heading --replace \"\\$1\" -- " (shell-quote-argument regex) " " project-dir))))
Now time to clean this up and make this usable.
Transform the Extracted YAML Lines Into Filterable Lists in Emacs
Samples output from
rg is e.g.:
zettelkasten, reading, archive calendarpasteapp zettelkasten, writing, personal, craft nv, zettelkasten, software, review writing, productivity, quantified-self
I need to split the lines into individual tags and then remove duplicates like
Split string by lines, trimming whitespace:
(split-string "..." "\n" nil " ")
Split lines by comma and/or spaces to extract individual tags, dropping empty strings:
(split-string "..." "[, ]+" t " ")
(split-string "..." "[, \n]+" t " \n")
To delete duplicates,
delete-dups does the trick:
(defun ct/all-tags () "Return a list of unique tags across all articles." (delete-dups (split-string (ct/all-tag-lines) "[, \n]+" t " \n")))
This returns an (unsorted) list of unique tag strings.
zettelkasten reading archive calendarpasteapp writing personal craft nv software review productivity quantified-self
With that, I’m almost finished. I can pass this to
completing-read to get – well, the name reveals almost as much: – interactive completion for matches in this selection.
And I ultimately want to insert the match, not just produce a result programmatically.
So this is the “public”, i.e. user facing function I’m using:
(defun ct/insert-project-tag () "Select and insert a tag from YAML frontmatter tags in the project." (interactive) (insert (completing-read "Tag: " (ct/all-tags))))
Since I’m using the built-in
project.el package, I added a key binding to C-x p t (am actually using SPC p t in command mode) to insert a tag:
(define-key project-prefix-map (kbd "t") #'ct/insert-project-tag)
Up next, I’d maybe like to push this completion from a selection in the mode-line to
completion-at-point, i.e. to get suggestions and auto-completion in-place while I type.
Judging by the speed I implemented these things in the past, it should be ready by 2026.