Magnus web site
Random stuff
Emacs salmagundi, 2026-08-13
Here's a few minor tweaks I've made to my Emacs setup recently.
Limiting enabled tree-sitter modes
After reading Rahul's post Emacs 31 Is Around the Corner: The Changes I'm Already Daily Driving I simplified my setup and turned on all the tree-sitter modes
(treesit-enabled-modes t)
I regretted that as soon as I opened a YAML file though. It turns out the
yaml-ts-mode is rather unusable.1 I switched back to yaml-mode and
adjusted the value of treesit-enabled-modes to include what I have use for
(the change):
(treesit-enabled-modes '(bash-ts-mode
c++-ts-mode
c-or-c++-ts-mode
c-ts-mode
dockerfile-ts-mode
go-mod-ts-mode
go-ts-mode
go-work-ts-mode
json-ts-mode
python-ts-mode
rust-ts-mode
typescript-ts-mode))
The customization of treesit-enabled-modes contains a list of all the
supported modes.
I hope someone will take pity on yaml-ts-mode and make it usable soon.
Tree-sitter grammar for Cabal files
When I began tinkering with my own treesitter-based mode for Haskell I failed to find a tree-sitter grammar for Cabal files so I wrote a rather rough one myself. My hope was that it could serve as a base, or motivation, for someone else to write a proper one. It took more than 2 years, but now it's finally happened!
I've switched my mode to use Curtis's grammar and I'll be archiving mine. (The change.)
Hello editable xref, good bye wgrep
Rahul's post also mentions that editable xref buffers. I had the same itch to
have editable xref buffers, and I'd added a function to turn them into grep
buffers to be able to edit them. I've now switched to using the built-in
support, and removed the function.
At the same time I also decided to remove wgrep and instead use the builtin
function for editing grep buffers.
Footnotes:
A mode for an indentation-sensitive language really ought to at least handle indentation!
Jumping to errors in Evil
Recently I realised that it'd be really nice if jumping to errors would store the previous location in the Evil jump list. These definitions do just that
(evil-define-motion mes/evil-goto-next-error (count)
:jump t
(unless (bound-and-true-p flymake-mode) (signal 'search-failed nil))
(flymake-goto-next-error count))
(evil-define-motion mes/evil-goto-prev-error (count)
:jump t
(unless (bound-and-true-p flymake-mode) (signal 'search-failed nil))
(flymake-goto-prev-error count))
and for now I've bound them to C-j and C-k (because that's what
evil-collection does)
(general-def flymake-mode-map
:states 'normal
"C-j" 'mes/evil-goto-next-error
"C-k" 'mes/evil-goto-prev-error)
This makes it easier to make a change, fix the errors caused by the change and then return to where I was.
Follow-up on switching to eglot
Jan G sent me a two-part comment.
Part one
I was under the impression that when using elpaca you needed to disable use-package, and that when using elpaca-use-package, you were redefining the macro. Iβm not 100% sure about this, but the documentation has an example of use-package and how it actually expands to an elpaca command.
I wouldn't know. All I can say is that it would be nice if package managers that
hook into, or completely redefines use-package, would document if they deviate
from the behaviour of "vanilla use-package" in some way.
Part two
Given that, use-packageβs documentation is always going to be a little off, since elpaca is doing everything async. The only way Iβve found to reliably manage some dependencies is to use the elpaca-after-init hook, so they donβt even try to run until elpaca is finished loading everything.
I'd say it sometimes seems like the documentation for use-package is a little
off for use-package itself π
The README for Elpaca says that
Add configuration which relies on after-init-hook, emacs-startup-hook, etc to elpaca-after-init-hook so it runs after Elpaca has activated all queued packages.
but that seems like a very big hammer and as I understand it I'd have to move
the whole :init block for python-mode into the hook in that case. Playing
around with the various blocks for use-package isn't too time consuming and I
think it's a good first thing to try.
Secrets when connecting to DBs
I should have dealt with comments I got to my posts on how I deal with secrets in my work notes, here, and here. Better late than never though, I hope.
Comment from Stefano R
The first one is a link to post titled How I use :dbconnection in org files. It
describes a nice way of setting sql-connection-alist based on the contents of
a file, in his case ~/.pgppass.
Comment from Harald J
The other starts with a function for searching ~/.authinfo.gpg for entries of
the form
machine <host>/<dbname> login <username> password <password> port <port>
and then setting sql-password-search-wallet-function and sql-password-wallet
to tell sql-mode to use it
(defun my/sql-auth-source-search-wallet (wallet product user server database port)
"Read auth source WALLET to locate the USER secret.
Sets `auth-sources' to WALLET and uses `auth-source-search' to locate the entry.
The DATABASE and SERVER are concatenated with a slash between them as the
host key."
(when-let (results (auth-source-search :host (concat server "/" database)
:user user
:port (number-to-string port)))
(when (and (= (length results) 1)
(plist-member (car results) :secret))
(plist-get (car results) :secret))))
(setq sql-password-search-wallet-function #'my/sql-auth-source-search-wallet)
(setq sql-password-wallet "~/.authinfo.gpg")
The value for sql-connection-alist is then as normal
(setq sql-connection-alist
'((some-dbname (sql-product 'oracle)
(sql-port 1521)
(sql-server ...)
...))
and the blocks in orgmode looks like this
SRC sql-mode :product oracle :dbconnection i3v1e-ro :results raw
SELECT to_char(sysdate, 'YYYY-MM-DD HH24:ii:ss') AS today,
to_char(sysdate + 1, 'YYYY-MM-DD HH24:ii:ss') AS tomorrow
FROM dual;
SRC
Thoughts
Both of these feel closer to the intent of sql-mode in a way. I'll have to try
using sql-connection-alist at some point.
Switching to project.el
I've used projectile ever since I created my own Emacs config. I have a vague memory choosing it because some other package only supported it. (It might have been lsp-mode, but I'm not sure.) Anyway, now that I'm trying out eglot, again, I thought I might as well see if I can switch to project.el, which is included in Emacs nowadays.
A non-VC project marker
Projectile allows using a file, .projectile, in the root of a project. This
makes it possible to turn a folder into a project without having to use version
control. It's possible to configure project.el to respect more VC markers than
what's built-in. This can be used to define a non-VC marker.
(setopt project-vc-extra-root-markers '(".projectile" ".git"))
Since I've set vc-handled-backends to nil (the default made VC interfere
with magit, so I turned it off completely) I had to add ".git" to make git
repos be recognised as projects too.
Xref history
The first thing to solve was that the xref stack wasn't per project. Somewhat
disappointingly there only seems to be two options for xref-history-storage
shipped with Emacs
xref-global-history- a single global history (the default)
xref-window-local-history- a history per window
I had the same issue with projectile, and ended up writing my own package for it. For project.el I settled on using xref-project-history.
(use-package xref-project-history
:ensure (:type git
:repo "https://codeberg.org/imarko/xref-project-history.git"
:branch "master")
:custom
(xref-history-storage #'xref-project-history))
Jumping between implementation and test
Projectile has a function for jumping between implementation and test. Not too
surprisingly it's called projectile-toggle-between-implementation-and-test. I
found some old emails in an archive suggesting that project.el might have had
something similar in the past, but if that's the case it's been removed by now.
When searching for a package I came across this email comparing tools for
finding related files. The author mentions two that are included with Emacs
ff-find-other-file- part of find-file.el, which a few other functions and a rather impressive set of settings to customise its behaviour.
find-sibling-file- a newer command, I believe, that also can be customised.
So, there are options, but neither of them are made to work nicely with project.el out of the box. My most complicated use case seems to be in Haskell projects where modules for implementation and test live in separate (mirrored) folder hierarchies, e.g.
src
βββ Sider
βββ Data
βββ Command.hs
βββ Pipeline.hs
βββ Resp.hs
test
βββ Sider
βββ Data
βββ CommandSpec.hs
βββ PipelineSpec.hs
βββ RespSpec.hs
I'm not really sure how I'd configure find-sibling-rules, which are regular
expressions, to deal with folder hierarchies like this. To be honest, I didn't
really see a way of configuring ff-find-other-file at first either. Then I
happened on a post about switching between a module and its tests in Python.
With its help I came up with the following
(defun mes/setup-hs-ff ()
(when-let* ((proj-root (project-root (project-current)))
(rel-proj-root (-some--> (buffer-file-name)
(file-name-directory it)
(f-relative proj-root it)))
(sub-tree (car (f-split (f-relative (buffer-file-name) proj-root))))
(search-dirs (--> '("src" "test")
(remove sub-tree it)
(-map (lambda (p) (f-join proj-root p)) it)
(-select #'f-directory? it)
(-mapcat (lambda (p) (f-directories p nil t)) it)
(-map (lambda (p) (f-relative p proj-root)) it)
(-map (lambda (p) (f-join rel-proj-root p)) it))))
(setq-local ff-search-directories search-dirs
ff-other-file-alist '(("Spec\\.hs$" (".hs"))
("\\.hs$" ("Spec.hs"))))))
A few things to note
- The order of rules in
ff-other-file-alistis important, the first match is chosen. (buffer-file-name)can, and really does, returnnilat times, andfile-name-directorydoesn't deal with anything but strings.- The entries in
ff-search-directorieshave to be relative to the file in the current buffer, hence the rather involvedvarlistin thewhen-let*expression.
With this in place I get the following values for ff-search-directories
src/Sider/Data/Command.hs("../../../test/Sider" "../../../test/Sider/Data")test/Sider/Data/CommandSpec.hs("../../../src/Sider" "../../../src/Sider/Data")
And ff-find-other-file works beautifully.
Conclusion
My setup with project.el now covers everything I used from projectile so I'm fairly confident I'll be happy keeping it.