Skip to content

About

VS Code's preview tab, for Emacs.

Resources

Stars

10 stars

Watchers

0 watching

Forks

Latest commit

 

History

49 Commits

Folders and files

Repository files navigation

preview-tab

MELPA test melpazoid

VS Code's preview tab, for Emacs.

Browsing a project leaves a trail of buffers behind. Every file you glanced at from the file tree, every definition you jumped to, every grep hit you opened sits in the buffer list forever, and C-x b slowly turns into an archive of things you looked at once.

VS Code solves this with the preview tab: a file opened by browsing goes into a single italicised tab, the next such file replaces it, and it only becomes a real tab once you edit it. preview-tab-mode brings that behaviour to Emacs.

preview-tab replacing browsed files and keeping an edited file

(preview-tab-mode 1)

That's it. Now opening a file from Dired, Treemacs, Magit, xref, grep results or consult puts it in one temporary buffer, marked in the mode line. Open the next one and the previous is gone.

The rules

A buffer becomes a preview when a command in preview-tab-commands visits a file that was not already open.

It stops being a preview — permanently — when you edit it (the first change promotes it) or run preview-tab-keep. A file the command has already written into, as Org capture does with a new note, is never a preview at all.

The preview is killed when the next preview takes its place, unless it is modified, visible in another window, or running a process. Those quietly become ordinary buffers instead.

Two things deliberately do not happen, both matching VS Code:

  • A buffer that was already open is never demoted to a preview.
  • Visiting an ordinary buffer does not close the standing preview. It survives until another preview replaces it, not until you look away.

find-file is not a preview source: typing a file name is a deliberate act, and VS Code does not preview from Quick Open either. To peek at a named file, use preview-tab-find-file — or turn the rule off, see Previewing named files.

Commands

Command What it does
preview-tab-mode Turn the whole thing on or off. Turning it off makes every preview permanent.
preview-tab-find-file Visit a file as a preview — the "just let me look at it" counterpart to find-file. Files already open are left alone.
preview-tab-keep Keep the current preview buffer for good.

Suggested bindings:

(global-set-key (kbd "C-c f t") #'preview-tab-find-file)
(global-set-key (kbd "C-c t p") #'preview-tab-keep)

Installation

On MELPA. No dependencies beyond Emacs 27.1.

M-x package-install RET preview-tab RET, or with use-package:

(use-package preview-tab
  :ensure t
  :config (preview-tab-mode 1))

The same works with elpaca; for straight.el, write :straight t instead of :ensure t.

Doom Emacs

Doom's own jump and search commands are in the default list, so nothing beyond the usual two blocks is needed:

;; packages.el
(package! preview-tab)

;; config.el
(use-package! preview-tab
  :config (preview-tab-mode 1))

Then run doom sync and restart Emacs.

That covers +lookup/definition, +lookup/references, +lookup/implementations and +lookup/type-definition, along with the +default/search-* family and the +vertico/, +ivy/ and +helm/ project searches they dispatch to. +lookup/file is left out, like find-file: pointing at a path and asking for it is a deliberate act. It comes along with preview-tab-include-named-files if you want it.

One more Doom default is worth changing. Out of the box, consult shows you the file under point as you move down the candidate list; Doom turns that off, so nothing appears until you press C-SPC on a candidate. To get the automatic display back:

(after! consult
  (consult-customize
   consult-ripgrep consult-git-grep consult-grep consult-recent-file
   consult-source-recent-file consult-source-project-recent-file
   +default/search-project +default/search-cwd
   :preview-key '(:debounce 0.2 any)))

That is consult's own preview, not this package's: it only affects what you see while still choosing, and consult closes those buffers itself when the session ends. preview-tab is unaffected either way — the file you finally pick becomes a preview if the command that opened it is in preview-tab-commands.

Customization

Option Default Meaning
preview-tab-commands Dired, Treemacs, Magit, xref, compile/grep, flymake, consult, Doom Commands whose file visits are previews.
preview-tab-include-named-files nil Whether find-file, magit-find-file, … preview too.
preview-tab-slant-faces vanilla, doom-modeline, centaur-tabs faces Faces italicised while a buffer is a preview. tab-line-mode is handled separately, by preview-tab-tab-line-face.
preview-tab-indicator auto auto, icon, label, both, or nil.
preview-tab-icon "nf-md-eye_outline" nerd-icons Material Design icon name.
preview-tab-label "PREVIEW" Text marker.

Magit

RET on a file in a Magit status or diff buffer runs magit-diff-visit-file, a preview source out of the box — as are its worktree, other-window and other-frame variants.

One caveat, and it is Magit's design rather than this package's: magit-diff-visit-file does not always visit a file. Which side of the diff it opens depends on the line point is on — an added or context line gives the new side, a removed line the old one. For a committed or staged change both sides are blobs, from a commit or from the index; a blob buffer is read-only, visits nothing on disk, and is left alone. Untracked files, and unstaged changes seen from an added or context line, open the real worktree file — as does magit-diff-visit-worktree-file (C-j, or C-<return>) from anywhere. Those become previews.

Previewing named files

Commands that open a file you named — find-file, magit-find-file, org-roam-node-find, org-open-at-point… — are not preview sources. If you'd rather they were:

(setopt preview-tab-include-named-files t)

setopt arrives in Emacs 29. Before that — here and everywhere else this README reaches for it — customize-set-variable does the same job: (customize-set-variable 'preview-tab-include-named-files t).

In Doom, add :custom (preview-tab-include-named-files t) to the use-package! block under Doom Emacs rather than writing a second one. :custom goes through customize-set-variable, which is the machinery this option needs, and it is applied before :config, so the mode comes up already knowing.

That takes in all of them, along with the other-window and other-frame variants of the first two. It also reaches further than the commands it names: a great deal of Emacs opens files by calling find-file, so project-find-file, recentf-open-files and file registers start previewing too. Anything going through find-file-noselect instead is untouched — bookmarks, for one. org-roam-node-find goes that way too, which is why the option names it outright. A title with no note behind it starts a capture instead, and the new note is kept as an ordinary buffer.

I run with this on myself: the wider reach turns out to be the same instinct one step out, not a surprise in kind.

For find-file alone, leave the option off and list it where everything else goes:

(setopt preview-tab-commands (cons #'find-file preview-tab-commands))

Adding your own entry points

Listing a command from a package you have not installed is harmless — the advice attaches to the bare symbol and only fires if that package loads. Add things freely, as long as you do it before turning the mode on:

(add-to-list 'preview-tab-commands #'my-jump-to-thing)
(preview-tab-mode 1)

Afterwards add-to-list won't reattach the advice. Go through the customize machinery instead, which will:

(setopt preview-tab-commands (cons #'my-jump-to-thing preview-tab-commands))

One limit: only files opened while the command itself runs are picked up. A command that hands the visit off to a timer or post-command-hook has already returned by the time the file appears.

The mode-line marker

The preview buffer's path is italicised and a small marker is shown: an eye icon with nerd-icons installed, otherwise the text PREVIEW. Set preview-tab-indicator to both for icon and label, or to nil for italics only — closest to what VS Code actually does. preview-tab-icon takes any name from the nerd-icons Material Design set; one it has no icon for draws nothing and says so in *Warnings*. If your mode line isn't italicised, add its faces to preview-tab-slant-faces.

Under tab-line-mode the preview's tab is italicised as well, and stays that way when you select another tab — the VS Code look. This is done by preview-tab-tab-line-face, which the mode adds to tab-line-tab-face-functions, and it needs Emacs 28.1, where that hook arrived. On Emacs 27 the tabs are not slanted; the mode-line marker is unaffected.

Relationship to consult

consult kills the buffers it opens while you scroll through candidates. preview-tab picks up at the file you actually chose, which consult keeps.

Development

make compile    # byte-compile, warnings are errors
make lint       # package-lint + checkdoc
make test       # behavioural suite
make test-tty   # frame-dependent suite
make all

The suite is split in two because emacs --batch has no frame, and format-mode-line returns the empty string for every input there. Mode-line tests therefore run on a terminal frame under a pty via util-linux script(1) (BSD and macOS ship a different program under that name; the target checks up front). To exercise the icon tests, point make at a nerd-icons checkout:

make test-tty NERD_ICONS=~/.emacs.d/elpa/nerd-icons

License

GPL-3.0-or-later. See LICENSE.

About

VS Code's preview tab, for Emacs.

Resources

Stars

10 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages