Personalized embark actions

I adopted Embark in 2025. To be honest, it was more because it seemed to be the typical companion for vertico + consul, than because I really understood what I could do with it.

Besides some very basic use (which I routinely forget) I didn’t bother to explore it in more detail.

Until I noticed that Embark could give me a fundamental component for a mechanism I had been trying to implement in the past.

First, some context. For my job, I often have to navigate through a quite large dbt project. If I were less picky about text editors I’d just use VS Code and the PowerUser package. But, it's no mistery, I’m fond of staying inside Emacs. Where, quite incredibly, there is no dbt support package available.

One simple thing I'd particulary like is the ability of hopping from one file to another following dbt references.

A reference, in dbt, is a Jinja expression that can be used in place of a table or view name, to inform dbt of a dependency between two models (a model is, simplifying, a named SQL query, saved in a file with that name).

For example, you can write a file called us_customers.sql like this:

select * from {{ ref('mart_customers') }}
where country = 'US'

and dbt knows that the mart_customers model (contained in a file mart_customers.sql, in some directory of the project) is an upstream dependency of us_customers.

This is useful because then you can invoke dbt like this (notice the + sign):

dbt run -m mart_customers+

and dbt knows that it needs to refresh mart_sustomers PLUS its downstream dependencies, including us_customers.

A typical dbt project may contain hundreds of interdependent models, organized in a tree of directories. So, references are very convenient.

Now to my specific Emacs problem. What I'd like to be able to do is, when the cursor is on a reference, to visit the file that contains the definition of the referenced model, without having to navigate the directories to find it.

The first useful thing that Embark does is figuring out what's the identifier I'm on with the cursor. In the example above, that would be the string mart_customers. In Embark (and Emacs) parlance, that is called the target at point. More precisely, in this case, that's the identifier at point.

When I invoke embark-act (it's customary to map this function to C-.), Embark figures what's the identifier at point, and I get a contextual list of actions that can be applied.

Now, I need to add my personalized action to that list.

For my needs, that is just a function that, given a reference name (the identifier at point) puts .sql at the end of the string, then uses the result as an argument for project-root-find-file. Model files are guaranteed to be unique across all the project (otherwise dbt does not work), so there is no ambiguity about the file I want to visit.

Here the implementation, and how it was integrated in the Embark's identifier target list.

(defun larsen/embark-dbt-navigate-reference (reference &rest args)
  "Opens the model source code for the dbt reference at point"
  (interactive "sReference: ")
  (project-root-find-file (string-join (list reference ".sql"))))

(use-package embark
  :ensure t
  :bind (("C-." . embark-act)
         :map embark-identifier-map
         ("D" . larsen/embark-dbt-navigate-reference)))

Suburban 144

Suburban 144 is a yearly gravel bike event organized by the Bikebash group. I participated to the 2026 edition in the 125km group. It turned out to be 130, to which I added another 4 because I missed a checkpoint and I had to go back. Another group went for a 165km variation, and another even more adventurous group started the day before, riding through the night for a total of 330km.

Events of this kind are conceived as social rides rather than competitions, and I have to say they are quite effective, considering for example that even I, notoriously unable to strike up a conversation with new people, started alone but managed to arrive in a group.

We had great weather and the ride was generally great fun, even if I had problems with the rear wheel; nothing that two plugs could not solve, anyway.

You can find more pictures on the event website.

The route

Search and replace across multiple documents in Emacs

This is not something I do every day, so I end up forgetting it and having to relearn it again. Here's a workflow based on consult‑ripgrep and Embark.

Long weekend in Manchester

Emacs config size

Let’s Play How Big Is Your Emacs Config?

In my case:

wc -l .emacs.d/lisp/larsen-* .emacs.d/init.el | ag total
    2400 total

Also

Other posts