Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/specs/terminal-escapes.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ A binary on `PATH` only has to be **found**, so it injects via one env var (`DOR

| Shell | Mechanism | Channel | Notes |
|---|---|---|---|
| zsh | `ZDOTDIR` → our dotfiles chain to the user's, then install `precmd`/`preexec` hooks | env (as reliable as the `PATH` prepend) | User's real `ZDOTDIR` is passed through as `USER_ZDOTDIR`; our `.zshrc` hands `ZDOTDIR` back so `.zlogin` and child shells are unaffected. |
| zsh | `ZDOTDIR` → our dotfiles chain to the user's, then install `precmd`/`preexec` hooks | env (as reliable as the `PATH` prepend) | User's real `ZDOTDIR` is passed through as `USER_ZDOTDIR`; our `.zshrc` hands `ZDOTDIR` back so `.zlogin` and child shells are unaffected. **Nothing may be written into our directory when shipped** — it sits inside the signed macOS app bundle, and any added file breaks the code signature (Gatekeeper then reports the app "damaged"). macOS `/etc/zshrc` runs while `ZDOTDIR` still points at our directory and sets `HISTFILE` inside it; our `.zshrc` redirects such a `HISTFILE` to `USER_ZDOTDIR` after sourcing the user's rc (a user-set `HISTFILE` is never touched). |
| bash | `--init-file` → our script replicates login-profile sourcing, then installs a `DEBUG`-trap / `PROMPT_COMMAND` hook | shellArgs | `--init-file` and login mode are mutually exclusive, so Dormouse drops `-l` and the script sources `/etc/profile` + the user's profile itself. Injected whenever the launch args are *only* interactive/login flags (`-i`/`-l`/`--login`) — so Git Bash, launched with `--login -i`, is covered too; a specific invocation like `-c <cmd>` is left untouched. Written for bash 3.2 (macOS system bash): no `PS0`, no array `PROMPT_COMMAND`. The `E` command line is the first simple command of a pipeline (a `DEBUG`-trap limitation); boundaries and exit codes stay exact. |
| PowerShell | `-Command ". '<script>'"` → the dot-sourced script wraps the user's `prompt` and PSReadLine's `PSConsoleHostReadLine` (covers `pwsh` and Windows `powershell.exe`) | shellArgs | `-NoProfile` is *not* passed, so the user's profile loads and defines their prompt before we wrap it. Injected for any **interactive** launch: a bare REPL gets `-NoExit -Command ". '<script>'"`, and a launch that already runs a startup command — e.g. the VS "Developer PowerShell" (`-NoExit -Command "& { Import-Module … }"`) — gets our dot-source *appended* to that command, so its environment is set up first and our wrapper installs after it. Non-interactive one-offs (a `-Command`/`-File`/`-EncodedCommand` without `-NoExit`) are left untouched. PowerShell has no `preexec`, so `E`/`C` (command line + start) are emitted by wrapping `PSConsoleHostReadLine`, which runs just before a submitted command executes — so the running command shows immediately, like bash/zsh. The matching `D` (finish, exit code from `$?`/`$LASTEXITCODE`) is emitted from the next `prompt` render. If PSReadLine is absent, the whole `E`/`C`/`D` triple is reported from the next prompt instead (command line from history): boundaries and exit codes stay exact, but the running command isn't shown until it finishes. |
| WSL | `wsl.exe -d <distro> -- sh -c <detector>` → the detector execs the distro's bash with our `--init-file` (the Windows bash script, referenced via its `/mnt/...` path) | shellArgs | Windows-side injection can't reach the Linux shell, so we append a command. The detector reads the login shell from `/etc/passwd`: it steps aside for an explicit zsh/fish login shell, execs bash+integration when bash exists (covering bash and an empty detection — the safe default), and falls back to the login shell only when bash is absent (e.g. Alpine). bash is the only WSL shell integrated for now. Assumes the default `/mnt` automount root. |
Expand Down
21 changes: 18 additions & 3 deletions standalone/sidecar/shell-integration/zsh/.zshrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,31 @@
# Hands ZDOTDIR back to the user, sources their real .zshrc, then installs the
# OSC 633 prompt/command hooks. We restore ZDOTDIR *before* running the user's rc
# so that anything zsh writes relative to ZDOTDIR — .zcompdump, .zsh_history —
# lands in the user's directory, not ours (which is read-only when shipped). It
# also means login shells read $USER_ZDOTDIR/.zlogin next (the user's, directly)
# and child shells behave normally, so this directory needs no .zlogin of its own.
# lands in the user's directory, not ours: when shipped, this directory lives
# inside the signed macOS app bundle, and any file written here breaks the
# bundle's code signature (Gatekeeper then refuses to launch the app as
# "damaged"). It also means login shells read $USER_ZDOTDIR/.zlogin next (the
# user's, directly) and child shells behave normally, so this directory needs
# no .zlogin of its own.

: ${USER_ZDOTDIR:=$HOME}
ZDOTDIR=${USER_ZDOTDIR}
if [[ -f ${USER_ZDOTDIR}/.zshrc ]]; then
builtin source ${USER_ZDOTDIR}/.zshrc
fi

# macOS /etc/zshrc runs before this file — while ZDOTDIR still points at our
# directory — and sets HISTFILE=${ZDOTDIR:-$HOME}/.zsh_history; a user
# .zshenv/.zprofile sourced during that window can do the same. Left alone, zsh
# would write history into the signed app bundle on shell exit (see above).
# Redirect it to what /etc/zshrc would have chosen without us. Runs after the
# user's rc so a HISTFILE they set themselves is never touched. The directory
# is quoted so that a user rc enabling GLOB_SUBST (e.g. sh/ksh emulation)
# can't turn an install path containing glob metacharacters into a pattern.
if [[ -n ${HISTFILE} && -n ${DORMOUSE_ZDOTDIR} && ${HISTFILE:A} == "${DORMOUSE_ZDOTDIR:A}"/* ]]; then
HISTFILE=${USER_ZDOTDIR}/.zsh_history
fi

# Guard against a re-sourced .zshrc installing the hooks twice.
if [[ -z ${DORMOUSE_SHELL_INTEGRATION} ]]; then
DORMOUSE_SHELL_INTEGRATION=1
Expand Down
Loading