Shell integration
Without it, eterm is a good terminal that cannot tell one command from another. With it, it knows where each command's output begins and ends, and where the shell is working.
Turning it on
zsh needs nothing done. eterm starts every zsh session pointed at a
ZDOTDIR of its own — four small files in ~/.config/eterm/zdotdir, rewritten
at every launch. Each one sources your own .zshenv, .zprofile, .zshrc or
.zlogin and then hands ZDOTDIR back, so your configuration runs exactly as
it would have and a shell started inside the session is an ordinary shell
again. Nothing is written into your home directory. cmd-, → Shell says
on for every session when this is what is happening.
The integration is sourced after your rc file, and that order is not
incidental: zsh hooks run in the order they were added, and a prompt framework
that rebuilds PROMPT in its own precmd — starship, powerlevel10k — would
otherwise throw away the mark that says where typing starts. Sourcing it twice,
which is what happens when the line below is also in your .zshrc, leaves one
hook rather than two.
To take your shell back, set ZDOTDIR yourself:
# ~/.config/eterm/settings.toml
[shell.env]
ZDOTDIR = "~/.config/zsh" # or "~", to go without the integration
Settings win over everything eterm sets, this included.
bash needs one line. Open cmd-, → Shell. The Shell integration row
says whether it is installed, and Install appends one marked line to your
.bashrc:
source "/Applications/eterm.app/Contents/Resources/shell/eterm.bash" # added by eterm
It appends and never rewrites: an rc file is yours, often long, and frequently in version control. Installing twice does nothing the second time. Then open a new session — this one keeps the shell it started with.
bash gets a line and zsh does not because there is nowhere to put it otherwise:
--rcfile is read only by interactive non-login shells and BASH_ENV only by
non-interactive ones, and neither is the login shell a terminal starts. Changing
which kind of shell you get in order to make room for a hook would be a worse
trade than asking for the line.
To do it by hand instead, add one line to your shell's rc file:
# ~/.bashrc
source /path/to/eterm/shell/eterm.bash
# ~/.zshrc
source /path/to/eterm/shell/eterm.zsh
Installed from the disk image, the scripts are inside the bundle at
/Applications/eterm.app/Contents/Resources/shell/.
When a shell says nothing
Press return twice in a session that has never reported anything and a strip appears above the footer: this shell is not reporting: nothing folds, nothing jumps, and the command line stays grey, with a button that adds the line to the rc file it belongs in.
It is there because the alternative is silence. Every one of the features below is invisible when it is missing — a terminal that cannot fold looks exactly like one nobody has asked to fold — and a row in a settings pane is only read by people who already went looking. It appears once per window, only for a shell eterm ships integration for, and Not now ends it.
What it does
The scripts emit two OSC sequences your terminal would otherwise never see:
- OSC 7 reports the working directory whenever the prompt is drawn. This is what names tabs, what a new tab or split inherits, and what a restored session comes back to.
- OSC 133 marks the prompt (
A), the end of the prompt (B), the start of a command's output (C) and its end with an exit code (D).A,CandDare what make a block a block;Bis what makes the command line colourable, because it says where the prompt stops and typing starts.
Neither reaches the emulator on its own: vte's parser handles a dozen OSC codes and drops the rest, including these two. eterm reads the pty itself and scans for them on the way past — see Architecture.
What you get
- Folding.
cmd-shift-entercollapses the output of the command at the cursor to a single line:▸ 14 lines folded, with the exit code when it was not zero. Clicking that line puts the output back. - Prompt jumping.
cmd-upandcmd-downmove the viewport between prompts. - Handing a block to the next pane.
cmd-shift-spastes the command and its output — trimmed the way AI mode trims it, both ends kept — into whichever pane had focus before this one. The shape of the day: tests in one pane, an agent waiting in the other, and the distance between the failure and the question used to be a selection, a copy, a click and a paste. With one pane, or the previous one gone, it goes to the clipboard instead. See Blocks. - The session as a runbook.
cmd-shift-mputs every finished command on the clipboard as Markdown, in order, with its exit code, how long it took and what it printed — trimmed and redacted the way AI mode does it, since a runbook goes to other people by definition. A handover or a bug report, already in the shape it will be read in. - A strip of blocks. Down the right edge of every session, the scrollback as a map: a mark per command, red where it failed, and the part on screen as a band. Where the problems are, before you scroll to them.
- Comparing two runs.
cmd-shift-con a block shows what changed in its output since the last time the same command ran, in a window that draws it like a pull request's diff. Same text exactly:cargo testandcargo test -- --nocaptureare different commands. - A coloured command line. Green for a command that exists. See configuration.
- Suggestions and completions while typing. See Suggestions.
- Directories. Tab names, inherited directories, and session restore.
- Tasks in AI mode. Knowing that a command has finished, and with which exit code, is what lets the model read the result and propose the next step. Without integration an approved command still runs, but nothing follows it.
Two runs of the same command
You run the tests, change something, run them again, and now there are two
hundred lines on screen and the question is which of them differ from last
time. cmd-shift-c on the block answers that: the two outputs are aligned line
by line and what changed is shown as a diff, three unchanged lines either side of
each change, in the same window that draws a pull request.
Two runs that printed the same thing say so rather than showing an empty diff, because an empty diff means two different things and you deserve to know which. Outputs past four thousand lines are not compared — nobody reads a diff that long, and the table behind the comparison grows with the product of the two lengths.
Over ssh
The marks come from the shell, so they only exist where the integration is
installed. A ZDOTDIR does not travel over ssh, so a remote host means putting
the line in an rc file there by hand. Everything that reads the screen — find,
selection, AI mode — works over ssh regardless, because the screen is local.
Writing your own
Any integration that emits the standard sequences works. The bash script guards
against one trap worth knowing: bash's DEBUG trap fires for every command,
including the ones inside PROMPT_COMMAND, so without a guard every prompt
starts a block that never ends.
One trap is worth naming, because it cost this project a release: in zsh,
status is a read-only alias for $?, so local status=$? fails the hook on
its first line — silently, at every prompt, emitting nothing at all. It is
correct bash. scripts/testing/shell-integration.sh runs both shells and reads
what they actually emit; run it after changing either script. cargo test goes
one further and starts a real zsh against a generated ZDOTDIR, because every
claim about what zsh does with a directory of startup files is a claim that
cannot be checked by reading the files.
B cannot be printed from a hook: the prompt is drawn after the hook returns,
so the mark has to live inside PROMPT or PS1 itself, wrapped in %{ %} for
zsh or \[ \] for bash so that line editing does not count it as width.
printf '\033]133;A\007' # prompt starts here
printf '\033]133;C\007' # output starts here
printf '\033]133;D;%s\007' "$status" # command finished with this code
printf '\033]7;file://%s%s\007' "$HOST" "$PWD"