Project-local Eat terminals with display-buffer-alist
:emacs: :eat: Jun 03, 2026Emacs gives you something new every day. A couple of weeks ago I stumbled across tab-bar-mode, which turned out to be a great fit for my workflow: one frame, with one tab per project. The only thing missing was a convenient terminal. I often need one for complex build steps or deployments, so I wanted a quick way to pop up a terminal for the current project and hide it again afterwards.
I found shell-pop, which seemed like a good fit and even works with eat.
Unfortunately, it only manages one terminal at a time. That made it unsuitable
for a tab-per-project workflow. But luckily, this is Emacs, so we can build the
behavior ourselves.
Let me show you a small built-in solution using display-buffer-alist.
Emacs provides display-buffer-alist, which controls how buffers are displayed:
which window they use, where that window appears, and whether an existing window
can be reused.
The mechanism is very powerful, but also quite complex. For a deeper
explanation, recommend Prot's article on display-buffer-alist.
For this use case, we only need two pieces: a display rule for eat-mode buffers
and a small toggle function.
;; Show Eat terminals in a bottom side window, similar to in many modern IDEs (add-to-list 'display-buffer-alist '((major-mode . eat-mode) (display-buffer-reuse-mode-window display-buffer-in-side-window) (inhibit-same-window . nil) (side . bottom) (window-height . 0.3) (mode . eat-mode)))
This rule makes eat buffers appear in a bottom side window, similar to many
modern editors. The trade-off is that every eat buffer is now
opened this way. The custom function below handles multiple project-local eat instances.
(defun my/toggle-eat () "Toggle an Eat terminal using the display rules in `display-buffer-alist'." (interactive) (let* ((buf (get-buffer (project-prefixed-buffer-name "eat"))) (win (and buf (get-buffer-window buf)))) (if win (quit-window nil win) (let* ((buf (get-buffer (project-prefixed-buffer-name "eat")))) (if buf (pop-to-buffer buf) (eat-project))))))
Since each tab represents one project, the function looks for the project-local
eat buffer name generated by project-prefixed-buffer-name. If that buffer is
already visible, the function hides its window and buries the buffer. If the
buffer exists but is not visible, it displays it again using the rules from
display-buffer-alist. If no project-local terminal exists yet, it creates one
with eat-project.
There are a few trade-offs:
- The display rule applies to all
eatbuffers, which may be annoying if you sometimes open terminals outside this workflow. - It relies on
eatandprojectagreeing on the same project-prefixed buffer name. This can break when the current buffer does not belong to a project.
Even with those drawbacks, this improves my current workflow: one frame, one tab per project, and a toggleable terminal for each project.