Compare commits

...
8 Commits
Author SHA1 Message Date
Robby RussellandCarlo Sala 964ddf789b feat(cli): add Shellsavers terminal animations (#14167)
Co-authored-by: Carlo Sala <carlosalag@protonmail.com>
2026-10-13 11:22:24 +02:00
John D Pell fab613bd93 fix(termsupport): improve Apple Terminal support (#14172) 2026-10-13 10:41:24 +02:00
Robby Russell 1e3960e6f9 feat(opencode): add completion and opc aliases (#14082) 2026-10-13 10:40:23 +02:00
dependabot[bot] cbf4a572ef chore(deps): bump the gha-minor group with 3 updates (#14176)
Bumps the gha-minor group with 3 updates: [step-security/harden-runner](https://github.com/step-security/harden-runner), [actions/upload-artifact](https://github.com/actions/upload-artifact) and [github/codeql-action/upload-sarif](https://github.com/github/codeql-action).


Updates `step-security/harden-runner` from 2.21.1 to 2.22.1
- [Release notes](https://github.com/step-security/harden-runner/releases)
- [Commits](https://github.com/step-security/harden-runner/compare/e14015d583714f6e62063499dc959a02595150a1...ccd8616d44fd3846e67624a50d5aad6d37bf2d25)

Updates `actions/upload-artifact` from 7.0.1 to 7.0.2
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/043fb46d1a93c77aae656e7c1c64a875d1fc6a0a...cf430e030ddbb5b0abf93d22962f4752f3646cd9)

Updates `github/codeql-action/upload-sarif` from 4.38.2 to 4.38.3
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2...24c54180a607b1449ed407dd24f251e4e9147c8d)

---
updated-dependencies:
- dependency-name: step-security/harden-runner
  dependency-version: 2.22.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: gha-minor
- dependency-name: actions/upload-artifact
  dependency-version: 7.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: gha-minor
- dependency-name: github/codeql-action/upload-sarif
  dependency-version: 4.38.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: gha-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-10-13 10:38:57 +02:00
Piotr Minkina 55c9071ee7 feat(docker-compose): add aliases for --watch (#13203) 2026-10-13 10:36:54 +02:00
Rik Wanders 083c339e06 feat(git): add gshs alias for git show --stat (#13249) 2026-10-12 03:55:10 -07:00
Masafumi Koba 417c814e2e feat(rbenv): provide completion for rbenv command (#12891)
Ref https://github.com/rbenv/rbenv/#shell-completions
2026-10-12 03:47:39 -07:00
Pete Johns 7d34868725 perf(mise): regenerate completion only when stale (#13759) 2026-10-12 03:34:33 -07:00
19 changed files with 907 additions and 47 deletions
+1 -1
View File
@@ -16,7 +16,7 @@ jobs:
contents: write # this is needed to push commits and branches
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
with:
egress-policy: audit
+2 -2
View File
@@ -26,7 +26,7 @@ jobs:
- macos-latest
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
with:
egress-policy: audit
@@ -47,7 +47,7 @@ jobs:
- test
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
with:
egress-policy: audit
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
if: github.repository == 'ohmyzsh/ohmyzsh'
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
with:
egress-policy: audit
+1 -1
View File
@@ -17,7 +17,7 @@ jobs:
if: github.repository == 'ohmyzsh/ohmyzsh'
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
with:
egress-policy: audit
- name: Authenticate as @ohmyzsh
+3 -3
View File
@@ -36,7 +36,7 @@ jobs:
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
uses: step-security/harden-runner@ccd8616d44fd3846e67624a50d5aad6d37bf2d25 # v2.22.1
with:
egress-policy: audit
@@ -53,13 +53,13 @@ jobs:
publish_results: true
- name: "Upload artifact"
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
with:
name: SARIF file
path: results.sarif
retention-days: 5
- name: "Upload to code-scanning"
uses: github/codeql-action/upload-sarif@2892aa5e19bbd11bc0cff5427e3b750a04d9e3c2 # v4.38.2
uses: github/codeql-action/upload-sarif@24c54180a607b1449ed407dd24f251e4e9147c8d # v4.38.3
with:
sarif_file: results.sarif
+28
View File
@@ -45,6 +45,7 @@ Twitter), and join us on [Discord](https://discord.gg/ohmyzsh).
- [Using Plugins](#using-plugins)
- [Themes](#themes)
- [Selecting A Theme](#selecting-a-theme)
- [Shellsavers](#shellsavers)
- [FAQ](#faq)
- [Advanced Topics](#advanced-topics)
- [Advanced Installation](#advanced-installation)
@@ -241,6 +242,33 @@ If you only know which themes you don't like, you can add them similarly to an i
ZSH_THEME_RANDOM_IGNORED=(pygmalion tjkirch_mod)
```
### Shellsavers
Give your terminal a little downtime with `omz shellsaver`—our playful name for
terminal screensavers. The aquarium is the default; choose another scene to change the mood:
```zsh
omz shellsaver # A quiet aquarium full of sea shells
omz shellsaver logo # The updater's rainbow logo, bouncing around
omz shellsaver hermit # A slightly lost hermit crab
omz shellsaver party # A rainbow stage and a dancing crowd
```
Press any key or Ctrl-C to return to your shell. Screensavers run only when invoked
and restore the terminal when they exit. No plugin or configuration change is needed.
Use `omz help` to discover the command, and Tab to complete scene names and options.
```zsh
omz shellsaver aquarium --seconds 30 # Stop automatically after 30 seconds
omz shellsaver logo --mono # Use the terminal's foreground color
omz shellsaver --list # List scenes (aquarium is the default)
omz shellsaver --help # Show all scenes and options
```
Requires Zsh 5.8 or newer, `stty`, and an xterm-compatible terminal with alternate-screen
support. No additional animation package is required. `NO_COLOR` is respected.
A window of at least 49 columns by 18 rows is recommended.
### FAQ
If you have some more questions or issues, you might find a solution in our
+23
View File
@@ -28,12 +28,25 @@ function _omz {
'plugin:Manage plugins'
'pr:Manage Oh My Zsh Pull Requests'
'reload:Reload the current zsh session'
'shellsaver:Launch a whimsical terminal screensaver'
'shop:Open the Oh My Zsh shop'
'theme:Manage themes'
'update:Update Oh My Zsh'
'version:Show the version'
)
if [[ "$words[2]" == (screensaver|shellsaver) ]] && (( CURRENT > 2 )); then
_arguments \
'1:command:' \
'2::scene:(aquarium logo hermit party)' \
'--list[List available scenes]' \
'--mono[Use the default foreground color]' \
'--seconds[Exit after a number of seconds]:seconds:' \
'--snapshot[Print a plain-text still]::frame:' \
'--help[Show Shellsaver help]'
return
fi
if (( CURRENT == 2 )); then
_describe 'command' cmds
elif (( CURRENT == 3 )); then
@@ -174,6 +187,7 @@ Available commands:
plugin <command> Manage plugins
pr <command> Manage Oh My Zsh Pull Requests
reload Reload the current zsh session
shellsaver [scene] Launch a Shellsaver (aquarium by default)
shop Open the Oh My Zsh shop
theme <command> Manage themes
update Update Oh My Zsh
@@ -182,6 +196,15 @@ Available commands:
EOF
}
function _omz::shellsaver {
command zsh -f "$ZSH/tools/shellsaver.zsh" "$@"
}
# Hidden alias; only shellsaver is advertised in help and command completion.
function _omz::screensaver {
_omz::shellsaver "$@"
}
function _omz::changelog {
local version=${1:-HEAD} format=${3:-"--text"}
+7 -5
View File
@@ -5,7 +5,6 @@
# See: http://www.faqs.org/docs/Linux-mini/Xterm-Title.html#ss3.1
# Fully supports screen, iterm, and probably most modern xterm and rxvt
# (In screen, only short_tab_title is used)
# Limited support for Apple Terminal (Terminal can't set window and tab separately)
function title {
setopt localoptions nopromptsubst
@@ -17,7 +16,7 @@ function title {
: ${2=$1}
case "$TERM" in
cygwin|xterm*|putty*|rxvt*|konsole*|ansi|mlterm*|alacritty*|st*|foot*|contour*|wezterm*)
cygwin|xterm*|putty*|rxvt*|konsole*|ansi|mlterm*|alacritty*|st*|foot*|contour*|wezterm*|nsterm*)
print -Pn "\e]2;${2:q}\a" # set window name
print -Pn "\e]1;${1:q}\a" # set tab name
;;
@@ -26,10 +25,12 @@ function title {
print -Pn "\ek${1:q}\e\\" # set screen hardstatus/tmux window name if `allow-rename` is on
;;
*)
if [[ "$TERM_PROGRAM" == "iTerm.app" ]]; then
case "$TERM_PROGRAM" in
"iTerm.app"|"Apple_Terminal")
print -Pn "\e]2;${2:q}\a" # set window name
print -Pn "\e]1;${1:q}\a" # set tab name
else
;;
*)
# Try to use terminfo to set the title if the feature is available
if (( ${+terminfo[fsl]} && ${+terminfo[tsl]} )); then
# Emit the capabilities with echoti so terminfo does its own parameter
@@ -39,7 +40,8 @@ function title {
print -Pn "$1"
echoti fsl
fi
fi
;;
esac
;;
esac
}
+29 -25
View File
@@ -13,28 +13,32 @@ plugins=(... docker-compose)
## Aliases
| Alias | Command | Description |
|-----------|----------------------------------|----------------------------------------------------------------------------------|
| dco | `docker-compose` | Docker-compose main command |
| dcb | `docker-compose build` | Build containers |
| dcc | `docker-compose config` | Parse, resolve and render compose file in canonical format |
| dce | `docker-compose exec` | Execute command inside a container |
| dcps | `docker-compose ps` | List containers |
| dcrestart | `docker-compose restart` | Restart container |
| dcrm | `docker-compose rm` | Remove container |
| dcr | `docker-compose run` | Run a command in container |
| dcstop | `docker-compose stop` | Stop a container |
| dcup | `docker-compose up` | Build, (re)create, start, and attach to containers for a service |
| dcupb | `docker-compose up --build` | Same as `dcup`, but build images before starting containers |
| dcupd | `docker-compose up -d` | Same as `dcup`, but starts as daemon |
| dcupdb | `docker-compose up -d --build` | Same as `dcup`, but build images before starting containers and starts as daemon |
| dcdn | `docker-compose down` | Stop and remove containers |
| dcl | `docker-compose logs` | Show logs of container |
| dclf | `docker-compose logs -f` | Show logs and follow output |
| dclF | `docker-compose logs -f --tail0` | Just follow recent logs |
| dcpull | `docker-compose pull` | Pull image of a service |
| dcstart | `docker-compose start` | Start a container |
| dck | `docker-compose kill` | Kills containers |
| dcv | `docker-compose version` | Show version information |
| dcsts | `docker-compose stats` | Show stats of containers |
| dci | `docker-compose images` | List images |
| Alias | Command | Description |
|-----------|-------------------------------------|----------------------------------------------------------------------------------------------|
| dco | `docker-compose` | Docker-compose main command |
| dcb | `docker-compose build` | Build containers |
| dcc | `docker-compose config` | Parse, resolve and render compose file in canonical format |
| dce | `docker-compose exec` | Execute command inside a container |
| dcps | `docker-compose ps` | List containers |
| dcrestart | `docker-compose restart` | Restart container |
| dcrm | `docker-compose rm` | Remove container |
| dcr | `docker-compose run` | Run a command in container |
| dcstop | `docker-compose stop` | Stop a container |
| dcup | `docker-compose up` | Build, (re)create, start, and attach to containers for a service |
| dcupb | `docker-compose up --build` | Same as `dcup`, but build images before starting containers |
| dcupd | `docker-compose up -d` | Same as `dcup`, but starts as daemon |
| dcupdb | `docker-compose up -d --build` | Same as `dcup`, but build images before starting containers and starts as daemon |
| dcupw | `docker-compose up --watch` | Watch changes in service and rebuild/restart accordingly (see [Use Compose Watch] for usage) |
| dcupwb | `docker-compose up --watch --build` | Same as `dcupw`, but build images before starting containers |
| dcdn | `docker-compose down` | Stop and remove containers |
| dcl | `docker-compose logs` | Show logs of container |
| dclf | `docker-compose logs -f` | Show logs and follow output |
| dclF | `docker-compose logs -f --tail0` | Just follow recent logs |
| dcpull | `docker-compose pull` | Pull image of a service |
| dcstart | `docker-compose start` | Start a container |
| dck | `docker-compose kill` | Kills containers |
| dcv | `docker-compose version` | Show version information |
| dcsts | `docker-compose stats` | Show stats of containers |
| dci | `docker-compose images` | List images |
[Use Compose Watch]: https://docs.docker.com/compose/how-tos/file-watch/
@@ -17,6 +17,8 @@ alias dcup="$dccmd up"
alias dcupb="$dccmd up --build"
alias dcupd="$dccmd up -d"
alias dcupdb="$dccmd up -d --build"
alias dcupw="$dccmd up --watch"
alias dcupwb="$dccmd up --watch --build"
alias dcdn="$dccmd down"
alias dcl="$dccmd logs"
alias dclf="$dccmd logs -f"
+1
View File
@@ -191,6 +191,7 @@ plugins=(... git)
| `grmc` | `git rm --cached` |
| `gcount` | `git shortlog --summary -n` |
| `gsh` | `git show` |
| `gshs` | `git show --stat` |
| `gsps` | `git show --pretty=short --show-signature` |
| `gstall` | `git stash --all` |
| `gstu` | `git stash --include-untracked` |
+1
View File
@@ -392,6 +392,7 @@ alias grm='git rm'
alias grmc='git rm --cached'
alias gcount='git shortlog --summary --numbered'
alias gsh='git show'
alias gshs='git show --stat'
alias gsps='git show --pretty=short --show-signature'
alias gstall='git stash --all'
alias gstaa='git stash apply'
+11 -8
View File
@@ -1,5 +1,5 @@
if (( $+commands[mise] )); then
_mise_bin=mise
_mise_bin=$commands[mise]
elif [[ -x ~/.local/bin/mise ]]; then
_mise_bin=~/.local/bin/mise
else
@@ -8,7 +8,6 @@ fi
# Load mise hooks
eval "$($_mise_bin activate zsh)"
unset _mise_bin
# If the completion file doesn't exist yet, we need to autoload it and
# bind it to `mise`. Otherwise, compinit will have already done that.
@@ -18,9 +17,13 @@ if [[ ! -f "$ZSH_CACHE_DIR/completions/_mise" ]]; then
_comps[mise]=_mise
fi
# Generate and load mise completion
zmodload -F zsh/files b:zf_mv
() {
local TMPPREFIX="$ZSH_CACHE_DIR/completions/._mise"
zf_mv -f -- =( mise completion zsh ) "$ZSH_CACHE_DIR/completions/_mise"
} &|
# Generate completion only when missing/empty or stale relative to the mise binary.
if [[ ! -s "$ZSH_CACHE_DIR/completions/_mise" || "$_mise_bin" -nt "$ZSH_CACHE_DIR/completions/_mise" ]]; then
zmodload -F zsh/files b:zf_mv
() {
local TMPPREFIX="$ZSH_CACHE_DIR/completions/._mise"
zf_mv -f -- =( "$_mise_bin" completion zsh ) "$ZSH_CACHE_DIR/completions/_mise"
} &|
fi
unset _mise_bin
+111
View File
@@ -0,0 +1,111 @@
# opencode plugin
This plugin adds completion and aliases for the [OpenCode](https://opencode.ai) CLI.
Install OpenCode using its [installation instructions](https://opencode.ai/docs/#install),
and make sure `opencode` is available on your `$PATH`.
To enable it, add `opencode` to the plugins array in your `.zshrc`:
```zsh
plugins=(... opencode)
```
## Learning OpenCode from the terminal
Use tab completion to explore commands and their descriptions without leaving the
shell. For example, these are some of the candidates shown by the CLI:
```text
$ opencode <TAB>
agent -- manage agents
models -- list all available models
run -- run opencode with a message
session -- manage sessions
stats -- show token usage and cost statistics
...
$ opencode session <TAB>
delete -- delete a session
list -- list sessions
$ opencode run --<TAB>
--agent -- agent to use
--continue -- continue the last session
--file -- file(s) to attach to message
--model -- model to use in the format of provider/model
...
```
Completion works through the aliases too. Type a partial command or option and
press Tab to finish it:
```text
$ opc session li<TAB>
$ opc session list
$ opcr --att<TAB>
$ opcr --attach
$ opcc --log-l<TAB>
$ opcc --log-level
```
Candidates depend on your installed OpenCode version. In OpenCode 1.18.29, the
CLI's completion output omits top-level TUI flags such as `--model` and `--fork`,
even though those flags work when typed. Use `opencode --help` or
`opencode run --help` for the full options, and see the
[CLI reference](https://opencode.ai/docs/cli/) for more workflows.
## Aliases
| Alias | Command | Description |
| ------ | --------------------- | ----------------------------------- |
| `opc` | `opencode` | Launch the terminal UI |
| `opcc` | `opencode --continue` | Continue the last session |
| `opcr` | `opencode run` | Run a prompt without opening the UI |
To keep completion but skip the aliases, set this in your `.zshrc`, above the line
that sources Oh My Zsh:
```zsh
# ~/.zshrc
zstyle ':omz:plugins:opencode' aliases no
plugins=(... opencode)
source $ZSH/oh-my-zsh.sh
```
## Common workflows
All aliases accept additional arguments:
```zsh
# Open a project
opc ~/projects/my-app
# Explore a different approach by forking the last session
opcc --fork
# Ask a question without entering the terminal UI
opcr "Explain the test setup in this project"
# Reuse an already running backend to avoid MCP server cold starts
opcr --attach http://localhost:4096 "Explain this project's architecture"
```
## Completion
Completion queries the installed OpenCode CLI on demand for commands and options,
including through the aliases above. It falls back to Zsh's default completion when
the CLI returns no candidates. No OpenCode process is started at shell startup,
and no generated completion cache is needed.
## Credits
Based on the OpenCode plugin proposals by
[pranavavva](https://github.com/pranavavva) in
[#13545](https://github.com/ohmyzsh/ohmyzsh/pull/13545) and
[mskadu](https://github.com/mskadu) in
[#13794](https://github.com/ohmyzsh/ohmyzsh/pull/13794).
The completion adapter is derived from OpenCode's `opencode completion` output.
+12
View File
@@ -0,0 +1,12 @@
#compdef opencode
# Adapted from `opencode completion`, using the CLI's live Yargs completions.
local -a reply
reply=("${(@f)$(COMP_CWORD="$((CURRENT-1))" COMP_LINE="$BUFFER" COMP_POINT="$CURSOR" \
command opencode --get-yargs-completions "${words[@]}" < /dev/null 2> /dev/null)}")
if [[ -n "${reply[1]}" ]]; then
_describe 'values' reply
else
_default
fi
+7
View File
@@ -0,0 +1,7 @@
if (( ! $+commands[opencode] )); then
return
fi
alias opc='opencode'
alias opcc='opencode --continue'
alias opcr='opencode run'
+2
View File
@@ -3,6 +3,8 @@
The primary job of this plugin is to provide `rbenv_prompt_info` which can be added to your theme to include Ruby
version and gemset information into your prompt.
Additionally, this plugin provides completion for the `rbenv` command. See also <https://github.com/rbenv/rbenv/#shell-completions>.
Some functionality of this plugin will not work unless you also have the rbenv plugin *gemset* installed.
https://github.com/jf/rbenv-gemset
+8 -1
View File
@@ -24,6 +24,13 @@ if [[ $FOUND_RBENV -ne 1 ]]; then
fi
if [[ $FOUND_RBENV -eq 1 ]]; then
rbenv_completions="${commands[rbenv]:A:h:h}/completions"
if [[ -f "$rbenv_completions/_rbenv" ]]; then
fpath+=("$rbenv_completions")
autoload -Uz _rbenv
compdef _rbenv rbenv
fi
eval "$(rbenv init --no-rehash - zsh)"
alias rubies="rbenv versions"
@@ -65,4 +72,4 @@ else
}
fi
unset FOUND_RBENV rbenvdirs dir
unset FOUND_RBENV rbenvdirs dir rbenv_completions
+657
View File
@@ -0,0 +1,657 @@
#!/usr/bin/env zsh
# Shared on-demand renderer for `omz shellsaver <scene>`.
# The CLI launches a separate Zsh process, keeping all state out of the caller.
{
emulate -L zsh
setopt extendedglob
unsetopt multibyte
integer tick=0 width=96 height=28 resized=1 active=0 seconds_limit=0
integer mono=0 snapshot=0 scene_selected=0
local saved_tty='' key='' blank='' result='' scene=''
local -a canvas inks palette
_zshell_list() {
print -r -- 'aquarium Sea shells, a ~ hermit crab, and a quiet current (default).
logo The Oh My Zsh wordmark takes a bouncing coffee break.
hermit A bewildered hermit crab tries to find its working directory.
party A rainbow terminal stage and a crowd of dancing shell fans.'
}
_zshell_usage() {
print -r -- 'Oh My Zsh / experimental Shellsavers
Usage: omz shellsaver [aquarium|logo|hermit|party] [--mono] [--seconds N]
omz shellsaver [aquarium|logo|hermit|party] --snapshot [FRAME]
omz shellsaver --list
'
_zshell_list
print -r -- '
Any key returns to the shell. Ctrl-C also exits safely.
--list List available scenes and the default.
--mono Use the terminal default foreground color.
--seconds N Exit automatically after 1-3600 seconds.
--snapshot Print a still at 96 columns x 28 rows (no terminal needed).
Requires Zsh 5.8+, stty, and an xterm-compatible terminal with an alternate screen.
Nothing is installed or enabled automatically.'
}
case "${1-}" in
--help|-h) _zshell_usage; exit 0 ;;
''|--*) scene=aquarium ;;
aquarium|logo|hermit|party) scene=$1; scene_selected=1; shift ;;
*) print -u2 -r -- "omz shellsaver: unknown scene: $1 (choose aquarium, logo, hermit, or party)"; exit 2 ;;
esac
while (( $# )); do
case "$1" in
aquarium|logo|hermit|party)
if (( scene_selected )); then
print -u2 -r -- 'omz shellsaver: choose only one scene (try --help)'
exit 2
fi
scene=$1; scene_selected=1 ;;
--help|-h) _zshell_usage; exit 0 ;;
--list) _zshell_list; exit 0 ;;
--mono) mono=1 ;;
--seconds)
shift
if [[ $1 != <1-3600> ]]; then
print -u2 -r -- 'omz shellsaver: --seconds requires an integer from 1 to 3600'
exit 2
fi
seconds_limit=$1 ;;
--snapshot)
snapshot=1
if [[ ${2-} == <0-100000> ]]; then tick=$2; shift; fi ;;
*) print -u2 -r -- "omz shellsaver: unknown argument: $1 (try --help)"; exit 2 ;;
esac
shift
done
# Paint into separate ASCII text and color planes. Clip before slicing so a
# creature partly outside the viewport never wraps onto another terminal row.
_zshell_put() {
integer x=$1 y=$2 ink=$3 count skip
local text=$4 prefix='' suffix='' iprefix='' isuffix=''
(( y >= 1 && y <= height && x <= width )) || return 0
if (( x < 1 )); then
skip=$(( 2 - x ))
(( skip <= ${#text} )) || return 0
text=${text[$skip,-1]}
x=1
fi
count=$(( width - x + 1 ))
text=${text[1,$count]}
count=${#text}
(( count )) || return 0
if (( x > 1 )); then
prefix=${canvas[$y][1,$((x-1))]}
iprefix=${inks[$y][1,$((x-1))]}
fi
if (( x + count <= width )); then
suffix=${canvas[$y][$((x+count)),-1]}
isuffix=${inks[$y][$((x+count)),-1]}
fi
canvas[$y]="$prefix$text$suffix"
inks[$y]="$iprefix${(pl:$count::$ink:)}$isuffix"
}
_zshell_sprite() {
integer sx=$1 sy=$2 color=$3
shift 3
local line
for line in "$@"; do
_zshell_put $sx $sy $color "$line"
(( sy++ ))
done
}
_zshell_logo() {
# The full-size artwork and per-letter color bands match tools/upgrade.sh.
# A compact badge preserves the bounce in narrower terminal windows.
local -a logo logo_inks quips
local line glyph bands run
integer logo_width=0 logo_height span_x span_y px py x y color i j n offset
logo=(
' __ __ '
' ____ / /_ ____ ___ __ __ ____ _____/ /_ '
' / __ \/ __ \ / __ `__ \/ / / / /_ / / ___/ __ \ '
'/ /_/ / / / / / / / / / / /_/ / / /_(__ ) / / / '
'\____/_/ /_/ /_/ /_/ /_/\__, / /___/____/_/ /_/ '
' /____/ '
)
logo_inks=(
11111111122222222333333333334444444455555556666677777
11111111222222223333333333344444444555555566666777777
11111112222222233333333333444444445555555666666777777
11111112222222333333333333444444455555556666667777777
11111122222223333333333334444444555555556666677777777
11112222222233333333333444444445555555666667777777777
)
if (( width < 64 )); then
logo=( '.------------------.' '| Oh My Zsh |' '| ~ >_ |' "'------------------'" )
logo_inks=()
for line in "${logo[@]}"; do
bands=''
for (( j=1; j<=${#line}; j++ )); do
bands+=$((1+(j-1)*7/${#line}))
done
logo_inks+=( "$bands" )
done
fi
for line in "${logo[@]}"; do
(( ${#line} > logo_width )) && logo_width=${#line}
done
logo_height=${#logo}
span_x=$(( width-logo_width-4 ))
span_y=$(( height-logo_height-8 ))
px=$(( (tick/2+span_x/2) % (2*span_x) ))
py=$(( (tick/6+span_y/2) % (2*span_y) ))
x=$(( 3 + (px <= span_x ? px : 2*span_x-px) ))
y=$(( 5 + (py <= span_y ? py : 2*span_y-py) ))
_zshell_put 3 2 1 'O H M Y Z S H'
_zshell_put $((width-17)) 2 0 'prompt on a break'
for (( i=0; i<width/14; i++ )); do
glyph='~'; (( i % 2 )) && glyph='>'
_zshell_put $((3+(i*23+tick/20)%(width-5))) $((4+(i*7)%(height-7))) 0 "$glyph"
done
# Keep the updater's red-to-pink bands attached to their letters on bounce.
for (( i=1; i<=logo_height; i++ )); do
line=${logo[$i]} bands=${logo_inks[$i]} offset=0
while [[ -n $bands ]]; do
glyph=${bands[1]}
run=${bands%%[^$glyph]*}
n=${#run}
_zshell_put $((x+offset)) $((y+i-1)) $glyph "${line[1,$n]}"
(( offset += n ))
line=${line[$((n+1)),-1]}
bands=${bands[$((n+1)),-1]}
done
done
_zshell_put $((x+logo_width-2)) $((y+logo_height)) 1 '>_'
if (( (px == 0 || px == span_x) && (py == 0 || py == span_y) )); then
_zshell_put $((x-2)) $((y-1)) 3 '*'
_zshell_put $((x+logo_width+1)) $((y-1)) 3 '*'
_zshell_put $(( (width-17)/2 )) $((height-2)) 3 'corner committed!'
else
quips=( 'home is where the ~ is' 'plugins on a coffee break' 'a delightful little detour' 'your prompt will be right back' )
line=${quips[$((1+tick/80%${#quips}))]}
_zshell_put $(( (width-${#line})/2 )) $((height-2)) 0 "$line"
fi
_zshell_put 3 $height 0 'SCREENSAVER / LOGO'
_zshell_put $((width-21)) $height 0 'any key: back to work'
}
_zshell_hermit() {
integer beat=$((tick/40%8)) within=$((tick%40)) shuffle=0 x y i
local left_eye='(o )' right_eye='(o )' mouth='._.' thought='...' legs
# Long, still looks make the tiny blinks and indecisive steps more visible.
case $beat in
0) thought='...where was I going?' ;;
1) left_eye='( o)'; right_eye='( o)'; thought='...over there?' ;;
2) left_eye='(O )'; right_eye='( O)'; mouth=' o '; thought='pwd...?' ;;
3) left_eye='( o)'; right_eye='(o )'; mouth='-_-'; thought='I brought home. Now what?' ;;
4) left_eye='( o)'; right_eye='( o)'; shuffle=$((within/10)); thought='maybe this way.' ;;
5) left_eye='( o)'; right_eye='(o )'; shuffle=3; thought='wrong directory.' ;;
6) shuffle=$((3-within/10)); thought='cd .. ?' ;;
7) left_eye='( ^)'; right_eye='(^ )'; mouth=' u '; thought='~ sweet ~ ...wait.' ;;
esac
# Short blinks run independently of the longer expression cycle.
if (( tick%73 >= 71 )); then left_eye='( -)'; right_eye='(- )'; fi
x=$(( (width-42)/2 + shuffle ))
y=$(( (height-12)/2 + 1 ))
_zshell_put 3 2 1 'O H M Y Z S H'
_zshell_put $((width-21)) 2 0 'a little shell-shocked'
# Photo-inspired three-quarter silhouette: tapered shell whorls behind
# the exposed head, unequal eyestalks, antennae and a hanging front claw.
_zshell_sprite $x $y 3 \
' __...---.' \
" _.-' .--. '." \
" .-' .' '. \\" \
" .' _.-./ ~ \ |" \
" / .' \ / |" \
"/ / .-. |'-----' /" \
'| ( ( @ ) / /' \
"\ \ '-' .' .'" \
" '-._'--'______.-'"
# Staggered eyes on stalks; the small head emerges from the shell opening.
_zshell_put $((x+25)) $((y+2)) 5 "$left_eye"
_zshell_put $((x+32)) $((y+1)) 5 "$right_eye"
_zshell_put $((x+27)) $((y+3)) 4 '|'
_zshell_put $((x+27)) $((y+4)) 4 '|'
_zshell_put $((x+33)) $((y+2)) 4 '/'
_zshell_put $((x+32)) $((y+3)) 4 '/'
_zshell_put $((x+31)) $((y+4)) 4 '/'
_zshell_put $((x+36)) $((y+3)) 5 "_.-'"
_zshell_put $((x+33)) $((y+4)) 5 ".-'"
_zshell_put $((x+32)) $((y+5)) 5 '/'
_zshell_put $((x+22)) $((y+5)) 4 '/___'
_zshell_put $((x+21)) $((y+6)) 4 '\___'
_zshell_put $((x+20)) $((y+7)) 4 '\__'
_zshell_put $((x+26)) $((y+5)) 4 '.----.'
_zshell_put $((x+25)) $((y+6)) 4 '/ \'
_zshell_put $((x+23)) $((y+7)) 4 '__\______/'
_zshell_put $((x+21)) $((y+8)) 4 '\___/ /\ \__'
_zshell_put $((x+27)) $((y+6)) 5 "$mouth"
# Legs bend down and forward instead of spreading symmetrically sideways.
_zshell_put $((x+18)) $((y+9)) 4 '\___/ / \___'
_zshell_put $((x+16)) $((y+10)) 4 '___/ / \'
legs=' _/____/ \_'
if (( (beat == 4 || beat == 6) && within/3%2 )); then
legs=' /___/ _/'
fi
_zshell_put $x $((y+11)) 4 "$legs"
# The big claw hangs in front. The much smaller claw tucks under the face.
if (( beat == 3 )); then
_zshell_sprite $((x+35)) $((y+7)) 4 ' __ ' '/ \' '| / |' ' V V '
_zshell_put $((x+32)) $((y+6)) 4 '()'
else
_zshell_sprite $((x+35)) $((y+8)) 4 ' __ ' '/ \' '| / |' ' V V '
_zshell_put $((x+30)) $((y+8)) 4 '()'
fi
if (( beat == 2 || beat == 5 )); then
_zshell_put $((x+39)) $y 1 '?'
fi
if (( height >= 24 )); then
for (( i=3; i<width-2; i+=5 )); do
_zshell_put $i $((height-4)) 8 '.'
done
fi
_zshell_put $(( (width-${#thought})/2 )) $((height-2)) 0 "$thought"
_zshell_put 3 $height 0 'SCREENSAVER / HERMIT'
_zshell_put $((width-21)) $height 0 'any key: back to home'
}
_zshell_party() {
integer x y i j row col sx sy glyph_width=5 scale=1 logo_width logo_x logo_y
integer crowd count step pose bob sway base bars profile build stature outfit hair body_rows person_height leg_y
local line bits letter pixel border
local -a letters letter_colors rows heads builds statures hairstyles outfits
local -A font
# A tiny bitmap font gives the stage the chunky, rainbow look of the image.
# Use ASCII pixels to keep the same width in every supported terminal font.
font=(
O '01110 10001 10001 10001 01110'
H '10001 10001 11111 10001 10001'
M '10001 11011 10101 10001 10001'
Y '10001 01010 00100 00100 00100'
Z '11111 00010 00100 01000 11111'
S '01111 10000 01110 00001 11110'
)
if (( width < 64 )); then
glyph_width=3
font=( O '111 101 101 101 111' H '101 101 111 101 101'
M '101 111 111 101 101' Y '101 101 010 010 010'
Z '111 001 010 100 111' S '111 100 111 001 111' )
elif (( width >= 120 )); then
scale=2
fi
letters=( O H M Y Z S H )
letter_colors=( 4 3 6 1 7 2 4 )
logo_width=$(( (7*glyph_width+6+4)*scale ))
logo_x=$(( (width-logo_width)/2+1 ))
logo_y=$(( height/4 )); (( logo_y < 4 )) && logo_y=4
# A terminal window behind the crowd, including tiny window controls.
border="+${(pl:$((width-8))::-:)}+"
_zshell_put 4 2 5 "$border"
_zshell_put 4 $((height-5)) 5 "$border"
for (( y=3; y<height-5; y++ )); do
_zshell_put 4 $y 5 '|'
_zshell_put $((width-3)) $y 5 '|'
done
_zshell_put 7 2 0 ' O H M Y Z S H '
_zshell_put $((width-10)) 2 4 'o'
_zshell_put $((width-8)) 2 3 'o'
_zshell_put $((width-6)) 2 6 'o'
x=$logo_x
for (( i=1; i<=7; i++ )); do
letter=${letters[$i]}
rows=( ${=font[$letter]} )
for (( row=1; row<=5; row++ )); do
bits=${rows[$row]}
line=''
for (( col=1; col<=glyph_width; col++ )); do
pixel=' '; [[ ${bits[$col]} == 1 ]] && pixel='#'
line+="${(pl:$scale::$pixel:)}"
done
_zshell_put $x $((logo_y+row-1)) ${letter_colors[$i]} "$line"
done
(( x += (glyph_width+1)*scale ))
(( i == 2 || i == 4 )) && (( x += 2*scale ))
done
# Small moving meters suggest music without sound or full-screen flashes.
for (( i=0; i<10; i++ )); do
bars=$((1+(tick/4+i*7)%3))
x=$((logo_x+i*logo_width/10))
for (( j=0; j<bars; j++ )); do
_zshell_put $x $((logo_y+7-j)) 6 ':'
done
done
# Stable appearance profiles are independent of the dance cycle. Hair,
# clothing, build and height vary separately rather than forming one type.
builds=( 0 2 1 2 0 1 2 0 )
statures=( 1 0 2 1 0 2 0 1 )
hairstyles=( 0 1 2 3 4 1 0 2 )
outfits=( 0 1 0 2 1 2 0 1 )
count=$(( (width-2)/9 ))
base=$(( (width-count*9)/2+1 ))
for (( crowd=0; crowd<count; crowd++ )); do
profile=$((crowd%8+1))
build=${builds[$profile]} stature=${statures[$profile]}
hair=${hairstyles[$profile]} outfit=${outfits[$profile]}
body_rows=$((1+stature))
person_height=$((5+body_rows))
# Different tempos and offsets prevent synchronized, marching motion.
step=$((tick/(3+crowd%3)+crowd*3))
pose=$((step%4))
bob=$((step%3 == 0))
sway=$((pose == 1 ? 1 : 0))
sx=$((base+crowd*9+sway))
sy=$((height-3-person_height+bob))
for (( row=0; row<person_height; row++ )); do
_zshell_put $sx $((sy+row)) 0 ' '
done
case $hair in
0) heads=( ' __ ' ' / \ ' ' \__/ ' ) ;;
1) heads=( ' /~~\ ' '(| |)' '(|__|)' ) ;;
2) heads=( ' .~~. ' '(: :)' ' \__/ ' ) ;;
3) heads=( ' _()_ ' ' / \ ' ' \__/ ' ) ;;
4) heads=( ' .--._' ' | |)' ' \__/ ' ) ;;
esac
_zshell_sprite $((sx+1)) $sy 5 "${heads[@]}"
for (( row=0; row<body_rows; row++ )); do
case $build in
0) line=' | | ' ;;
1) line=' | | ' ;;
2) line=' | | ' ;;
esac
if (( row == body_rows-1 )); then
case $build in
0) line=' |_| ' ;;
1) line=' |__| ' ;;
2) line=' |____| ' ;;
esac
(( outfit == 1 )) && line=' /____\ '
fi
_zshell_put $sx $((sy+3+row)) 5 "$line"
done
# Longer hair continues beside the shoulders; outfits aren't tied to it.
if (( hair == 1 && body_rows > 1 )); then
_zshell_put $((sx+1)) $((sy+3)) 5 '|'
_zshell_put $((sx+6)) $((sy+3)) 5 '|'
fi
leg_y=$((sy+3+body_rows))
if (( outfit == 2 )); then
if (( pose%2 )); then
_zshell_sprite $sx $leg_y 5 ' | |\ \ ' ' |_/_\_\'
else
_zshell_sprite $sx $leg_y 5 ' | || | ' ' |_||_| '
fi
else
case $pose in
0) _zshell_sprite $sx $leg_y 5 ' / \ ' ' _/ \_ ' ;;
1) _zshell_sprite $sx $leg_y 5 ' > / ' ' / /_ ' ;;
2) _zshell_sprite $sx $leg_y 5 ' / < ' ' /_ \_ ' ;;
3) _zshell_sprite $sx $leg_y 5 ' \ \ ' ' _\ _\ ' ;;
esac
fi
# Arms animate around each silhouette, leaving hair and clothes stable.
if (( pose == 1 || pose == 3 )); then
_zshell_put $sx $sy 5 '\'
_zshell_put $((sx+1)) $((sy+1)) 5 '\'
_zshell_put $((sx+1)) $((sy+2)) 5 '|'
else
_zshell_put $((sx+1)) $((sy+3)) 5 '/'
_zshell_put $sx $((sy+4)) 5 '/'
fi
if (( pose == 1 || pose == 2 )); then
_zshell_put $((sx+7)) $sy 5 '/'
_zshell_put $((sx+6)) $((sy+1)) 5 '/'
_zshell_put $((sx+6)) $((sy+2)) 5 '|'
else
_zshell_put $((sx+6)) $((sy+3)) 5 '\'
_zshell_put $((sx+7)) $((sy+4)) 5 '\'
fi
done
_zshell_put 3 $height 0 'SCREENSAVER / PARTY'
_zshell_put $((width-21)) $height 0 'any key: after-party'
}
_zshell_scene() {
integer y x i j base phase sway drift bob floor=$(( height - 5 ))
local leaf bubble
blank=${(pl:$width:: :)}
canvas=() inks=()
for (( y=1; y<=height; y++ )); do
canvas+=( "$blank" )
inks+=( "${(pl:$width::0:)}" )
done
if (( width < 48 || height < 18 )); then
if [[ $scene == aquarium ]]; then
_zshell_put 2 2 1 'A little more ocean, please.'
else
_zshell_put 2 2 1 'A little more room, please.'
fi
_zshell_put 2 4 0 'Resize to at least 49 x 18.'
_zshell_put 2 6 0 'Any key returns to your shell.'
return
fi
if [[ $scene == logo ]]; then
_zshell_logo
return
elif [[ $scene == hermit ]]; then
_zshell_hermit
return
elif [[ $scene == party ]]; then
_zshell_party
return
fi
_zshell_put 3 2 1 'O H M Y Z S H'
_zshell_put $((width-17)) 2 0 'a shell aquarium'
_zshell_put 3 $height 0 'SCREENSAVER / AQUARIUM'
_zshell_put $((width-22)) $height 0 'any key: back to shore'
# Tiny particles occupy the background; bubbles rise at different speeds.
for (( i=0; i<width/7; i++ )); do
x=$(( 2 + (i * 31 + tick / 24) % (width - 3) ))
y=$(( 4 + (i * 11 + 3) % (floor - 5) ))
_zshell_put $x $y 0 '.'
done
for (( i=0; i<5; i++ )); do
y=$(( floor - 1 - (tick / (3 + i % 2) + i * 5) % (floor - 4) ))
x=$(( 5 + (i * 23 + tick / 18 + (y % 3)) % (width - 9) ))
bubble='o'
(( (y+i) % 3 )) && bubble='.'
_zshell_put $x $y 7 "$bubble"
done
# Sand and tide marks.
for (( x=2; x<width; x+=3 )); do
_zshell_put $x $((floor+3)) 8 '.'
(( x % 2 )) && _zshell_put $((x+1)) $((floor+2)) 8 '_'
done
for (( i=0; i<4; i++ )); do
base=$(( i < 2 ? 3 + i * 4 : width - 4 - (i-2)*4 ))
for (( j=0; j<4+i%2; j++ )); do
sway=$(( (tick/7 + j + i) % 4 ))
leaf=')'; (( sway > 1 )) && leaf='('
_zshell_put $((base + (sway>1))) $((floor+1-j)) 6 "$leaf"
done
done
# Spirals ride a slow current. A triangle wave provides gentle vertical bob.
drift=$(( (tick / 9) % (width + 18) ))
x=$(( (width/4 + drift) % (width+18) - 9 ))
phase=$(( tick/12 % 6 )); bob=$(( phase < 3 ? phase : 5-phase ))
_zshell_sprite $x $((5+bob)) 2 ' __ ' ' /@/)' ' \_/'
# Nautilus moves in the opposite direction, tentacles trailing behind it.
x=$(( (width*3/4 + width+20 - tick/6 % (width+20)) % (width+20) - 10 ))
_zshell_sprite $x $((5+(tick/18%2))) 2 ' .---. ' ' / (@) \ ' ' \ __/~~' ' `" ~~'
# A fan shell closes briefly, then scoots during the same part of its cycle.
phase=$(( tick % 100 ))
drift=$(( (tick/100*4 + (phase>80 ? (phase-80)/5 : 0)) % (width+14) ))
x=$(( (width/2 + drift) % (width+14) - 7 ))
y=$(( floor-4 - (phase>80 && phase<95 ? 1 : 0) ))
if (( phase > 80 )); then
_zshell_sprite $x $y 4 ' _______ ' '(_______)' ' \/'
else
_zshell_sprite $x $y 4 ' .-----.' ' /|/|\|\' ' \|||||/' ' \___/'
fi
# Conch and hermit crab live on different stretches of the seabed.
x=$(( 7 + (width/5 + tick/24) % (width-22) ))
_zshell_sprite $x $((floor-1)) 3 ' /\__' ' _/ / /\>' '(__/_/_)'
x=$(( 8 + (width*2/3 + tick/10) % (width-22) ))
if (( tick/5 % 2 )); then
_zshell_sprite $x $((floor-1)) 5 ' .-~-. ' ' ( ~ )oo' ' _/___/\\_'
else
_zshell_sprite $x $((floor-1)) 5 ' .-~-. ' ' ( ~ )oo' ' /_/_/ /\'
fi
}
# Render color runs rather than individual cells. One write per frame and no
# per-frame subprocesses; the final column stays unused to prevent autowrap.
_zshell_render() {
integer row n
local text colors ink run
result=${terminfo[home]}
for (( row=1; row<=height; row++ )); do
text=${canvas[$row]} colors=${inks[$row]}
while [[ -n $colors ]]; do
ink=${colors[1]}
run=${colors%%[^$ink]*}
n=${#run}
result+="${palette[$((ink+1))]}${text[1,$n]}"
text=${text[$((n+1)),-1]}
colors=${colors[$((n+1)),-1]}
done
result+=${terminfo[el]}
(( row < height )) && result+=$'\r\n'
done
result+=${terminfo[sgr0]}
}
if (( snapshot )); then
_zshell_scene
print -r -- "${(F)canvas}"
exit 0
fi
if [[ ! -t 0 || ! -t 1 || -z $TERM || $TERM == dumb ]]; then
print -u2 -r -- 'omz shellsaver: run in an interactive terminal (or use --snapshot)'
exit 1
fi
zmodload zsh/terminfo || exit 1
zmodload zsh/datetime || exit 1
local capability
for capability in smcup rmcup home clear civis cnorm sgr0 el; do
if [[ -z ${terminfo[$capability]} ]]; then
print -u2 -r -- "omz shellsaver: terminal lacks $capability; try --snapshot"
exit 1
fi
done
if (( mono || ${+NO_COLOR} || ${terminfo[colors]:-0} < 8 )); then
palette=( '' '' '' '' '' '' '' '' '' )
elif (( ${terminfo[colors]} >= 256 )); then
palette=( $'\e[38;5;60m' $'\e[38;5;159m' $'\e[38;5;183m'
$'\e[38;5;222m' $'\e[38;5;210m' $'\e[38;5;230m'
$'\e[38;5;72m' $'\e[38;5;110m' $'\e[38;5;137m' )
else
palette=( $'\e[34m' $'\e[36m' $'\e[35m' $'\e[33m' $'\e[31m'
$'\e[37m' $'\e[32m' $'\e[36m' $'\e[33m' )
fi
# Match the updater's exact truecolor and 256-color rainbow. The other
# scenes retain their own palettes, and monochrome always takes precedence.
if [[ $scene == logo ]] && (( !mono && !${+NO_COLOR} && ${terminfo[colors]:-0} >= 8 )); then
local -a rainbow
if [[ $COLORTERM == (truecolor|24bit) || $TERM == (iterm|tmux-truecolor|linux-truecolor|xterm-truecolor|screen-truecolor) ]]; then
rainbow=( $'\e[38;2;255;0;0m' $'\e[38;2;255;97;0m' $'\e[38;2;247;255;0m'
$'\e[38;2;0;255;30m' $'\e[38;2;77;0;255m' $'\e[38;2;168;0;255m'
$'\e[38;2;245;0;172m' )
elif (( ${terminfo[colors]} >= 256 )); then
rainbow=( $'\e[38;5;196m' $'\e[38;5;202m' $'\e[38;5;226m'
$'\e[38;5;082m' $'\e[38;5;021m' $'\e[38;5;093m' $'\e[38;5;163m' )
else
rainbow=( $'\e[31m' $'\e[33m' $'\e[33m' $'\e[32m' $'\e[34m' $'\e[35m' $'\e[35m' )
fi
integer color_index
for (( color_index=1; color_index<=7; color_index++ )); do
palette[$((color_index+1))]=${rainbow[$color_index]}
done
fi
_zshell_cleanup() {
# Ignore handled signals throughout restoration, including in child stty.
# Keep active set until both the screen and terminal modes are restored.
trap '' INT TERM HUP QUIT TSTP WINCH
(( active )) || return 0
print -rn -- "${terminfo[sgr0]}${terminfo[cnorm]}${terminfo[rmcup]}"
command stty "$saved_tty" 2>/dev/null
active=0
}
_zshell_size() {
local dimensions
integer old_width=$width old_height=$height first=$resized
dimensions=$(command stty size 2>/dev/null) || return 1
local -a size=( ${=dimensions} )
(( ${#size} == 2 && size[1] > 0 && size[2] > 1 )) || return 1
height=$size[1] width=$((size[2]-1))
# Very large terminals retain a calm, bounded scene in the top-left corner.
(( width > 180 )) && width=180
(( height > 55 )) && height=55
resized=0
if (( first || old_width != width || old_height != height )); then
print -rn -- "${terminfo[clear]}"
fi
return 0
}
saved_tty=$(command stty -g) || exit 1
trap '_zshell_cleanup' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
trap 'exit 129' HUP
trap 'exit 131' QUIT
# Suspending a raw terminal would strand the parent prompt. Exit on Ctrl-Z.
trap 'exit 148' TSTP
trap 'resized=1' WINCH
{
active=1
command stty -echo -icanon -ixon min 1 time 0 || exit 1
print -rn -- "${terminfo[smcup]}${terminfo[civis]}"
float started=$EPOCHREALTIME deadline wait_for next_size_check=0
while true; do
# Zsh can defer WINCH while a timed read is active. Poll at 2 Hz as well.
if (( resized || EPOCHREALTIME >= next_size_check )); then
_zshell_size || exit 1
next_size_check=$(( EPOCHREALTIME + 0.5 ))
fi
tick=$(( (EPOCHREALTIME-started)*10 ))
if (( seconds_limit && EPOCHREALTIME-started >= seconds_limit )); then break; fi
deadline=$(( EPOCHREALTIME + 0.1 ))
_zshell_scene
_zshell_render
print -rn -- "$result"
wait_for=$(( deadline-EPOCHREALTIME ))
(( wait_for < 0.005 )) && wait_for=0.005
if read -r -k 1 -u 0 -t $wait_for key; then
# Drain the entire key sequence or paste until input goes quiet, so no
# queued suffix (including pasted commands) reaches the parent prompt.
while read -r -k 1 -u 0 -t 0.01 key; do :; done
break
fi
done
} always {
_zshell_cleanup
}
exit 0
}