Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
19 changes: 16 additions & 3 deletions standalone/sidecar/shell-integration/zsh/.zshrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,29 @@
# 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.
if [[ -n ${HISTFILE} && -n ${DORMOUSE_ZDOTDIR} && ${HISTFILE:A} == ${DORMOUSE_ZDOTDIR:A}/* ]]; then
Comment thread
dormouse-bot marked this conversation as resolved.
Outdated
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