Weekly Reading — 2026-07-19

Terminal Velocity: Eshell, vterm, and Eat

https://systemcrafters.net/live-streams/july-28-2023/

If you’re an Emacs fan and haven’t been using it as your terminal of choice, you’ve been missing out!

In this stream, we’ll take a look at three different options you should consider for a terminal (or terminal-like) experience in Emacs:

We will experiment with these three options to see which one gives the best results based on the following factors:

  • Speed
  • Terminal emulation quality
  • Efficiency/ease of use

We’ll quickly try to come up with a decent configuration with each of them and then experiment to see what kind of results we get!

  • You still aren’t using a POSIX shell (Eshell) so you might get confused and expect certain things to work like Bash (subcommands, piping, etc)

Note: It’s possible to do something similar with vterm and the eshell-vterm package: https://github.com/iostapyshyn/eshell-vterm

(use-package eat
  :ensure t
  :config
  (eat-eshell-mode)
  (setq eshell-visual-commands '()))

~/.emacs.d/eshell/alias

alias ls *ls $*
alias cp *cp $*

An introduction to Magit, an Emacs mode for Git

https://www.masteringemacs.org/article/introduction-magit-emacs-mode-git

Magit is the sweetener that masks the bitter taste you get when you have to commune through algebraic brevity with git. Magit – unlike other user interfaces bolted on top of a command line version control system – is faithful in its adherence to git’s vocabulary and capabilities.

Although Magit is now firmly entrenched in Emacs canon, it’s still a complex piece of software because, well, so is git. And so this tutorial sets out to help you make the transition from command line – or Emacs’s builtin VC – to Magit.

Getting Started

To use Magit, type M-x magit-status or C-x M-g j. You can also type C-x p m to open a Magit status buffer in your project root, if you use that functionality in Emacs.

The status window is the most common entryway into Magit, but it is not the only one.

If you’re a user of Emacs’s VC – a wonderful one-size-fits-all version control abstraction layer built into Emacs – then you’ll have to learn how to operate Magit as there is little overlap in key bindings. Having said that, I happily mix-and-match both; there are some commands I find more ergonomic.

Magit Status Window

Magit status of a git repository. There is a handy overview of the most recent commits; unpulled and unpushed commits, if any; what HEAD is pointing at; and more.
Magit status of a git repository. There is a handy overview of the most recent commits; unpulled and unpushed commits, if any; what HEAD is pointing at; and more.

The first thing you’ll notice about the Magit status window is that it plays nice with your window configuration when you open it, and when you close it with q.

Magit works in much the same way as M-x dired (or indeed C-x v d, the VC Directory status window) as it prefers single-character key bindings. That’s a smart decision, as you’ll often find yourself repeating the same commands over and over; and like dired, you wouldn’t ordinarily edit anything in Magit’s many buffers anyway.

The status window is similar to git status as it offers an overview of your repository. But unlike git status Magit summarizes more than just the staged and unstaged changes. You can also see unpulled/unpushed commits and your stashes. There is a header containing: local and optional remote; the latest tag, if any; and what HEAD points at.

Staging and Unstaging

One major benefit of using a UI to manage your git interactions is the ability to quickly browse staged and unstaged changes. Not only can you stage or unstage whole files, but you can also do so with the individual hunks in a diff – even regions of text, if a hunk is not granular enough.

Using a simple system of tree-like sections, you can expand or collapse sections, files, or hunks. For more granular control, you can use M-1 through to M-4 for all the files; and 1 to 4 for the selected section; and TAB and S-TAB to cycle the current section or all sections. Most tree-like sections of Magit behave this way; not just on the status window.

You can cycle through sections easily in Magit. It's a simple but powerful feature as adds immediate clarity, particularly if you stage commits by hunk or file.
You can cycle through sections easily in Magit. It's a simple but powerful feature as adds immediate clarity, particularly if you stage commits by hunk or file.
Basic Navigation

Being Emacs, n and p moves between logical entries in all parts of Magit. That’s your bread-and-butter navigational aid; the usual movement keys in Emacs still work: C-n and C-p, and so forth.

There’s also M-n and M-p that move between sibling sections, such as between each file or between each section (like staged or unstaged).

Staging and Unstaging, and Working with Hunks

You can use + and - to enlarge or shrink each hunk and 0 to reset to the default. You can also type H to refine the hunk for additional diff highlighting.

Pressing RET will go to the file where the change is made. It works on both hunks and files.

To stage or unstage you can type s or u to stage/unstage the item (be it a whole file, or just a hunk).

I frequently find that a hunk is not granular enough if I want to manicure what goes into my commits. To work around that, you can navigate into a hunk and stage (or unstage) line by line.

If you select a region, you can instead stage or unstage just that region.

Discarding Changes

If you want to discard something you can do so with k. It works on staged or unstaged files, hunks and untracked files.

Committing Changes

The popups in Magit support a wide range of common switches and commands for each type of work you want to perform in git
The popups in Magit support a wide range of common switches and commands for each type of work you want to perform in git

To begin committing changes in Magit, type c. You’ll see a window pop up – that popup window is the main way you interface, and amend, the parameters Magit must pass to git. Note that Magit may add actions that don’t exist as a singular command or concept in git. Instead they exist only in Magit and serve as a shortcut to save you time.

Part of the appeal of Magit is that you can tailor each command with ease. But if you don’t need that, just tap c again to open the commit message buffer.

The commit message buffer appears when you’re ready to finalize the commit with a message. There’s a number of handy key bindings you should know about:

M-n and M-p

They cycle through the commit message history ring. So if you write a long commit message but realize you need to amend some of the staged changes before you commit it, you can recover your draft commit message by tapping M-p.

C-c C-c

Commits the changes with the message you’ve written.

Magit checks if your message is too long and warns you if you breach the ‘unwritten’ rule that the first line of a git commit message is self-contained and no longer than around 70 characters. (That way, it won’t get truncated when you use git log --one-line.)

This limitation does not apply to subsequent lines or paragraphs you write. (But I recommend you adopt that rule if you don’t follow it already.)

C-c C-k

Cancels the commit. You’re returned to the Magit window and you can resume the commit if you like by repeating the c c key combo.

The Commit and Reference Log

Filtering and Searching the Commit Log

There's a plenitude of options to help you find what you are looking for in git's commit log.

Git on the command line is frustrating because you spend so much of your time trying to feed information from one part of it into another. Doubly so if you frequently browse or filter git’s commit log.

Magit automatically enriches and displays any query it gets back from git log, no matter how gnarly.

I particularly like -G as a means of tracking down the history of changes made in git. But -F is equally useful is you know that the things you are looking for exist in a commit message.

And although l l is the most common way of browing the commit log, I also like l o as you can query any revision and most refspecs. For instance, master..development is a valid input here. Magit will, if your minibuffer completion is up to the task, let you auto complete ranges.

Browsing the Commit Log

Magit's log mode is an enriched version of <code>git log</code>. It's got a number of tricks up it sleeve in addition to pretty printing the output of your commit log history.
Magit's log mode is an enriched version of git log. It's got a number of tricks up its sleeve in addition to pretty printing the output of your commit log history.

I think one of the areas where Magit really shines is its multitude of switches to filter, sort and search your git history. Not only does it render the logs in beautiful technicolor, but it’s interactive and it has a large number of nifty integrations with other parts of Magit.

The first handy shortcut you should know is l l. That opens the “short log”. In it, you will see a one-line commit message; the name of author; how long ago the commit happened; the tree structure of the git log; and various labels, like where HEAD is and where the branch markers are.

The log view is probably the most frequently used view for a lot of git users, as it’s an essential aid if you want to keep up with changes in a fast-moving code base.

Here’s a handful of keys I find useful:

  • C-w copies the commit hash of the selected commit. If you select multiple commits you will not get multiple commit hashes!
  • x prompts you to reset your HEAD and index to a particular commit. When you execute it with a selected log entry, Emacs defaults to the commit hash of the selected entry.
  • v will revert to the commit.
  • d opens the diff view.

    d d tries to DWIM: Do What I Mean. An Emacs term for guessing your intent.

    I recommend you select the range of commits you want to d d, as it’ll otherwise use <commit>^..<commit> which is just the changes made in the selected commit against its parent.

    d s diffs the commit against your staged changes; d u against your unstaged changes.
  • a applies a patch of the commit(s) to your files / index.
  • A cherry picks the commit on top of your working tree.
  • r opens the rebase interface.

    For history rewriting this is perhaps the best entry point for it. You can also use it to quickly collect commits for squashes, fixups and commit message amendments.
  • RET pops open the full commit log entry. You’ll see the diff, author, etc.

    If you switch back to the log and navigate up or down, the commit log entry follows the selected commit.

Branches

Magit has a number of utility commands that speed up development.

Type b to enter the branch view. Of note is b b that checks out a different branch. That’s a common activity, so it has an easy mnemonic.

I also like b l as it creates a local branch of a remote branch and ensures it tracks the remote.

Another life saver is b s: it creates a spin-off branch. It’s a Magit concept, and it’s useful when you end up creating one or more unpushed commits that you’d rather spin off into a separate feature branch. Magit creates a branch of your choosing and moves the unpushed commits to the new branch.

The similarly-named spin-out b S works much the same but it does not change your current branch to the new branch after completing the transfer.

Pushing and Pulling

You can push to remotes with P and pull from remotes with F.

You’ll want P p or P u depending on how you configure your default upstream remote. For pulling it is much the same with F p and F u.

Because fast-forwarding and rebasing on pulling is so common, you can instruct Magit to default to this by typing F C-x C-s after changing the settings.

Dispatching from Anywhere

You jump straight into a log view from any git-enabled buffer with C-x M-g. It works in much the same way as though you’d invoked the action from inside M-x magit-status.

You can also access M-x magit-status through Emacs’s builtin project management suite with C-x p m.

Dispatching Actions on Individual Buffers

Although it’s not immediately obvious, bound to C-c M-g is Magit’s action dispatcher. Note: do not confuse it with the similarly bound C-x M-g.

Unlike the regular Magit interface, this command works on the current buffer. For instance, C-c M-g l opens the commit log history for just that file.

Every command you’d reasonably want to invoke against the current buffer is found in that dispatcher.

You can trace the historical changes made to a definition (usually the nearest function belonging to point) with C-c M-g t. A powerful feature.

One command that you must experiment with is C-c M-g t. It shows the historical logs that changed the function point is in. (So it’ll only work well if Emacs can infer the function point is in.) So if you want to trace changes made to a particular function in your git history, that’s the command to use.

It derives the nearest function using magit-which-function, a derived version of which-function. The latter is a mechanism Emacs uses in Which Function Mode to display the function point is in, in your modeline.

Blaming / Annotate

You can enable interactive blame mode with M-x magit-blame or C-c M-g b. Having said that, this is one area where I prefer C-x v a, the default VC annotation system.

Reviewing & Manually Executing Git Commands

Type ! to open the command line invocation view. The ! ! chord invokes a git command at the root of your git repository. If you want to circumvent Magit’s interface, that is the command you should use.

If you want to review the actions it took, then type $ in the status window to reveal the commands Magit executed on your behalf.

Get Help

If you’re lost, you can type ? to see an overview of action dispatchers available in that view.

You can type C-h in a magit popup followed by a key binding to see the underlying elisp function it invokes. Useful if you want to read up on what a command does.

Magit also ships with an excellent Info manual: M-x magit-info.

Conclusion

Magit’s a great tool for both beginners and experts. Magit does not hide anything; instead, it surfaces capabilities only the most ardent and serious git experts know about. Git is obtuse and hard to use, and that is especially true once you delve into its more esoteric features, like pickaxing through commits with the log viewer, bisecting a range of commits, or interactively rebasing.

In fact, I’ve barely scratched the surface. But I think you know enough now to start using Magit as your daily driver.

Demystifying Emacs's Window Manager

https://www.masteringemacs.org/article/demystifying-emacs-window-manager

Emacs is a fantastic tiling window manager, and not enough people know that. There, it’s out now; I said it; now everybody knows. And thanks to a number of consolidations and improvements, it’s no longer the indomitable black box it once was.

At some point in the distant past, Emacs got into the business of handling frames. I’m sure you, like me, eschew frames in favor of windows. Frames are just that much harder to deal with — unless you’re using an actual tiling window manager, of course. But few do, and the baleful support for frames in regular window managers nix any temptation to make it work.

Later on, tabs also joined the family business. M-x tab-bar-mode adds tab-style window configurations to Emacs, and M-x tab-line-mode a way of browsing visible buffers.

Back to windows. They’re one of Emacs’s greatest assets, and they’re highly configurable. Historically it was a rather difficult affair to control window and buffer placement as you’d have to rein in a cavalcade of variables and functions and hope they did what you wanted. But not any more.

Modern Emacs is now able to mirror the paneling so beloved of IDE users, and without the tears! And you can easily control how or where buffers and windows must go, giving you even more control over your Emacs experience. That means you may not need a third-party window management library – if you ever tried to use one, that is – for most things.

So here’s a snapshot of what is possible today:

Grouping Windows

You can now group windows so certain commands that affect one now affect all of them. They’re given the cryptic name atomic.

When you call C-x 0 and delete an atomic (ugh) window, all other windows that belong to that cohort also disappear. It does come with a couple of limitations, but it’s still a great feature.

Dedicating Windows

You can dedicate a window to a buffer. Dedicated windows are locked to the buffer it was dedicated to, and so any attempt to switch its buffer will merely display that buffer in another window. And if you switch to a buffer that belongs to a dedicated window, Emacs selects the dedicated window instead.

This feature solves another common complaint: that it’s easy to screw up a perfectly manicured window layout – like gdb’s debug view, M-x calc, your fancy Org agenda setup, and so on – because you accidentally switched buffers in one of them.

Sidebars

You can now attach a window to one of the four sides of a frame: left, right, top or bottom. Sidebars are like their kin in other IDEs: they are a full-length (or full-width) windows that resist switching and splitting.

With a sidebar you can finally dedicate sides of a frame to specialized buffers, like the Calendar, Org Agenda, Compilation Output, GDB, you name it. And thanks to a handy command, you can toggle the sidebars to hide or show them all at once.

Override window and buffer placement

Almost all window customization now involve one – albeit elephantine – variable that controls all of it.

Much like Emacs’s reformed minibuffer completion system you’re able to precisely control how Emacs creates and shapes windows using a tiered system of overrides. And package authors can still suggest a preferred layout that you can override if you want to.

Using a regular expression you can command all python buffers to appear only in open windows that already have python mode buffers; you can order M-x eshell to always pop up a new window, no matter what; or insist that M-x compile buffers appear as a side bar on the right-hand side of your screen, and that it must never change your selected window’s buffer.

You can also dictate if you can switch to windows with C-x o, or even prevent Emacs from deleting a window, and thus preserve your layout, even if you use C-x 1.

These four concepts give you near-complete control of all aspects of Emacs’s window management, and that’s not even everything. Yet they’re enough to put you firmly in the driver’s seat.

And the best thing about it all? Experimenting with it is easy and interactive. If you use my snippets of templates and examples you can use them as a starting point for your own window excursions.

Important Terminology and Concepts

Switching Buffers

There is a subtle but important distinction between displaying a buffer and switching to it. Switching is done with C-x b (or its sibling commands, like C-x 4 b) and it is the default user-facing key binding for switching a window’s buffer.

By default Emacs distinguishes between automatic and manual window switching. If you effect a window switch yourself with C-x b, it’s manual — and exempt from any display action rules you create yourself.

You probably don’t want that. I recommend you set this:

;; Requires Emacs 27+
(setq switch-to-buffer-obey-display-actions t)

Now Emacs treats manual buffer switching the same as programmatic switching.

However, it also guards against (some) misbehaving commands you may encounter: those that call out to switch-to-buffer programmatically. That is “against the rules”, as switch-to-buffer is a user-facing command.

Picking the Right Window

Thanks to a concerted cleanup, display-buffer is now the ultimate arbiter of where your buffer and/or window goes. And that’s a good thing indeed, as the previous system required a mish-mash of let-bound variables and mystery functions to do what you wanted.

M-x display-buffer is both a command and an important internal function. It’s called in one of several ways. Here are two common scenarios:

  • A function somewhere wants to compel Emacs to display a buffer in a frame, like M-x calendar.
  • You have manually switched buffers with C-x b, C-x 4 b, M-x ibuffer or some other means.

Eventually they – if they’re programmed right – call, either directly or indirectly, display-buffer to do this. By centralizing the display of buffers you can more easily control how it happens.

Programmatically, display-buffer takes an optional ACTION argument with a suggested display action. At this point in time, the ACTION is a guideline. Emacs may, or may not, follow it; that’s important, because display-buffer decides where things go, and it does so by consulting this list:

  1. First it checks display-buffer-overriding-action, but that is for Lisp use only, so we won’t talk about it again.
  2. Then it checks display-buffer-alist, which we very much will talk about, as it’s the most important variable in Emacs’s window management system.
  3. Then it checks the ACTION argument.
  4. Then it checks display-buffer-base-action, which is a catch-all default.

    It’s a user-facing variable you can configure, and unlike display-buffer-alist it can only hold one ACTION.

    Although we won’t talk about it much, it’s mostly there so you can build a catch-all rule for windows that don’t fall under rules 1, 2 or 3.
  5. At last it checks display-buffer-fallback-action, but that is also for Lisp only.

    The variable is only consulted last, and it’s the variable of last resort if there are no other actions that match.

Although the term ACTION is thrown around a lot, it’s a bit of a misnomer, I think. It’s really a constraint. It is a mixture of “do that” along with “but only if these facts hold true”.

You see, when you ask Emacs to display a buffer it checks the aforementioned list. Each variable in that list may hold – particularly in the case of display-buffer-alist – rules that Emacs must check before it decides on the next course of action.

The first set of constraints – actions – that satisfy Emacs, wins. It’s perfectly fine to have more than one set of constraints that match a given buffer; just know that the first one Emacs encounters is the one it picks.

So as you can see, the ACTION argument in #3 is really more of a guideline, even though the package author had a particular layout in mind when they wrote it. That means elisp hackers can’t know for sure if Emacs will actually display a buffer the way they intended — good!

Look no further than M-x speedbar for a disastrously bad example of curtailing choice. Speedbar only works in a standalone frame, and it’s impossible, without a third-party hack, to get it to display in a window.

Predicated Actions

The display-buffer function checks a list of variables (and its own ACTION argument) to determine what it needs to do. They’re all, in one way or another, made up of ACTIONS.

Here’s the name of a function, and one part of, an action: display-buffer-same-window. As its name alludes to, it’ll display a buffer in the same window (same refers to the selected window.) Its definition looks like this:

(defun display-buffer-same-window (buffer alist)
  (unless (or (cdr (assq 'inhibit-same-window alist))
              (window-minibuffer-p)
              (window-dedicated-p))
    (window--display-buffer buffer (selected-window) 'reuse alist)))

Asking Emacs to put something in the same window is only possible if:

  1. inhibit-same-window is not t in ALIST.

    This flag – more on it later – instructs Emacs to never put something in the same window. Which, of course, in the case of display-buffer-same-window defeats the point of the display function!
  2. The window is not the minibuffer.

    We can’t use that, for obvious reasons.
  3. The window is not dedicated.

    Dedicated windows resist attempts to change their current buffer.

If it’s none of those things, then the predicate succeeds and the buffer is displayed in the selected window. The search is over, and display-buffer has done its job.

Note that there’s a mixture of both predicates and logic to carry out the work and that’s because – back to my argument about constraints – that in order for Emacs to put a buffer somewhere it must first ascertain whether it’s even possible.

In the unlikely event that Emacs is not able to place a buffer, it’ll eventually consult display-buffer-fallback-action and pick an option from that list.

Constraint Satisfaction

Consider this. If you split your frame into a kaleidoscope of windows, each doing their own thing, and you then add a bunch of custom rules, like:

  • *info* and *Help* windows go in a sidebar on the left;
  • All Python buffers recycle existing Python windows;
  • *Compilation* mode buffers always pop up in a new window, split along the top, with a height of 10 lines.
  • *EShell* buffers, once shown, are marked dedicated.

You’re giving Emacs a large number of rules it must abide whenever you display a buffer. Not only must Emacs respect the rules you’ve defined in display-buffer-alist, but it must also contend with the existing windows and their buffers in your frame.

Now where should the next buffer you want to display go?

That is the dilemma Emacs faces when it must place both a buffer and maybe create a window. For every rule you add, you are constraining Emacs’s ability to pick (what it thinks) is the best place for a window or a buffer.

Dedicated windows, sidebars, forcing Emacs to re-use a window, or enforcing a minimum height or width for a window — they all add up to to a laundry list of constraints.

Emacs is not always able to do what you tell it to. Given a sufficiently complex web of rules, it’s possible those rules conflict with one another (and your current window layout) and so Emacs is unable to follow those rules.

That is why Emacs has a “safety valve”, of sorts. It’ll do its darnedest to respect the rules you set, and if it can’t, it’ll turn maverick and just display it somewhere. That’s why, despite Emacs’s best intentions, it may switch a buffer in a window you told it not to touch, or even – as the last resort – create a brand-new frame.

It’s rare, but it can happen.

Trees, Windows and Internal Windows

Emacs tracks your current window layout in a tree structure called the window tree. The window tree represents the dimensions and location of every window in your current frame. A tree structure is ideally suited to this task, as subdivisions are either horizontal or vertical, which Emacs represents as nodes in the tree. The tree also knows where your minibuffer is, but that is not very interesting to us, although it has a window also.

You can browse your current window tree by evaluating (window-tree).

When you split a window, Emacs creates a new node in the tree that holds the live windows (the ones with buffers in them.) But as you successively split windows, you’ll end up with nodes in the tree that aren’t actual windows in your frame. Those nodes are called internal windows.

Internal windows aren’t terribly interesting to us, so I won’t belabor their role in Emacs’s window tree much. The only thing you need to know is that whenever you split a window, Emacs needs to keep track of each distinct, live, window you see on the screen. It does so with ‘invisible’ windows called internal windows.

Consider this:

+----------+-----+-----+
|          |     |     |
|    R1    | R2  | R3  |
|          |     |     |
+----------+-----+-----+

If R1 is the selected window and you want to split below, then all you need to do is call C-x 2. But if you want to split across R1 through R3 you can’t — not with C-x 2 anyway.

But it is possible: but you must split from the shared parent of R1, R2 and R3 — an internal window.

Without an internal window, or some other structure that can represent the relationship between windows, you wouldn’t be able to split across those three windows.

Although it’s not super-duper important to know about, internal windows play an important role when you want to carry out actions that involve the parent, children or siblings of windows.

Splitting Windows

Note: Emacs 29 adds native support for this. See M-x split-root-window-<right/below>.

The default split commands – C-x 2 and C-x 3 – split below or to the right of the selected window. Sometimes, though, you may want to split from the root of the tree. So if you want a full-length window below or to the right of all your other windows, you can, with a little bit of elisp:

(defun mp-split-below (arg)
  "Split window below from the parent or from root with ARG."
  (interactive "P")
  (split-window (if arg (frame-root-window)
                  (window-parent (selected-window)))
                nil 'below nil))

You’re free to replace below with right if you want it to split to the right instead, or swap the order of the if statement if you’d rather invert the operation.

If you want to bind it to a key, check out my article on binding keys in Emacs.

This command works well when you want to split against the parent of the selected window, or from the root with C-u.

Indeed, split-window is the function that all the other ones call when they want to split a window. The distinguishing factor is the WINDOW argument, as it controls the window you want to split from.

Splitting from RT1 with M-x mp-split-below yields:

+----------+-----+-----+
|          | RT1 | RT2 |
|          +-----+-----+
|          |    RB2    |
|    L1    +-----+-----+
|          |           |
|          |    RB1    |
|          |           |
+----------+-----------+

And with C-u M-x mp-split-below:

+----------+-----+-----+
|          |     |     |
|          | RT1 | RT2 |
|    L1    |     |     |
|          +-----+-----+
|          |    RB1    |
+----------+-----------+
|          B1          |
+----------+-----------+

This concept plays a role in a few of the display ACTION functions we’ll look at later.

Dedicating Windows

When you dedicate a window, you’re effectively saying that you would prefer that Emacs does not switch the buffer in that window for another. It’s not an ironclad guarantee; there are instances where Emacs is up against the wall and has to put something somewhere.

Having said that, window dedication works well, and you should definitely use it.

A dedicated window confers the following benefits:

  • Switching buffers manually with C-x b fails with an error.

    You can customize switch-to-buffer-in-dedicated-window and control how it behaves.

    I set it to pop, because I want it to open the buffer somewhere else instead. But there’s also ignore, and it’ll ignore your switch; nil, which raises an error and stops the switch; and t, that undedicates the window and switches.

    Note that if you use custom completion frameworks that circumvent C-x b (switch-to-buffer) like Ido Mode it may not behave exactly this way.

  • Splitting a dedicated window with C-x 2 and C-x 3 fails with an error.
  • Emacs will not switch buffers in dedicated windows.

However, dedicated windows are not immovable objects. You can still delete the windows with C-x 0 or C-x 1; kill the buffers they belong to; or resize them.

The function set-window-dedicated-p handles the dedicating. Unfortunately it’s not exposed to users by default, so let’s fix that by making a little elisp helper that toggles dedication in the selected window:

(defun mp-toggle-window-dedication ()
  "Toggles window dedication in the selected window."
  (interactive)
  (set-window-dedicated-p (selected-window)
     (not (window-dedicated-p (selected-window)))))

Note that the documentation is quite diffuse about the flag you can set. Here I toggle between t or nil, but it accepts a range of non-nil values. Values like side is not as strong a dedication as setting it to t is.

But yeah. Just toggle between t and nil and it’ll generally behave the way you want.

Another approach is setting dedicated flag directly in display-buffers-alist, but more on that later.

The command is still useful, though: you can set and unset it as part of your window layout workflow, by using a keyboard macro.

Side Windows

Side windows are full-length windows attached to either the left, top, bottom or right-hand side of a frame. You can have more than one side window attached to the same side of the frame, and you can have sidebars on more than one side at a time.

A side window and a dedicated window go hand-in-hand, and they share many similarities. Side windows are also dedicated, but they set dedicated to side, which is not as strong a promise as setting it to t.

Every side in a frame has a number of slots, controlled by the window-sides-slots variable. If you want to use side windows, then I strongly recommend that you customize it and set limits on how many you’ll allow on each side. Once you exceed the number of slots, Emacs will instead cycle the buffers in the existing side bar windows.

Side windows are special because you can order them relative to one another using their slot, an integer position.

They also benefit from a really neat command that toggles side windows visible or invisible: M-x window-toggle-side-window. Really useful command to know about if you’re flanked on all sides and want to briefly toggle them off.

One thing to be aware of is that, if you’re experimenting with windows, that the frame’s window state can get out of whack. The fix is to evaluate this form:

(set-frame-parameter (selected-frame) 'window-state nil)

Controlling Buffer and Window Display

If you want to control where a buffer or window must appear, you must customize display-buffer-alist.

It’s a little… complex. Well, actually, once I explain how it all fits together, you’ll see that it is not.

The first thing you should do is customize it with M-x customize-option. Insert an entry and click around a bit to get a feel for what it’s about. Don’t worry too much about making sense of the component parts of it yet. I’ll explain that in a moment.

First things first though. I want you to consider customizing these, and at least consider why I want you to change them:

switch-to-buffer-in-dedicated-window

Controls what happens if you, as a user, attempt to switch buffers in a dedicated window. (Remember, sidebars are also a form of dedicated window.)

I prefer pop to the default, as I’d rather have it pop up the buffer somewhere else than simply error out. That then goes hand-in-hand with the next variable.

switch-to-buffer-obey-display-actions

If nil, the default, user-switched buffers are exempt from display buffer actions set in display-buffer-alist & friends. Requires Emacs 27.

You probably want those rules to affect your interactive buffer switching, as it makes for a consistent buffer switching experience. I set it to t myself.

I find that some packages and functions disregard all rhyme and reason and use switch-to-buffer instead of display-buffer when they want to show a buffer to the user.

This is wrong. And the net result is that your display rules won’t apply if this variable is not set to t.

Testing and Experimenting

If you’re not an elisp expert I recommend you use the Customize interface.

You can use it to browse and tweak display-buffer-alist, even though my examples below use elisp. So my suggested workflow is:

  1. Evaluate the elisp you want to test.
  2. M-x customize-option on display-buffer-alist.

    You’ll see the newly-added entry, and you can modify it in-situ.

    (If it’s already open, M-x revert-buffer reloads the Customize interface.)
  3. Apply changes with C-c C-c to test them.

Creating a display-buffer-alist entry

A display-buffer-alist entry consists of:

  • A CONDITION, which is either a string or a function
  • An ACTION, which is either a function or a list of functions
  • An optional ALIST of flags and settings

Defining a CONDITION

Every entry must have a CONDITION, which is either a regular expression matched against the buffer name, or a function that returns t if a buffer name is a valid match or nil if it is not.

Practically speaking you are limited to searches by buffer name with regular expressions. That’s almost always enough. If it is not, you have to use a function (and I’ll show you how to make one later on.)

Common reasons for needing a function include wanting a condition that triggers if a buffer belongs to a derived mode, for instance.

If you need help writing your regular expressions, then I recommend you use Emacs’s interactive regexp builder.

Choosing the Predicated ACTIONs

When you have defined your CONDITION, you can give it either a singular ACTION or a (ACTION_1 ... ACTION_N) list of actions.

But what’s an ACTION? Well, the description for display-buffer does proffer a list of suggested ACTION functions, but this is not the complete list.

To get the complete list you… well, you have to hope they’re sensibly named.

This is one way to find the builtin ones:

C-u C-h a ^display-buffer-[^-]

Note that you may find a few stray functions that have nothing to do with windows. But as you scan the list, you’ll see a pattern:

display-buffer-below-selected
display-buffer-at-bottom
...

They’re ACTION entries. Or as I prefer to think of them: predicated actions.

Recall that they not only determine where something goes, but also check if they’re able to carry out the action in the first place!

Whether you specify a single ACTION or a list of ACTIONs, the outcome is the same:

Emacs cycles through one of more ACTIONS and it stops when it receives the window the buffer was displayed in.

That means you can order your preferred display methods in the order you want Emacs to try them. It also means that a sneaky display action can change stuff and not return a window — necessitating more work by Emacs. That is, as far as I know, only used in one place (so far.)

For instance, you can declare that *Help* windows must:

  1. First display-buffer-reuse-window, which tells Emacs to find an existing *Help* window and re-use that one.
  2. Next, try display-buffer-pop-up-window and pop up a new window with the buffer.
  3. And if that fails, the entire display entry is skipped and Emacs continues with the next one in display-buffer-alist.

That example works like this:

(add-to-list 'display-buffer-alist
   '("\\*Help\\*"
     (display-buffer-reuse-window display-buffer-pop-up-window)))
Useful ACTION Functions

Here’s a list of some of the more useful display buffer functions you can use. As always I encourage you to use apropos to find the complete list yourself.

NOTE: If you use the customize interface, know that not all the functions below appear in the dropdown. You may have to select Other Functions and type in the name below for this to work.

Some of the ACTION functions require additional setttings to work. More on how to set them below. I recommend you C-h f the functions you want to use.

display-buffer-below-selected

Attempts to display the buffer immediately below the selected window.

This is useful for contextual popups that you want near the selected window that triggered the buffer display.

You can optionally specify a setting of window-min-height that governs the required minimum size of the new window.

display-buffer-in-atom-window

Marks the window as atomic when it is displayed somewhere. It will ‘stick’ to nearby atomic windows, if such windows exist.

You can control where it is displayed relative to an existing window by setting window.

You must also specify a side where the new window is created, relative to window.

Useful for “shared” windows that must only exist as long as the other, related, windows exist.

One example is M-x calc and its calculation window and history window.

display-buffer-in-child-frame

Really intriguing option that pops up an internal child frame, as opposed to a top-level frame. So it’s more like an in-frame popup window than a standalone window.

You can control its frame parameters with child-frame-parameters.

display-buffer-in-direction

Displays a buffer in a window in an absolute direction from a window.

The default window is the selected window, and so it’s a generalized version of display-buffer-below-selected, but capable of displaying the buffer in any direction.

If you set window to root you can split across the height or width of the entire frame.

display-buffer-in-tab / display-buffer-in-new-tab

NOTE: These display actions only return a valid window – thus ending the search – after initializing the tab, and only if the buffer is already present in a window. That means you can use these actions to initialize the tab and another action to refine what happens after that has taken place.

Places a buffer in a (new) tab. Note that this refers to M-x tab-bar-mode and not the tab line mode.

Tab bars are effectively tabbed window configurations, and a great addition to Emacs.

display-buffer-in-tab pops a buffer in a tab named tab-name (and optionally part of a tab-group.)

If the tab does not exist, it is first created. If you use display-buffer-in-new-tab then a new tab is always created, and existing ones are never reused.

Tab groups are collections of tabs; they are not displayed by default, so customize M-x customize-option tab-bar-format first.

display-buffer-no-window

Do not display the buffer in a window at all.

Useful if you want to suppress a buffer from showing. Great for throw-away buffer windows that insist on appearing even if you do not want them to.

display-buffer-pop-up-window

Pops a buffer in a new window. If you want Emacs to always requisition a new window for a buffer, use this.

display-buffer-reuse-mode-window

Force Emacs to reuse a window that has a buffer with mode major mode.

Great if you have either a selection of major modes that you want a buffer to reuse. Use it to instruct Emacs to always open, say, Javascript files in existing js-mode windows.

The mode setting also works with a list of major modes. It also uses derived-mode-p to check if a buffer’s major mode qualifies: that means you can type prog-mode to catch all the many programming modes.

display-buffer-reuse-window

Reuse a window if it is already displaying that buffer.

So if you want Emacs to reuse the window that is already displaying the buffer, then use this. Note that you can force Emacs to disregard the window if it is the same window you’re in. Set inhibit-same-window to t.

display-buffer-other-frame / display-buffer-pop-up-frame / display-buffer-use-some-frame

Much like their window cousins, you can instruct Emacs to pop up a buffer in another frame if is already displaying the buffer.

You can also tell it to always pop up a new frame, or simply use any frame it likes.

display-buffer-same-window

Forces Emacs to display a buffer in the currently selected window.

Note that it respects window dedication (including sidebars), so it will not work there.

display-buffer-use-least-recent-window

Instructs Emacs to prefer the LRU (least recently used) window.

How it determines that is how often you (or Emacs) has interacted with a window.

display-buffer-use-some-window

Displays the buffer in some window in your frame. If you don’t care where a buffer is put, you can use this.

It also supports inhibit-same-window, so if you want it to display anywhere but your current window, set that flag.

ACTION Settings

In addition to one or more ACTION entries, you can – sometimes must – specify settings that fine tune how Emacs places a buffer or window.

I call them settings but they don’t have, as far as I can tell, a formal name beyond ALIST in the description for display-buffer-alist. ALIST is just a generic word for association list, so I’ll refer to them as settings from now on.

You can find a list of settings in C-h f display-buffer but it is, yet again, not a complete list. (But you should read the list of settings, nonetheless.)

Some ACTION functions have their own requirements. They are usually listed in that function’s docstring. An example is display-buffer-in-side-window:

The following two symbols, when used in ALIST, have a special meaning:

‘side’ denotes the side of the frame where the new window shall be located.  Valid values are ‘bottom’, ‘right’, ‘top’ and ‘left’.  The default is ‘bottom’.

‘slot’ if non-nil, specifies the window slot where to display BUFFER.  A value of zero or nil means use the middle slot on the specified side.  A negative value means use a slot preceding (that is, above or on the left of) the middle slot. A positive value means use a slot following (that is, below or on the right of) the middle slot.  The default is zero.

The manual is also a good choice if you want further information. (Press i in the Help buffer.)

Useful ALIST Settings

I’ve included some of the more interesting ALIST settings here that I think most people would find useful:

inhibit-same-window

It defaults to nil, but if you set it to t then Emacs will ignore any desire to place a buffer in your same (selected) window.

Use inhibit-same-window to force Emacs to place a window in a different window from your current one, even if the function would prefer to use your current one.

This setting is used by display-buffer-in-previous-window, display-buffer-use-least-recent-window, display-buffer-use-some-window, etc.

window-height, window-min-height and window-width

Controls the ultimate window size. Great if you know roughly how big you want the window to be, in height or width.

If you specify an integer then it’s a constant-sized window.

If you use a floating point number you can instead make it scale to the size of the frame. 0.25 is 1/4th the frame size, for instance.

You can also give it a function and Emacs will attempt to use that function to size the window. Try fit-window-to-buffer. Note that it may not be able to fit the window to the buffer!

The width and size settings only apply to some functions. window-min-height in particular only works with display-buffer-below-selected.

preserve-size

Orders Emacs to preserve (or not) the height and/or width of a window. It’s a cons form with the following format: (WIDTH . HEIGHT).

Replace WIDTH or HEIGHT with t or nil to preserve or not preserve the size in that direction.

dedicated

Marks the window dedicated if t. You can also set it to side, but I don’t recommend that. Stick to t and nil.

side

Used by display-buffer-in-side-window and display-buffer-in-atom-window.

Set side (bottom, left, top, right) to determine the direction you want the window to be.

slot

Controls the integer slot where the window must go. Used by display-buffer-in-side-window.

direction

Used by display-buffer-in-direction and it places the window in direction of the selected window. direction must be one of left, up, right, bottom.

Great if you prefer your M-x calendar or M-x calc windows to always appear at the top or bottom.

mode

Reuses an existing window provided it has one of mode major modes.

This is used by display-buffer-reuse-mode-window and it’s great if you want to reuse windows based on a major mode.

You can combine ‘similar’ major modes, like Info-mode and help-mode and Emacs will dutifully put a buffer in any one of those.

window-parameters

Very useful, but requires that you know the names of useful window parameters.

There are many, and I recommend you explore a window of your choosing by evaluating this:

M-: (window-parameters (selected-window))

Of particular interest, I think, are the following:

no-delete-other-windows

If t, the window is not deleted when you call C-x 1.

Great for side bars and other important windows.

no-other-window

If t, the window is unselectable with C-x o.

Great for windows where you want to look, but not interact with, the contents of the buffer in that window.

You can still click-select the window or C-x b to it. It’s only removed from the other-window (C-x o) cycle.

The window-parameters setting is itself an alist.

Putting it all together

Now that you know how each component part of a display entry works, let’s look at a few different ways of constructing them.

I still recommend you use the Customize interface if you’re not interested in hacking around with elisp.

The most basic of display entries looks like this:

(add-to-list 'display-buffer-alist
  '("\\*Compilation\\*"
    display-buffer-reuse-window))

We match against one buffer name, *Compilation*; with one ACTION, display-buffer-reuse-window; and no ALIST settings, so it’s not listed.

A more complicated example might look like this:

(add-to-list 'display-buffer-alist
 '("\\*info\\*"
   (display-buffer-in-side-window)
   (side . right)
   (slot . 0)
   (window-width . 80)
   (window-parameters
    (no-delete-other-windows . t))))

There’s a bit more going on here. I want *info* windows in a side bar window; it must be on the right-hand side and in slot 0; the window-width must be 80; and Emacs must set the window no-delete-other-windows window parameter to t.

(add-to-list 'display-buffer-alist
 '("\\*Help\\*"
   (display-buffer-reuse-window display-buffer-pop-up-window)
   (inhibit-same-window . t)))

Here I insist that *Help* buffers reuse any existing *Help* window if such a window exists. And if that is not possible, it must pop up a new window. Furthermore, Emacs cannot use the same (selected) window, and it must use another.

Limitations

Let’s talk limitations. There’s a large corpus of third-party packages – and a number of internal ones in Emacs – that don’t know or care about cooperating with you or your window configuration. More often than not it’s because they believe their settings are the best; they may well be right, but it’s still wrong. And older packages are at least partially excused, as they predate the modernization efforts. But there’s a large body of up-to-date packages that don’t create or manage windows correctly even when it’s most likely easier to do so.

If you encounter such a package – they are more common than you think – you have little in the way of recourse beyond working around the code, or raising an issue with the maintainer(s).

In particular, package authors must use display-buffer (or pop-to-buffer) and avoid the temptation to write long tracts of elisp that manicure a window immediately after displaying a buffer. Calling out to display-buffer-xxxx actions is also not allowed.

Moving Around & Undoing Window Changes

If you’re not already aware, M-x winner-mode is a builtin minor mode that lets you undo (and redo) window configuration changes. They’re bound to C-c <left> and C-c <right>, respectively, by default.

There’s also a near-equivalent for tab bar window configurations called M-x tab-bar-history-mode. You can now run M-x tab-bar-history-forward and M-x tab-bar-history-backward to cycle through changes made in the active tab’s window layout.

I also want to quickly mention windmove. It’s a package that makes it possible to move (switch to a buffer) in all four cardinal directions using – by default – S-<left/right/up/down>. It notably got an upgrade in Emacs 28 to let you delete or create windows in a direction also.

You may prefer windmove to C-x o. I prefer M-o though as the default is awful. More about my personalized key bindings here.

Templates & Examples

Here’s a selection of templates that demonstrate all the concepts you’ve read so far. They’re designed to selectively override how Emacs displays buffers and windows.

I recommend, as always, that you experiment thoroughly. That is easiest if you don’t add-to-list (it’s all too easy to end up with near-duplicated entries) but instead setq or the Customize interface.

One common theme that I see a lot of newer users ask for is a way of repeating their favorite layouts, particularly between restarts or if the layout is changed. So I recommend you record a keyboard macro. Keyboard macros are more than capable of not only opening files, splitting windows or switching buffers, they can do nearly everything you can do. So give them a try. No elisp required.

Another suggestion is to use tab-bar-mode. A tab is merely a window configuration — nothing more, nothing less.

Handy Helpers

You don’t have to use a regular expression to match buffer names. There’s a little mini-language you can use instead; it’s usually enough for most things.

Instead of the car of your display-buffer-alist entry containing a string, you can use a form that is interpreted by Emacs’s display mechanism.

The docstring for buffer-match-p explains it all, but I’ll throw in a few examples.

Matching with the buffer matching language

(add-to-list 'display-buffer-alist
           '((or (major-mode . Info-mode)
                 (major-mode . help-mode))
             (display-buffer-reuse-window
              display-buffer-in-side-window)
             (reusable-frames . visible)
             (side . right)
             (window-width . 0.33)))

Instead of a complex regex that may fail to match every possible name that use either help-mode or Info-mode, I’ve instead opted for the form (or (major-mode . Info-mode) (major-mode . help-mode)) that will match if the major mode is either of those two.

The docstring starts bloviating about cadr this and cdr that to describe what you can put where; but I think it’s easier to just think of it as:

  • Either a cons cell with an explicit rule in the shape of (A . B). Where A is something like major-mode, or derived-mode and B is the value it must match;
  • A (nested) bool form of (BOOL REST) where BOOL is or, and, etc. and REST is any number of cons cells or more BOOL values.

There are other things you can put in there, like a string, which is what most people think of when they use display-buffer-alist. So you can indeed use the boolean system to test multiple regexes; or a combination of calling a function, checking the major-mode, and so on.

Only match buffers that have a project

(defun mp-buffer-has-project-p (buffer action)
  (with-current-buffer buffer (project-current nil)))

Returns non-nil if a buffer belongs to a project – i.e., C-x p p – and it is another way to match buffers.

You can use this inside the matcher code

Frustration-Free Window Recycling

This setup tames all those annoying windows that keep popping up when you run a command, even if there’s already a window with a similar buffer. VTerm is one such culprit.

NOTE: As I explained earlier, you can only modify well-behaving windows. Some packages – even some of the ones in Emacs – are not, and they may ignore your attempts to tame them.

This snippet instructs any buffer named *vterm* to reuse a window with a major mode of vterm-mode or vterm-copy-mode. Changing inhibit-same-window to t prevents Emacs from reusing the selected window if it’s a vterm window (you’ll instead get a new window.)

(add-to-list 'display-buffer-alist
     '("\\*vterm\\*" display-buffer-reuse-mode-window
       ;; change to `t' to not reuse same window
       (inhibit-same-window . nil)
       (mode vterm-mode vterm-copy-mode)))

Example 2: Reusing Windows

Here I collect a bunch of buffer names and ask that they share their respective buffers’ windows. That is, *xref* always reuses an *xref* window; a *grep* buffer a *grep* window, etc.

(add-to-list 'display-buffer-alist
          `(,(rx (| "*xref*"
                    "*grep*"
                    "*Occur*"))
            display-buffer-reuse-window
            (inhibit-same-window . nil)))

Here I default to display-buffer-reuse-window.

If you have a random selection of buffers that pop up here and there, you can collect them all in one rule that forces them to reuse an existing window if such a window exists.

Example 3: Matching by major mode

One common problem is matching against one or more major modes. Sometimes, it’s simply not possible to match against the buffer name.

Consider Magit that has a large range of buffer names and major modes.

;; Required. But note that this _does_ change Magit's default buffer display behavior.
(setq magit-display-buffer-function #'display-buffer)

(add-to-list 'display-buffer-alist
          `((derived-mode . magit-mode)
            (display-buffer-reuse-mode-window
             display-buffer-in-direction)
            (mode magit-mode)
            (window . root)
            (window-width . 0.15)
            (direction . left)))

This snippet forces magit to reuse any magit-mode-derived buffer window already present.

If there is no such buffer, Emacs will instead create a window in the left-most direction with a width of 15% of the frame for all magit-mode-derived modes.

Hiding Buffers

You can also instruct Emacs to not show a window at all if it matches a buffer pattern.

Example

This hides *compilation* buffers. You can still switch to them, but their display is inhibited when created.

(add-to-list 'display-buffer-alist
     '("\\*compilation\\*" display-buffer-no-window
        (allow-no-window . t)))

Great for all those annoying popups where you only care about the contents when something goes wrong.

IDE-style Windows

This setup emulates the paneling you often find in commercial IDEs. They are more often than not attached to the sides.

  • The bottom of the screen is used for ephemeral Eshell or Shell mode buffers, for command line shell access.
  • The right-hand side is a side bar for compiling and running scripts with M-x compile or the output from *Grep*.

    The sidebar is sacrosanct, so it prevents deletion with C-x 1. It is also marked dedicated, so you cannot C-x b inside it by accident. This also stops Emacs from reusing it for other things.

  • Any buffer named test_ is opened to the immediate right of the selected window.

Example

This first part controls the shells. It will force Emacs to place them at the bottom, with a window height of no more than 30% of the size of the frame.

(add-to-list 'display-buffer-alist
  '("\\*e?shell\\*" display-buffer-in-direction
    (direction . bottom)
    (window . root)
    (window-height . 0.3)))

Controlling side windows is equally straight forward. I recommend you limit the number of side window slots – nil means infinite, and a positive number the maximum you’ll allow – as that means Emacs swaps out the buffer in that slot instead of creating a new entry. Great for situations where you have a handful of ephemeral things – compilation, test output, shell command output, etc. – that you want to share windows.

I also set a window parameter, no-delete-other-windows, that prevents Emacs from destroying the side window when you type C-x 1. It also has a fixed size of 80.

;; left, top, right, bottom
(setq window-sides-slots '(0 0 1 0))

(add-to-list 'display-buffer-alist
          `(,(rx (| "*compilation*" "*grep*"))
            display-buffer-in-side-window
            (side . right)
            (slot . 0)
            (window-parameters . ((no-delete-other-windows . t)))
            (window-width . 80)))

And don’t forget: you can type M-x window-toggle-side-windows to toggle them visible or hidden.

This snippet places a buffer to the immediate right of the current window if the buffer name starts with test_ or test-.

(add-to-list 'display-buffer-alist
         `("^test[-_]"
           display-buffer-in-direction
           (direction . right)))

Tweak it to use something else, of course. Maybe header files if you’re hacking on C or C++.

Tab-Based Workflows

NOTE: This makes use of Emacs’s M-x tab-bar-mode.

Also, I recommend you customize tab-bar-format and change it so that you show formatted groups.

Grouping Buffers into a Named Tab

Let’s say you frequently work with Org mode files, and you want them to appear in a separate tab. That tab is your dedicated Org mode tab; only org-related stuff should appear in it.

(add-to-list 'display-buffer-alist
          `((or (derived-mode . org-mode)
                (derived-mode . org-agenda-mode))
            (display-buffer-in-tab display-buffer-in-direction)
            (ignore-current-tab . t)
            (direction . bottom)
            (window-height . .2)
            (tab-name . "🚀 My ORG Mode Files")
            ;; Optional
            (tab-group . "Org")))

This creates a display entry that routes org-agenda-mode and org-mode major modes to:

  1. A tab called 🚀 My Org Mode Files. If the tab does not exist, it is first created.

    If you leave out tab-name Emacs will instead auto-generate it. That’ll generate lots of new tabs. So beware.
  2. The tab-group Org is also set up if it does not exist. The group is optional and is not a requirement.
  3. If the buffer Emacs is displaying is already visible in a window in that tab bar, then Emacs stops the search.

    However, if it is not, Emacs will continue with the search and apply display-buffer-in-direction in that tab.

    The newly-minted window is given a height of 20%.

As you can see, display-buffer-in-tab is special: it’s capable of creating a tab and handing over the window display to a subordinate display routine.

Tabs for Elfeed

Tabbed Elfeed Example
Tabbed Elfeed Example

Elfeed is an Atom/RSS newsreader. It’s an excellent package, and you can read Mastering Emacs in it.

Here I force Elfeed to use unique buffers for each elfeed article. I then match both the generic search buffer and the entry pages and show them in a dedicated tab group called 📻 Elfeed. The tab name is auto-generated and sourced from the feed title.

(setq elfeed-show-entry-switch #'pop-to-buffer
      elfeed-show-unique-buffers t)

(add-to-list 'display-buffer-alist
             `("^\\*elfeed-entry-"
               (display-buffer-in-tab)
               (dedicated . t)
               (tab-name . (lambda (buffer alist)
                             (with-current-buffer buffer
                               (concat "🚀 " (elfeed-feed-title (elfeed-entry-feed elfeed-show-entry))))))
               (tab-group . "📻 Elfeed")))

(add-to-list 'display-buffer-alist
             `("\\*elfeed-search\\*"
               (display-buffer-in-tab)
               (dedicated . t)
               (tab-name . "📣 Entries")
               (tab-group . "📻 Elfeed")))

Project Tabs

This creates a tab group for each project – as defined by C-x p p and Emacs’s project management implementation – and with each buffer getting its own tab. Buffers without projects are ignored.

NOTE: This is more of a demonstration; you’d probably want to tweak this quite heavily to match your workflow which does, unfortunately, require some elisp skills. It’s also not 100% free of bugs or quirks either!

(defun mp-buffer-has-project-p (buffer action)
  (with-current-buffer buffer (project-current nil)))

(defun mp-tab-group-name (buffer alist)
  (with-current-buffer buffer (concat "🗃 " (or (cdr (project-current nil)) "🛡 Ungrouped"))))

(defun mp-tab-tab-name (buffer alist)
  (with-current-buffer buffer
    (buffer-name)))

(add-to-list 'display-buffer-alist
             '(mp-buffer-has-project-p
               (display-buffer-in-tab display-buffer-reuse-window)
               (tab-name . mp-tab-tab-name)
               (tab-group . mp-tab-group-name)))

;;; OPTIONAL, but probably required for everything to work 100%
(defun tab-bar-tabs-set (tabs &optional frame)
  "Set a list of TABS on the FRAME."
  (set-frame-parameter frame 'tabs (seq-sort-by (lambda (el) (alist-get 'group el nil))
                                                #'string-lessp
                                                tabs)))

(defun mp-reload-tab-bars (&optional dummy)
  "Reload the tab bars... because they're buggy."
  (interactive)
  (tab-bar-tabs-set (frame-parameter nil 'tabs)))

(add-hook 'kill-buffer-hook #'mp-reload-tab-bars)
(add-hook 'window-selection-change-functions #'mp-reload-tab-bars)

The optional code at the end is there to, ah… fix a few ‘quirks’ in tab bar’s implementation. Basically the tab groups (as you seem them in the tab bar) don’t sort by the tab group, so you end up with duplicates, which is weird. The hooks are there to try and rein in consistency bugs where the tab bar’s not updated as frequently as it should be.

Conclusion

With a little bit of work you can (probably!) coax Emacs to do what you want it to do. The window manager is not perfect by any means, but I think it’s good enough to accommodate a large range of workflows.

If you build something nifty, by all means let me know.

Replacing tmux and GNU screen with Emacs

https://www.masteringemacs.org/article/replacing-tmux-gnu-screen-emacs

If you’re a command line hacker – and I know you are – then I am sure you are familiar with, and possibly use, GNU screen or its contemporary replacement, tmux. If you are in the process of switching from Vim to Emacs, then tmux is no doubt a large part of your workflow.

But here’s the thing: you do not need a terminal multiplexer if you are already using Emacs. Emacs can accomplish nearly everything your terminal multiplexer can do. If you’re not a serious user of screen or tmux, but you’re still addicted to the command line, then I’m sure this article will convince you of the merits of multiplexers, and how Emacs can slot right in. If you’re still unconvinced after having read this article, then my parting advice is to read about the keyboard shortcuts in GNU readline as that’ll at least bring more of Emacs to your command line life.

Before I get into the weeds of why, let me just quickly state that, sure, tmux and friends will always have their uses: on remote servers for people who insist on running their daemons inside a screen session like it’s 1997 (bonus points if it’s an IRC client); and of course for actual terminal use on machines where Emacs is not available, or where you have to work with others who may not know Emacs.

Caveats aside, let me share why I think you should consider using Emacs for some or all of your multiplexing needs, as it can do everything from detaching and re-attaching sessions; to windowing; to terminal emulation and running shells; and even the odd bit of text editing…

The Perils of Terminal Programs

Naturally, terminal multiplexers are meant for terminals, and as such, I’ll talk exclusively about running Emacs in a terminal from now on, although nearly all the advice I write applies equally to GUI Emacs. My thoughts on terminal Emacs are well known if you’re a frequent reader, but I will restate them again by saying that running GUI Emacs is a far better proposition than running Emacs in a terminal (multiplexer or not) for the following simple reasons:

Key Bindings often clash with your terminal

It’s such a common problem for beginners: they fire up Emacs in a terminal, and wonder why some keys don’t work at all. The answer is usually the terminal tool itself. You’ll have to disable their invasive overreach of your key bindings so they pass through to the terminal, but now you lose said key bindings as you may want to use them elsewhere.

Not all key bindings work in a terminal

Despite a lot of bolted-on protocols, additions and extensions made over the years, the fact of the matter is that some keys are simply not possible to represent and transmit to a terminal and the underlying program. This is another common newbie trap.

Lack of fidelity

You cannot view PDFs, use images, or rice your Emacs with fancy SVG icons; some terminal emulators have limited color bit depth. The value of this is debatable, but I find the ability to view PDFs and images useful.

Flow Control Chaos

And the ultimate trap of them all: the ossified flow control feature that some terminals still enable by default. Type Control+s in some terminal setups, and your terminal freezes, so as to give you enough time to read the text before it can scroll by. Control+q thaws your terminal. A fantastic feature when you’re downloading Dr. Who fan scripts from a dusty newsgroup back in the 1980s. Today… not so much. You can permanently disable flow control with stty -ixon, but it’s just one more thing to think about.

There’s one more reason not to run terminal Emacs inside a terminal multiplexer and that is… well, everything, actually. The multiplexer gets first dips on the ANSI escape codes terminals use to move the cursor and draw stuff to the screen, and it’s oh-so-easy to break or gum things up. Mismatched colors; loss of color bit-depth; keys not getting translated properly; keys intercepted and repurposed by the multiplexer; weird scrollback and buffering issues; the list goes on and on.

Terminal multiplexers are awesome, but they’re also faced with a tricky problem: they must work with nearly everything in a way that is sensible and consistent and often across a wide range of terminal capabilities and platforms. Sure, you can tweak this stuff: do you really want to, though?

So those are the downsides of terminal Emacs with or without the lather of a multiplexer messing with your escape codes. But as annoying as these issues are, terminal Emacs is a full-fledged Emacs experience otherwise.

Now let’s replicate the multiplexing experience inside Emacs.

Emacs’s Client-Server Architecture

Of course Emacs has a client-server architecture. It’s a text editor!

Run M-x server-start in an active Emacs instance and your current Emacs becomes the server host. Note that there are multiple ways of running Emacs in server mode. If you use emacs --daemon to start the daemon, you’ll get a persistent daemon that does not spawn a frame; you must then type emacsclient -c to connect to your Emacs. A third way is systemctl --user enable emacs and you can let systemd run Emacs as a daemon.

Either way, from that moment on, if you call emacsclient, Emacs will talk to your Emacs server. This is how experienced hackers prefer to use Emacs, but you do not have to, of course. If you run emacs it will launch a standalone instance. You can even mix and match.

Now you can set emacsclient to your $EDITOR and everything Just Works.

NOTE: Emacs’s client-server architecture is not intended to straddle multiple computers; both should be run on the same machine, lest you run into weird, hard-to-debug problems. If you want to use Emacs ‘remotely’, you can use Tramp or ssh to your target machine and run Emacs as a client-server architecture there.

Running Programs from Emacs

Obviously this is the main purpose of a multiplexer, and Emacs is more than capable of doing this. Running instances of your favorite shell is the most common, so let’s start with that.

Shells and Terminal Emulators

Command Description
M-x shell

Runs your preferred shell via a pseudo-terminal or pipe. Uses Emacs keys, Emacs for scrollback, its own history, better search and a plain Emacs buffer.

Limited emulation; cannot handle curses apps without third-party packages like xterm-color.

M-x term Emacs’s builtin terminal emulator. Can switch between traditional terminal mode and a mode that makes it temporarily behave like an Emacs buffer. Can be quite slow.
M-x vterm

Uses libvterm to provide a modern, fast and compliant terminal emulator.

NOTE: Requires a third-party package.

M-x eat

EAT is another terminal emulator that plugs into Eshell and also has its own terminal mode. Very fast.

NOTE: Requires a third-party package.

M-x eshell

Not a terminal emulator, but instead a replacement for your preferred shell. It’s loosely inspired by zsh and friends, and is written entirely in Emacs Lisp.

NOTE: Eshell is a pathway to many abilities some consider to be unnatural.

My article on Running Shells and Terminal Emulators in Emacs is a more in-depth look at these options. If you want a near-terminal-like experience, then vterm is a great package. If you want to lean into the Emacs way of doing things, then M-x shell is a good choice, and that is what I mostly use, unless I absolutely have to run a curses app (then I use M-x vterm.) If you are unsure, then I recommend you install vterm and start with that.

Another way – the more Emacsy way – is to instead consider that most things may not require a shell (and a terminal) at all for common tasks. If you just want to compile stuff (or run your development server, test harness, etc. etc.), then compiling and running scripts in Emacs is the way to go. You gain the added benefit of error highlighting (with hyperlinks to the erroneous file) that works with darn near every structured error output you can throw at it.

But wait, there’s more: you can run a range of command line network utilities; use M-x proced, Emacs’s replacement for top; and use dired, Emacs’s directory editor to do all sorts of cool stuff.

For simple things? You can execute shell commands directly, or even feed buffer text to the standard input of a command line program and get the output piped back. That solves a whole swathe of problems also.

Interacting with fzf

Not really multiplexer related per se, so I’m going to point out that you can use Emacs for fuzzy finding instead of fzf using a fistful of shell script code. Kinda cool.

Starting programs from outside or inside Emacs

Another common thing to do with a multiplexer is to quickly open a program inside a running tmux instance; perhaps from a shell pane inside your tmux instance, or perhaps outside it.

You can do this with Emacs also (with M-x vterm if it’s installed, otherwise M-x term) with the provided bash script. Let’s call it emux.sh:

#!/usr/bin/env bash
set -o nounset -o errexit -o pipefail
argcmd="$@"
cmd="${argcmd:=$SHELL}"
exec emacsclient -e <<EOF
(let ((vterm-shell "${cmd}")) (if (fboundp 'vterm) (vterm t) (term vterm-shell)))
EOF

Here I temporarily bind the Emacs variable vterm-shell to the command to run (it defaults to your shell). Then I invoke vterm (if it’s installed, otherwise I use term) and start a new instance.

To use it is simple:

  • ./emux.sh top runs top inside your active Emacs instance.
  • ./emus.sh watch free -hg calls watch and tells it to run free -hg.
  • ./emux.sh on its own will call your $SHELL instead.

All from the comfort of your Emacs. You don’t have to run this from inside an Emacs instance, either. Provided you’ve got the client-server feature enabled, it’ll work anywhere.

Detaching and Attaching & Long-running programs

By running programs in an Emacs server instance, you can leave them running even if you hang up your remote connection (such as when you disconnect from ssh) but that does depend a bit on the method you used to start your server. I recommend you daemonize it with emacs --daemon, or run it through systemd, to ensure this works.

Your Emacs server remembers everything: processes, sockets, buffers — you name it. You can even have multiple daemons if you really, really want to.

So if your workflow depends heavily on the ability to engage and disengage from a ‘running’ session of work, then Emacs can definitely do this.

If you regularly work on remote servers, a lot of your work could be done from your local Emacs by talking to the remote server directly with Emacs’s Tramp.

Panes, Windows, Buffers, Frames

Emacs’s windowing technology is so far ahead of every other terminal or GUI application that I’d struggle to even begin to cover all the things you can do with it. Emacs is a tiling window manager onto itself (and some do indeed use it as their tiling window manager!), with hundreds of commands and variables that govern how it works. It also has frames (Emacs’s name for what most people call windows), if you prefer a non-tiling approach.

I’ve made a handy table of the window-related nomenclature used in screen and tmux and of course Emacs. They do differ, so let’s just quickly cover that.

Term Emacs tmux GNU Screen
Frame The entire Emacs instance visible on screen, can contain multiple windows The entire terminal instance running tmux, equivalent to the terminal window The entire terminal instance running Screen, equivalent to the terminal window
Window A subdivision within a frame that displays one buffer A full-screen view within a session that can be divided into panes (similar to tabs) A full-screen virtual terminal identified by a number or name
Buffer Container for text being edited, displayed within windows No direct equivalent (closest is the content displayed in a pane) No direct equivalent (closest is the content displayed in a window)
Pane No direct equivalent (similar to windows) Subdivision of a window that contains a separate terminal instance Called “regions” — subdivisions of the display when split screen is used
Session Another term for an Emacs server instance A collection of windows under a single server instance that can be detached/reattached A detachable collection of windows running as a single process

Of note is that in Emacs, a frame is of course what most people would call a window, but there’s another dimension to it: frames behave differently in a terminal Emacs session.

In terminal Emacs, a frame is really more like window in tmux or screen. Only one is ever visible at a time; in GUI Emacs, you can have many visible at the same time, subject to the whims of your window manager (tiling vs stacking for example) and Emacs has another set of complex commands for interacting with frames specifically. Emacs does its level best to provide parity between the GUI and terminal versions, but this is one area where there is some difference in appearance and behavior.

Now let’s talk about – let’s call it “windowing features” for want of a better phrase – across the multiplexers and Emacs. There’s an awful lot of asterisks attached to some of these, but more on that below.

tmux Feature screen Key Binding tmux Key Binding Emacs Key Binding
Create new window C-a c C-b c C-x 2, C-x 3 (splits instead)
Close current window C-a K C-b & C-x 0 (close window), C-x k (kill buffer)
Rename current window C-a A C-b , M-x rename-buffer or C-x x r
List all windows C-a " C-b w C-x b (switch to) or C-x C-b
Next window C-a n C-b n C-x o (other window)
Previous window C-a p C-b p N/A but you can fiddle with numeric arguments to C-x o to simulate it, or use windmove.
Go to window by number C-a [0-9] C-b [0-9] Registers can sort of stand in for this, but there is no simple equivalent.
Go to last active window C-a C-a C-b l Maybe M-x mode-line-other-buffer
Split window horizontally C-a S (added later) C-b " C-x 2
Split window vertically C-a | (added later) C-b % C-x 3
Move to next pane C-a tab C-b o C-x o
Convert pane to window N/A C-b ! N/A
Show pane numbers N/A C-b q N/A
Toggle between pane layouts N/A C-b space Window Configurations; M-x tab-bar-mode
Resize pane N/A (not as intuitive) C-b C-[arrow keys] C-x { / C-x }
Break pane into window N/A C-b ! N/A
Join pane from window N/A C-b :join-pane -s [window] N/A
Enter copy mode C-a [ C-b [ N/A
Search in copy mode / (forward), ? (backward) / (forward), ? (backward) C-s (forward), C-r (backward)
Swap window positions C-a :number [n] C-b . (prompt for new number) Use Windmove
Detach from session C-a d C-b d C-x # (if in an emacs client)
Choose a session N/A (via command line) C-b s Technically a thing, but multiple daemons is a rarely used feature.
Zoom pane (toggle full-screen) N/A C-b z C-x 1
Rotate panes N/A C-b C-o N/A
Display clock C-a t C-b t M-x display-time
Command prompt C-a : C-b : M-x

tmux and screen are not trying to be Emacs, and Emacs did not set out to replace either of these tools — it’s just that it is so advanced that it can step into the role of multiplexer with relative ease.

Windows in Emacs are transient and considered disposable, and the commands are sort of built around that idea. That is a stark difference to other IDEs and editors, but consider that this is a complex topic with a lot of opinions. Luckily, Emacs can accommodate most of people’s complex windowing requirements, if you’re willing to learn how Emacs’s Window Manager works. As such swapping windows or referencing them by number is not really an Emacsy thing to do, though you can with a little bit of code (or using a third-party package like avy), or perhaps by bending registers to your will.

In Emacs you have to contend with buffers first and foremost, as that is the currency of Emacs. Buffers, though, have no real equivalents in the multiplexers, as you can preserve the window layout in Emacs but change their buffers to show something else (though you technically can do this in tmux, it’s clearly not a well-trodden path) so in Emacs I’ve crammed both buffer and window switching commands into the mix. I would be remiss if I did not also mention both M-x global-tab-line-mode, for browser-style tabs; and M-x tab-bar-mode to set up and switch between window configurations (“pane layouts” in tmux), but more on that below.

The builtin package windmove adds ortographic movement from the current active window (for example, C-x <up> to pick the window above the current one) as opposed to layout-driven keys like C-x o that cycle through the windows on your screen. It’s a useful package, and one a lot of people use to make movement between windows more intuitive and faster.

Detaching / attaching to a session is best done by explicitly launching a server daemon (as I mentioned before) with emacs --daemon and then connecting and disconnecting (C-x #) at will with emacsclient.

Because GUI frames and terminal frames behave differently, I have opted to use a mix of buffer and window commands to represent the pane/window commands as that is ultimately what you are going to use in Emacs. But that’s obviously not the only way of looking at this: if you want to mirror the behavior of screen or tmux you can use Emacs’s frames for some of this:

Operation Terminal Emacs Frame tmux GNU Screen
Create new frame C-x 5 2 C-b c (new window) C-a c (new window)
Switch to next frame C-x 5 o C-b n (next window) C-a n (next window)
Previous frame N/A C-b p (previous window) C-a p (previous window)
Close current frame C-x 5 0 C-b & (kill window) C-a K (kill window)
Open file in new frame C-x 5 f C-b c C-a c
Open buffer in new frame C-x 5 b C-b c C-a c
List all frames M-x select-frame-by-name C-b w (list windows) C-a " (list windows)
Rename current frame M-x set-frame-name C-b , (rename window) C-a A (rename window)

Personally, I think the frame approach is an average facsimile, and that it’s better to just embrace Emacs’s way of doing things, instead of shoehorning frames into the multiplexers’ notion of “windows”.

For that, I recommend you just use M-x tab-bar-mode. Each tab is its own window configuration, and you can interact with tabs in many ways. Note again that tab bars are not like tab lines, the latter being more like a browser’s open tabs.

Operation Emacs tab-bar-mode Key Binding
Enable tab-bar-mode M-x tab-bar-mode
Create new tab C-x t 2 or C-x t n
Close current tab C-x t 0 or C-x t k
Rename current tab C-x t r
Switch to next tab C-x t o or C-TAB
Switch to previous tab C-S-TAB
Switch to tab by name C-x t RET
Duplicate current tab C-x t n
List all tabs M-x tab-switcher or M-x tab-list
Open file in new tab C-x t f

So that’s yet another way to do it. This is my preferred approach: it’s consistent across GUI and terminal Emacs, and it’s a more ergonomic way of interacting with window setups in Emacs. It is also a close approximation of how your terminal multiplexer treats distinct “windows”.

Prefix Keys

The problem with screen and tmux is that their default prefix keys override two rather useful Emacs key bindings that also work everywhere GNU Readline is used: C-a, to move to the beginning of a line; and C-b to move backward a character.

In Emacs, that obviously isn’t a problem, as the key bindings you’ll need are scattered across a range of keys, and in any event, you’ll have full control over the keys available to you inside Emacs and which ones are sent to the underlying process, should you choose to use Emacs as your terminal emulator.

Emacs is a full-fledged text editor, and it has scrollback everywhere, so there’s not much more to say about that. Selecting (marking) and copying is available everywhere, and Emacs’s terminal emulators can switch from “terminal mode” to “cooked” (meaning it behaves like a regular Emacs buffer with freeform navigation) so you can copy and search as you please. Now is the time mention occur mode, one of many ways of sifting through text. It lets you regexp search in Buffers with a handy popup window of the matches. Terminal multiplexers can’t do that without plugins.

My Workflow

Workflow are personal things, but I’ll quickly talk about how I use Emacs in lieu of a multiplexer.

  1. I prefer to do everything local to my machine, where feasible.

    For remote sessions, I will use ssh in a M-x shell instance (unless I have to interface with curses apps, then I use M-x vterm) and combine it with Tramp for file editing.

  2. I avoid running Emacs on a remote machine directly because I will always opt for local, as I mentioned in #1. But when I have to, I will run emacs in a client-server architecture. Having said that, it is very rare that I cannot do 90% of my work from my local machine.

    For things like docker containers or even talking to kubernetes pods, I can do this with Tramp also. I can also ssh to a bastion host and then onwards to root with sudo and then perhaps into a container with docker. I can bookmark these things in Emacs so I can return to them with the snap of a finger.

  3. Emacs’s Tramp can execute command line programs remotely for you. If you’re editing a remote file in a buffer, then M-x dired (C-x d) and tools like M-x compile, M-x grep, etc. automatically call out to their remote counterpart and return the results back to your local Emacs instance. Very powerful.

  4. M-x shell is the right tool for me. It’s a ‘dumb terminal’ in the sense that everything is fed into an Emacs buffer which is then enriched with features such as history, prompt detection, and more. Works great, except when it does not. However, I can live with the tradeoffs. I prefer a free-flowing buffer with all the advantages and disadvantages that brings. For example, I can type M-s h p (M-x highlight-phrase) and highlight stuff in the buffer by regexp. Emacs will highlight current and future instances.

    I’m sure you’re thinking of all the many, many instances in your career where you wished you could do such a simple, simple thing like highlight stuff.

  5. Watching logs and other changing files. Again with Tramp: open the file then M-x auto-revert-mode or M-x auto-revert-tail-mode and Emacs will revert the file when it changes. Simple.

And that’s just some of the things I do. The main thing to take away from my workflow – and this article – is that things orbit around Emacs, because I am proficient at using Emacs, and it is capable of a great many things.

Honestly? Thrashing around on the command line – no matter your shell, even with nice ones like fish – feels like an enormous step back in productivity compared to using Emacs and regular old bash.

But that’s me. You’ll build your own workflow that meets your needs.

Conclusion

If you’re familiar with Emacs then most of my advice should not come as a surprise to you. If you’re new or still learning, then I hope this will give you some food for thought.

You can pare back your use of your if it makes sense to do so: but most of the day-to-day grind I see tmux users do can easily be done from within Emacs, but with a real gain to your productivity. Switching to Emacs just to replace your terminal multiplexer is perhaps a harder sell, but if you can learn and memorize your multiplexer’s commands, then you’ll do just fine learning Emacs also.

Let me know in the comments if ditching a multiplexer helps (or hinders!) your workflow!

Medium

https://medium.com/@TusharKanjariya/i-used-the-terminal-wrong-for-years-5557b10c0b85

I Used the Terminal Wrong for Years

What senior devs do differently in the terminal

I watched a senior dev fix a deploy issue in under 90 seconds.

Read Free for non-members.

No Googling. No copy-pasting from a cheat sheet. Just flowing through the terminal like it was a conversation.

I thought he had some secret toolset. He didn’t. He just knew the terminal better than me.

Press enter or click to view image in full size
I Used the Terminal Wrong for Years

The Problem Most Developers Have

Most developers use the terminal the same way they learned it in their first tutorial: type a command, hit enter, repeat.

That works.

But it’s slow.

And over time, those small delays turn into real friction.

What I didn’t realize was this:

The terminal is not just a tool. It’s an environment.

Once you treat it like that, everything changes.

Here are the tricks I actually use every day.

Not 50 commands.

Just the ones that stick.

Trick #1: Stop Retyping Commands You Already Ran

Extract PDF text in your browser with LiteParse for the web

https://simonwillison.net/2026/Apr/23/liteparse-for-the-web/

Extract PDF text in your browser with LiteParse for the web

23rd April 2026

LlamaIndex have a most excellent open source project called LiteParse, which provides a Node.js CLI tool for extracting text from PDFs. I got a version of LiteParse working entirely in the browser, using most of the same libraries that LiteParse uses to run in Node.js.

Spatial text parsing

Refreshingly, LiteParse doesn’t use AI models to do what it does: it’s good old-fashioned PDF parsing, falling back to Tesseract OCR (or other pluggable OCR engines) for PDFs that contain images of text rather than the text itself.

The hard problem that LiteParse solves is extracting text in a sensible order despite the infuriating vagaries of PDF layouts. They describe this as “spatial text parsing”—they use some very clever heuristics to detect things like multi-column layouts and group and return the text in a sensible linear flow.

The LiteParse documentation describes a pattern for implementing Visual Citations with Bounding Boxes. I really like this idea: being able to answer questions from a PDF and accompany those answers with cropped, highlighted images feels like a great way of increasing the credibility of answers from RAG-style Q&A.

LiteParse is provided as a pure CLI tool, designed to be used by agents. You run it like this:

npm i -g @llamaindex/liteparse
lit parse document.pdf

I explored its capabilities with Claude and quickly determined that there was no real reason it had to stay a CLI app: it’s built on top of PDF.js and Tesseract.js, two libraries I’ve used for something similar in a browser in the past.

The only reason LiteParse didn’t have a pure browser-based version is that nobody had built one yet...

Introducing LiteParse for the web

Visit https://simonw.github.io/liteparse/ to try out LiteParse against any PDF file, running entirely in your browser. Here’s what that looks like:

Screenshot of the LiteParse browser demo web page. Header reads "LiteParse" with subtitle "Browser demo of LiteParse — parse PDFs in your browser. Nothing leaves your machine." A dashed-border drop zone says "Drop a PDF here or click to choose / Your file stays in your browser." with a file pill labeled "19720005243.pdf". Below are a checked "Run OCR" checkbox, an unchecked "Render page screenshots" checkbox, and a blue "Parse" button. Status text: "Parsed 86 pages." Two side-by-side panels follow. Left panel titled "Text" with a Copy button shows monospace extracted text beginning "Apollo 5 was an unmanned system, both propulsion systems ascent and descent stages". Right panel titled "JSON", also with a copy button, contains JSON showing the dimensions and position and detected font of each piece of text.

The tool can work with or without running OCR, and can optionally display images for every page in the PDF further down the page.

Building it with Claude Code and Opus 4.7

The process of building this started in the regular Claude app on my iPhone. I wanted to try out LiteParse myself, so I started by uploading a random PDF I happened to have on my phone along with this prompt:

Clone https://github.com/run-llama/liteparse and try it against this file

Regular Claude chat can clone directly from GitHub these days, and while by default it can’t access most of the internet from its container it can also install packages from PyPI and npm.

I often use this to try out new pieces of open source software on my phone—it’s a quick way to exercise something without having to sit down with my laptop.

You can follow my full conversation in this shared Claude transcript. I asked a few follow-up questions about how it worked, and then asked:

Does this library run in a browser? Could it?

This gave me a thorough enough answer that I was convinced it was worth trying getting that to work for real. I opened up my laptop and switched to Claude Code.

I forked the original repo on GitHub, cloned a local copy, started a new web branch and pasted that last reply from Claude into a new file called notes.md. Then I told Claude Code:

Get this working as a web app. index.html, when loaded, should render an app that lets users open a PDF in their browser and select OCR or non-OCR mode and have this run. Read notes.md for initial research on this problem, then write out plan.md with your detailed implementation plan

I always like to start with a plan for this kind of project. Sometimes I’ll use Claude’s “planning mode”, but in this case I knew I’d want the plan as an artifact in the repository so I told it to write plan.md directly.

This also means I can iterate on the plan with Claude. I noticed that Claude had decided to punt on generating screenshots of images in the PDF, and suggested we defer a “canvas-encode swap” to v2. I fixed that by prompting:

Update the plan to say we WILL do the canvas-encode swap so the screenshots thing works

After a few short follow-up prompts, here’s the plan.md I thought was strong enough to implement.

I prompted:

build it.

And then mostly left Claude Code to its own devices, tinkered with some other projects, caught up on Duolingo and occasionally checked in to see how it was doing.

I added a few prompts to the queue as I was working. Those don’t yet show up in my exported transcript, but it turns out running rg queue-operation --no-filename | grep enqueue | jq -r '.content' in the relevant ~/.claude/projects/ folder extracts them.

Here are the key follow-up prompts with some notes:

  • When you implement this use playwright and red/green TDD, plan that too—I’ve written more about red/green TDD here.
  • let's use PDF.js's own renderer (it was messing around with pdfium)
  • The final UI should include both the text and the pretty-printed JSON output, both of those in textareas and both with copy-to-clipboard buttons - it should also be mobile friendly—I had a new idea for how the UI should work
  • small commits along the way—see below
  • Make sure the index.html page includes a link back to https://github.com/run-llama/liteparse near the top of the page—it’s important to credit your dependencies in a project like this!
  • View on GitHub → is bad copy because that's not the repo with this web app in, it's the web app for the underlying LiteParse library
  • Run OCR should be unchecked by default
  • When I try to parse a PDF in my browser I see 'Parse failed: undefined is not a function (near '...value of readableStream...')—it was testing with Playwright in Chrome, turned out there was a bug in Safari
  • ... oh that is in safari but it works in chrome
  • When "Copy" is clicked the text should change to "Copied!" for 1.5s
  • [Image #1] Style the file input so that long filenames don't break things on Firefox like this - in fact add one of those drag-drop zone UIs which you can also click to select a file—dropping screenshots in of small UI glitches works surprisingly well
  • Tweak the drop zone such that the text is vertically centered, right now it is a bit closer to the top
  • it breaks in Safari on macOS, works in both Chrome and Firefox. On Safari I see "Parse failed: undefined is not a function (near '...value of readableStream...')" after I click the Parse button, when OCR is not checked—it still wasn’t working in Safari...
  • works in safari now —but it fixed it pretty quickly once I pointed that out and it got Playwright working with that browser

I’ve started habitually asking for “small commits along the way” because it makes for code that’s easier to understand or review later on, and I have an unproven hunch that it helps the agent work more effectively too—it’s yet another encouragement towards planning and taking on one problem at a time.

While it was working I decided it would be nice to be able to interact with an in-progress version. I asked a separate Claude Code session against the same directory for tips on how to run it, and it told me to use npx vite. Running that started a development server with live-reloading, which meant I could instantly see the effect of each change it made on disk—and prompt with further requests for tweaks and fixes.

Towards the end I decided it was going to be good enough to publish. I started a fresh Claude Code instance and told it:

Look at the web/ folder - set up GitHub actions for this repo such that any push runs the tests, and if the tests pass it then does a GitHub Pages deploy of the built vite app such that the web/index.html page is the index.html page for the thing that is deployed and it works on GitHub Pages

After a bit more iteration here’s the GitHub Actions workflow that builds the app using Vite and deploys the result to https://simonw.github.io/liteparse/.

I love GitHub Pages for this kind of thing because it can be quickly configured (by Claude, in this case) to turn any repository into a deployed web-app, at zero cost and with whatever build step is necessary. It even works against private repos, if you don’t mind your only security being a secret URL.

With this kind of project there’s always a major risk that the model might “cheat”—mark key features as “TODO” and fake them, or take shortcuts that ignore the initial requirements.

The responsible way to prevent this is to review all of the code... but this wasn’t intended as that kind of project, so instead I fired up OpenAI Codex with GPT-5.5 (I had preview access) and told it:

Describe the difference between how the node.js CLI tool runs and how the web/ version runs

The answer I got back was enough to give me confidence that Claude hadn’t taken any project-threatening shortcuts.

... and that was about it. Total time in Claude Code for that “build it” step was 59 minutes. I used my claude-code-transcripts tool to export a readable version of the full transcript which you can view here, albeit without those additional queued prompts (here’s my issue to fix that).

Is this even vibe coding any more?

I’m a pedantic stickler when it comes to the original definition of vibe coding—vibe coding does not mean any time you use AI to help you write code, it’s when you use AI without reviewing or caring about the code that’s written at all.

By my own definition, this LiteParse for the web project is about as pure vibe coding as you can get! I have not looked at a single line of the HTML and TypeScript written for this project—in fact while writing this sentence I had to go and check if it had used JavaScript or TypeScript.

Yet somehow this one doesn’t feel as vibe coded to me as many of my other vibe coded projects:

  • As a static in-browser web application hosted on GitHub Pages the blast radius for any bugs is almost non-existent: it either works for your PDF or doesn’t.
  • No private data is transferred anywhere—all processing happens in your browser—so a security audit is unnecessary. I’ve glanced once at the network panel while it’s running and no additional requests are made when a PDF is being parsed.
  • There was still a whole lot of engineering experience and knowledge required to use the models in this way. Identifying that porting LiteParse to run directly in a browser was critical to the rest of the project.

Most importantly, I’m happy to attach my reputation to this project and recommend that other people try it out. Unlike most of my vibe coded tools I’m not convinced that spending significant additional engineering time on this would have resulted in a meaningfully better initial release. It’s fine as it is!

I haven’t opened a PR against the origin repository because I’ve not discussed it with the LiteParse team. I’ve opened an issue, and if they want my vibe coded implementation as a starting point for something more official they’re welcome to take it.

BSB: The Power of Friendship

https://www.baseballprospectus.com/news/article/107129/bsb-the-power-of-friendship/

Image credit: © Vincent Carchietta-Imagn Images

Even when they win, the Mets have to be the Mets.

Notes on clarifying man pages

https://jvns.ca/blog/2026/02/18/man-pages/

Hello! After spending some time working on the Git man pages last year, I’ve been thinking a little more about what makes a good man page.

I’ve spent a lot of time writing cheat sheets for tools (tcpdump, git, dig, etc) which have a man page as their primary documentation. This is because I often find the man pages hard to navigate to get the information I want.

Lately I’ve wondering – could the man page itself have an amazing cheat sheet in it? What might make a man page easier to use? I’m still very early in thinking about this but I wanted to write down some quick notes.

I asked some people on Mastodon for their favourite man pages, and here are some examples of interesting things I saw on those man pages.

If you’ve read a lot of man pages you’ve probably seen something like this in the SYNOPSIS: once you’re listing almost the entire alphabet, it’s hard

ls [-@ABCFGHILOPRSTUWabcdefghiklmnopqrstuvwxy1%,]

grep [-abcdDEFGHhIiJLlMmnOopqRSsUVvwXxZz]

The rsync man page has a solution I’ve never seen before: it keeps its SYNOPSIS very terse, like this:

 Local:
     rsync [OPTION...] SRC... [DEST]

and then has an “OPTIONS SUMMARY” section with a 1-line summary of each option, like this:

--verbose, -v            increase verbosity
--info=FLAGS             fine-grained informational verbosity
--debug=FLAGS            fine-grained debug verbosity
--stderr=e|a|c           change stderr output mode (default: errors)
--quiet, -q              suppress non-error messages
--no-motd                suppress daemon-mode MOTD

Then later there’s the usual OPTIONS section with a full description of each option.

The strace man page organizes its options by category (like “General”, “Startup”, “Tracing”, and “Filtering”, “Output Format”) instead of alphabetically.

As an experiment I tried to take the grep man page and make an “OPTIONS SUMMARY” section grouped by category, you can see the results here. I’m not sure what I think of the results but it was a fun exercise. When I was writing that I was thinking about how I can never remember the name of the -l grep option. It always takes me what feels like forever to find it in the man page and I was trying to think of what structure would make it easier for me to find. Maybe categories?

A couple of people pointed me to the suite of Perl man pages (perlfunc, perlre, etc), and one thing I noticed was man perlcheat, which has cheat sheet sections like this:

 SYNTAX
 foreach (LIST) { }     for (a;b;c) { }
 while   (e) { }        until (e)   { }
 if      (e) { } elsif (e) { } else { }
 unless  (e) { } elsif (e) { } else { }
 given   (e) { when (e) {} default {} }

I think this is so cool and it makes me wonder if there are other ways to write condensed ASCII 80-character-wide cheat sheets for use in man pages.

A common comment was something to the effect of “I like any man page that has examples”. Someone mentioned the OpenBSD man pages, and the openbsd tail man page has examples of the exact 2 ways I use tail at the end.

I think I’ve most often seen the EXAMPLES section at the end of the man page, but some man pages (like the rsync man page from earlier) start with the examples. When I was working on the git-add and git rebase man pages I put a short example at the beginning.

This isn’t a property of the man page itself, but one issue with man pages in the terminal is it’s hard to know what sections the man page has.

When working on the Git man pages, one thing Marie and I did was to add a table of contents to the sidebar of the HTML versions of the man pages hosted on the Git site.

I’d also like to add more hyperlinks to the HTML versions of the Git man pages at some point, so that you can click on “INCOMPATIBLE OPTIONS” to get to that section. It’s very easy to add links like this in the Git project since Git’s man pages are generated with AsciiDoc.

I think adding a table of contents and adding internal hyperlinks is kind of a nice middle ground where we can make some improvements to the man page format (in the HTML version of the man page at least) without maintaining a totally different form of documentation. Though for this to work you do need to set up a toolchain like Git’s AsciiDoc system.

It would be amazing if there were some kind of universal system to make it easy to look up a specific option in a man page (“what does -a do?”). The best trick I know is use the man pager to search for something like ^ *-a but I never remember to do it and instead just end up going through every instance of -a in the man page until I find what I’m looking for.

The curl man page has examples for every option, and there’s also a table of contents on the HTML version so you can more easily jump to the option you’re interested in.

For instance the example for --cert makes it easy to see that you likely also want to pass the --key option, like this:

  curl --cert certfile --key keyfile https://example.com

The way they implement this is that there’s [one file for each option](https://github.com/curl/curl/blob/dc08922a61efe546b318daf964514ffbf41583 25/docs/cmdline-opts/append.md) and there’s an “Example” field in that file.

Quite a few people said that man ascii was their favourite man page, which looks like this:

 Oct   Dec   Hex   Char                     
 ───────────────────────────────────────────
 000   0     00    NUL '\0' (null character)
 001   1     01    SOH (start of heading)   
 002   2     02    STX (start of text)      
 003   3     03    ETX (end of text)        
 004   4     04    EOT (end of transmission)
 005   5     05    ENQ (enquiry)            
 006   6     06    ACK (acknowledge)        
 007   7     07    BEL '\a' (bell)          
 010   8     08    BS  '\b' (backspace)     
 011   9     09    HT  '\t' (horizontal tab)
 012   10    0A    LF  '\n' (new line)      

Obviously man ascii is an unusual man page but I think what’s cool about this man page (other than the fact that it’s always useful to have an ASCII reference) is it’s very easy to scan to find the information you need because of the table format. It makes me wonder if there are more opportunities to display information in a “table” in a man page to make it easier to scan.

When I talk about man pages it often comes up that the GNU coreutils man pages (for example man tail) don’t have examples, unlike the OpenBSD man pages, which do have examples.

I’m not going to get into this too much because it seems like a fairly political topic and I definitely can’t do it justice here, but here are some things I believe to be true:

  • The GNU project prefers to maintain documentation in “info” manuals instead of man pages. This page says “the man pages are no longer being maintained”.
  • There are 3 ways to read “info” manuals: their HTML version, in Emacs, or with a standalone info tool. I’ve heard from some Emacs users that they like the Emacs info browser. I don’t think I’ve ever talked to anyone who uses the standalone info tool.
  • The info manual entry for tail is linked at the bottom of the man page, and it does have examples
  • The FSF used to sell print books of the GNU software manuals (and maybe they still do sometimes?)

After a certain level of complexity a man page gets really hard to navigate: while I’ve never used the coreutils info manual and probably won’t, I would almost certainly prefer to use the GNU Bash reference manual or the The GNU C Library Reference Manual via their HTML documentation rather than through a man page.

Here are some tools I think are interesting:

  • The fish shell comes with a Python script to automatically generate tab completions from man pages
  • tldr.sh is a community maintained database of examples, for example you can run it as tldr grep. Lots of people have told me they find it useful.
  • the Dash Mac docs browser has a nice man page viewer in it. I still use the terminal man page viewer but I like that it includes a table of contents, it looks like this:

Man pages are such a constrained format and it’s fun to think about what you can do with such limited formatting options.

Even though I’m very into writing I’ve always had a bad habit of never reading documentation and so it’s a little bit hard for me to think about what I actually find useful in man pages, I’m not sure whether I think most of the things in this post would improve my experience or not. (Except for examples, I LOVE examples)

So I’d be interested to hear about other man pages that you think are well designed and what you like about them, the comments section is here.

How to remove all files ending with ~ made by Emacs

https://stackoverflow.com/questions/2680389/how-to-remove-all-files-ending-with-made-by-emacs

While all the others answers here correctly explain how to remove the files, you ought to understand what's going on. Those files ending in ~ are backup files, automatically created by Emacs. They can be useful sometimes. If you're annoyed by the files and want to delete them every time, then you either

(1). prevent the creation of backup files:

(setq make-backup-files nil)

or

(2). Have it save the backup files in some other directory, where they won't bother you unless you go looking for them. I have the following in my .emacs:

(setq backup-directory-alist '(("." . "~/.emacs.d/backup"))
  backup-by-copying t    ; Don't delink hardlinks
  version-control t      ; Use version numbers on backups
  delete-old-versions t  ; Automatically delete excess backups
  kept-new-versions 20   ; how many of the newest versions to keep
  kept-old-versions 5    ; and how many of the old
  )

(Only the first line is crucial.) To see documentation about backup-directory-alist, type C-h v backup-directory-alist.

`uniq` without sorting an immense text file?

https://stackoverflow.com/questions/30906191/uniq-without-sorting-an-immense-text-file

The awk '!x[$0]++' trick is one of the most elegant solutions to de-duplicate a file or stream without sorting. However, it is inefficient in terms of memory and unsuitable for large files, since it saves all unique lines into memory.

However, a much more efficient implementation would be to save a constant-length hash representation of the lines in the array rather than the whole line. You can achieve this with Perl in one line and it is quite similar to the awk script.

perl -ne 'use Digest::MD5 qw(md5_base64); print unless $seen{md5_base64($_)}++' huge.txt

Here I used md5_base64 instead of md5_hex because the base64 encoding takes 22 bytes, while the hex representation 32.

However, since the Perl implementation of hashes still requires around 120bytes for each key, you may quickly run out of memory for your huge file.

The solution in this case is to process the file in chunks, splitting manually or using GNU Parallel with the --pipe, --keep-order and --block options (taking advantage of the fact that duplicate lines are not far apart, as you mentioned). Here is how you could do it with parallel:

cat huge.txt | pv | 
parallel --pipe --keep-order --block 100M -j4 -q \
perl -ne 'use Digest::MD5 qw(md5_base64); print unless $seen{md5_base64($_)}++' > uniq.txt

The --block 100M option tells parallel to process the input in chunks of 100MB. -j4 means start 4 processes in parallel. An important argument here is --keep-order since you want the unique lines output to remain in the same order. I have included pv in the pipeline to get some nice statistics while the long running process is executing.

In a benchmark I performed with a random-data 1GB file, I reached a 130MB/sec throughput with the above settings, meaning you may de-duplicate your 40GB file in 4 minutes (if you have a sufficiently fast hard disk able to write at this rate).

Other options include:

  • Use an efficient trie structure to store keys and check for duplicates. For example a very efficient implementation is marisa-trie coded in C++ with wrappers in Python.
  • Sort your huge file with an external merge sort or distribution/bucket sort
  • Store your file in a database and use SELECT DISTINCT on an indexed column containing your lines or most efficiently md5_sums of your lines.
  • Or use bloom filters

Here is an example of using the Bloom::Faster module of Perl:

perl -e 'use Bloom::Faster; my $f = new Bloom::Faster({n => 100000000, e => 0.00001}); while(<>) { print unless $f->add($_); }' huge.txt > uniq.txt

You may install Bloom::Faster from CPAN (sudo cpan install "Bloom::Faster")

Explanation:

  • You have to specify the probabilistic error rate e and the number of available buckets n. The memory required for each bucket is about 2.5 bytes. If your file has 100 million unique lines then you will need 100 million buckets and around 260MB of memory.
  • The $f->add($_) function adds the hash of a line to the filter and returns true if the key (i.e. the line here) is a duplicate.
  • You can get an estimation of the number of unique lines in your file, parsing a small section of your file with dd if=huge.txt bs=400M count=1 | awk '!a[$0]++' | wc -l (400MB) and multiplying that number by 100 (40GB). Then set the n option a little higher to be on the safe side.

In my benchmarks, this method achieved a 6MB/s processing rate. You may combine this approach with the GNU parallel suggestion above to utilize multiple cores and achieve a higher throughput.