Compare 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
Inkyu Oh 106f9874a2 fix(updater): accept Korean IME 'ㅛ' as yes in update prompt (#13791)
On the Korean Dubeolsik keyboard layout, the Y key position produces ㅛ
(U+315B) when a Korean IME is active. Since the update confirmation prompt
only matched [yY$'\n'], Korean users who pressed the Y key with the IME on
had their update silently cancelled. Accept ㅛ as an affirmative answer.
2026-10-11 16:39:36 -07:00
Kunpeng Xie eb29cdb33a fix(globalias): preserve quoted words and assignment values (#14092)
Only invoke alias completion when the complete shell word is an alias name. Completion otherwise treats assignment values and quoted text as standalone alias names, corrupting inputs such as DEBUG=1.

Add a real ZLE regression test covering the reported assignments, quotes, escaped aliases, filtering, and existing expansion behavior.

Assisted-by: OpenAI Codex
2026-10-11 15:58:22 -07:00
Martin MeyerandClaude Opus 5.5 535aaaf924 feat(gpg-agent): add ssh-support style to skip the gpgconf check (#14171)
The plugin runs `gpgconf --list-options gpg-agent` on every shell start
to learn whether `enable-ssh-support` is set. On macOS with GnuPG 2.5.24
that one command takes about 600 ms.

The `ssh-support` style lets the user state the answer instead. With
`no` the plugin sets `GPG_TTY` and stops. That also skips the preexec
hook, which runs `gpg-connect-agent updatestartuptty` before every
command and is only needed for ssh support. With `yes` the plugin sets
`SSH_AUTH_SOCK` without asking.

Left unset, the plugin detects as before, so existing setups do not
change.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 15:46:27 -07:00
Aryan 05c2cb0b2b fix(ruby): preserve special characters in rfind filenames (#14173) 2026-10-11 15:46:21 -07:00
Aryan bd9fb5231b docs(ruby): include -run in the rserver example (#14174) 2026-10-11 15:40:40 -07:00
ohmyzsh[bot] 48feb570f7 chore(zsh-syntax-highlighting): update to e6683f20 (#14175)
Co-authored-by: ohmyzsh[bot] <54982679+ohmyzsh[bot]@users.noreply.github.com>
2026-10-11 10:27:56 +02:00
Ken van der Eerden 42a4ccb1b1 feat(laravel): add setting to override the php command (#14146)
Aliases always ran `php artisan`, so users of Laravel Herd (`herd php`)
or a versioned binary (`php8.4`) couldn't use them. The new
`zstyle ':omz:plugins:laravel' php` setting replaces `php` in every
alias; without it the aliases are unchanged.
2026-10-09 07:11:17 -07:00
Minh Vu 2d833b453d fix(copyfile): stop on clipboard failure (#14154) 2026-10-09 07:03:29 -07:00
dhairya d7aca552b3 fix(extract): preserve bzip2 archives and honor extraction destination (#14152) 2026-10-09 07:01:26 -07:00
34 changed files with 1223 additions and 97 deletions
+1 -1
View File
@@ -75,7 +75,7 @@ dependencies:
plugins/zsh-syntax-highlighting:
repo: zsh-users/zsh-syntax-highlighting
branch: master
version: 2fc57d63067c18b1100ecdbf684fa5baf49459d1
version: e6683f20e5c38d3e5447f45845d04ddda75d0f23
precopy: |
set -e
version=$(<.version)
+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
}
+1 -1
View File
@@ -14,6 +14,6 @@ function copyfile {
return 1
fi
clipcopy $1
clipcopy $1 || return 1
echo ${(%):-"%B$1%b copied to clipboard."}
}
+74
View File
@@ -0,0 +1,74 @@
#!/usr/bin/env zsh
emulate -LR zsh
source "${1:-${0:A:h:h}/copyfile.plugin.zsh}"
test_dir=$(mktemp -d) || exit 1
trap 'command rm -rf -- "$test_dir"' EXIT
typeset -i clipboard_status=0 failures=0 tests=0
typeset output expected_output
clipcopy() {
print -r -- "$#" "$1" >| "$test_dir/call"
if (( clipboard_status )); then
print -u2 -r -- "clipboard backend failed"
return $clipboard_status
fi
command cat -- "$1" >| "$test_dir/clipboard"
}
check_copyfile() {
local description="$1" expected_status="$2" expected_stdout="$3" expected_stderr="$4"
shift 4
output=$(copyfile "$@" 2> "$test_dir/stderr")
local actual_status=$?
(( tests++ ))
if [[ $actual_status == $expected_status && $output == "$expected_stdout" && $(<"$test_dir/stderr") == "$expected_stderr" ]]; then
print -r -- "PASS: $description"
else
print -u2 -r -- "FAIL: $description"
print -u2 -r -- " status: $actual_status (expected $expected_status)"
print -u2 -r -- " stdout: ${(qqq)output} (expected ${(qqq)expected_stdout})"
print -u2 -r -- " stderr: $(<"$test_dir/stderr") (expected $expected_stderr)"
(( failures++ ))
fi
}
for filename in plain.txt 'file with spaces.txt' 'unicode-ą.txt' empty.txt; do
if [[ $filename == empty.txt ]]; then
: >| "$test_dir/$filename"
else
print -r -- "contents of $filename" >| "$test_dir/$filename"
fi
expected_output=${(%):-"%B$test_dir/$filename%b copied to clipboard."}
clipboard_status=0
check_copyfile "successful copy of $filename" 0 "$expected_output" '' "$test_dir/$filename"
if [[ $(<"$test_dir/call") != "1 $test_dir/$filename" ]] || ! command cmp -s -- "$test_dir/$filename" "$test_dir/clipboard"; then
print -u2 -r -- "FAIL: clipboard received a different argument or contents for $filename"
(( failures++ ))
fi
for clipboard_status in 1 42 127; do
check_copyfile "clipboard exit $clipboard_status for $filename" 1 '' 'clipboard backend failed' "$test_dir/$filename"
done
done
command rm -f -- "$test_dir/call"
check_copyfile 'missing argument' 1 'Usage: copyfile <file>' ''
check_copyfile 'empty argument' 1 'Usage: copyfile <file>' '' ''
check_copyfile 'nonexistent file' 1 "Error: '$test_dir/missing.txt' is not a valid file." '' "$test_dir/missing.txt"
check_copyfile 'directory argument' 1 "Error: '$test_dir' is not a valid file." '' "$test_dir"
if [[ -e "$test_dir/call" ]]; then
print -u2 -r -- 'FAIL: invalid input invoked the clipboard backend'
(( failures++ ))
fi
print -r -- "$tests tests, $failures failures"
(( failures == 0 ))
+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"
+3 -1
View File
@@ -101,7 +101,9 @@ EOF
(*.tar.lz4) lz4 -c -d "$full_path" | tar xvf - ;;
(*.tar.lrz) (( $+commands[lrzuntar] )) && lrzuntar "$full_path" ;;
(*.gz) (( $+commands[pigz] )) && pigz -cdk "$full_path" > "${file:t:r}" || gunzip -ck "$full_path" > "${file:t:r}" ;;
(*.bz2) (( $+commands[pbzip2] )) && pbzip2 -d "$full_path" || bunzip2 "$full_path" ;;
(*.bz2)
(( $+commands[pbzip2] )) && pbzip2 -cd "$full_path" > "${file:t:r}" \
|| bunzip2 -c "$full_path" > "${file:t:r}" ;;
(*.xz) xzcat "$full_path" > "${file:t:r}" ;;
(*.lrz) (( $+commands[lrunzip] )) && lrunzip "$full_path" ;;
(*.lz4) lz4 -d "$full_path" ;;
+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'
+5 -1
View File
@@ -4,7 +4,11 @@ globalias() {
# (A) makes it an array even if there's only one element
local word=${${(Az)LBUFFER}[-1]}
if [[ $GLOBALIAS_FILTER_VALUES[(Ie)$word] -eq 0 ]]; then
zle _expand_alias
# Completion also considers quoted strings and assignment values as aliases.
# Only expand when the complete shell word is an alias name.
if (( ${+aliases[$word]} || ${+galiases[$word]} )); then
zle _expand_alias
fi
zle expand-word
fi
zle self-insert
@@ -0,0 +1,81 @@
#!/usr/bin/env zsh
# Exercise the actual ZLE widgets in an isolated interactive shell.
emulate -L zsh
setopt err_return
zmodload zsh/zpty
zmodload zsh/zselect
local test_dir=$(mktemp -d)
export GLOBALIAS_TEST_PLUGIN=${0:A:h:h}/globalias.plugin.zsh
export GLOBALIAS_TEST_RESULT=$test_dir/result
wait_for_marker() {
local marker=$1 output received
local -i deadline=$(( SECONDS + 5 ))
while (( SECONDS < deadline )); do
if zpty -r globalias-test output; then
received+=$output
[[ $received == *$marker* ]] && return 0
else
zselect -t 1 || true
fi
done
print -u2 -r -- "Timed out waiting for ZLE: $received"
return 1
}
check_expansion() {
local input=$1 expected=$2 actual
zpty -w -n globalias-test "$input"$' \x18'
wait_for_marker GLOBALIAS_TEST_CAPTURED
actual=$(< "$GLOBALIAS_TEST_RESULT")
if [[ $actual != "$expected" ]]; then
print -u2 -r -- "Input: ${(qqq)input}"
print -u2 -r -- "Expected: ${(qqq)expected}; got: ${(qqq)actual}"
return 1
fi
}
{
cat > "$test_dir/setup.zsh" <<'SETUP'
autoload -Uz compinit
compinit -D
source "$GLOBALIAS_TEST_PLUGIN"
alias 1='cd -1'
alias ll='ls -l'
alias -g G='| grep'
GLOBALIAS_FILTER_VALUES=(ll)
_globalias_test_capture() {
print -rn -- "$BUFFER" > "$GLOBALIAS_TEST_RESULT"
print -r -- $'\nGLOBALIAS_TEST_CAPTURED'
BUFFER=
zle .accept-line
}
zle -N _globalias_test_capture
bindkey '^X' _globalias_test_capture
PS1='GLOBALIAS_TEST_READY> '
RPROMPT=
SETUP
zpty -b globalias-test zsh -dfi
zpty -w globalias-test "source ${(q)test_dir}/setup.zsh"
wait_for_marker GLOBALIAS_TEST_READY
check_expansion 'DEBUG=1' 'DEBUG=1 '
check_expansion 'DEBUG="1"' 'DEBUG="1" '
check_expansion "DEBUG='1'" "DEBUG='1' "
check_expansion '1' 'cd -1 '
check_expansion '"1"' '"1" '
check_expansion "'1'" "'1' "
check_expansion '\1' '\1 '
check_expansion 'echo G' 'echo | grep '
check_expansion 'DEBUG=1 1' 'DEBUG=1 cd -1 '
check_expansion 'll' 'll '
check_expansion 'echo $((1+2))' 'echo 3 '
check_expansion 'echo {a,b}' 'echo a b '
check_expansion '' ' '
print 'All globalias expansion tests passed.'
} always {
zpty -d globalias-test 2>/dev/null || true
rm -rf -- "$test_dir"
}
+31 -1
View File
@@ -5,10 +5,40 @@ Applies some fixes for some common issues encountered with [GPG's gpg-agent](htt
More specifically, this plugin:
- Updates the `GPG_TTY` environment variable before each shell execution.
- Updates the `SSH_AUTH_SOCK` environment variable if `enable-ssh-support` is turned on.
- Updates the `SSH_AUTH_SOCK` environment variable if `gpg-agent` is your SSH
agent. By default the plugin checks whether `enable-ssh-support` is turned
on. The [`ssh-support` setting](#settings) lets you say so yourself.
To use it, add `gpg-agent` to the plugins array of your zshrc file:
```zsh
plugins=(... gpg-agent)
```
## Settings
Every time a shell starts, the plugin runs `gpgconf --list-options gpg-agent` to
find out whether `enable-ssh-support` is turned on. That check can be slow. If
you already know the answer, state it with the `ssh-support` style and the
plugin skips the check. Set it in your zshrc, before Oh My Zsh is sourced.
If you don't use `gpg-agent` as your SSH agent:
```zsh
zstyle ':omz:plugins:gpg-agent' ssh-support no
```
The plugin then only sets `GPG_TTY`. It leaves `SSH_AUTH_SOCK` alone, and it
doesn't update the agent's tty before each command, which only SSH support
needs.
If you do use `gpg-agent` as your SSH agent:
```zsh
zstyle ':omz:plugins:gpg-agent' ssh-support yes
```
The plugin then sets `SSH_AUTH_SOCK` without checking. `enable-ssh-support`
still has to be set in your `gpg-agent.conf` for SSH to work.
Leave the style unset to keep the automatic check.
+16 -1
View File
@@ -1,5 +1,16 @@
export GPG_TTY=$TTY
# The ssh-support style says whether gpg-agent is used as the ssh agent.
# Setting it saves asking gpgconf, which can be slow.
zstyle -t ':omz:plugins:gpg-agent' ssh-support
_gpg_agent_ssh_support=$? # 0 if yes, 1 if no, 2 if not set
# Everything below is only needed for ssh support
if (( _gpg_agent_ssh_support == 1 )); then
unset _gpg_agent_ssh_support
return
fi
# Fix for passphrase prompt on the correct tty
# See https://www.gnupg.org/documentation/manuals/gnupg/Agent-Options.html#option-_002d_002denable_002dssh_002dsupport
function _gpg-agent_update-tty_preexec {
@@ -9,9 +20,13 @@ autoload -U add-zsh-hook
add-zsh-hook preexec _gpg-agent_update-tty_preexec
# If enable-ssh-support is set, fix ssh agent integration
if [[ $(gpgconf --list-options gpg-agent 2>/dev/null | awk -F: '$1=="enable-ssh-support" {print $10}') = 1 ]]; then
if (( _gpg_agent_ssh_support == 0 )) \
|| [[ $(gpgconf --list-options gpg-agent 2>/dev/null \
| awk -F: '$1=="enable-ssh-support" {print $10}') = 1 ]]; then
unset SSH_AGENT_PID
if [[ "${gnupg_SSH_AUTH_SOCK_by:-0}" -ne $$ ]]; then
export SSH_AUTH_SOCK="$(gpgconf --list-dirs agent-ssh-socket)"
fi
fi
unset _gpg_agent_ssh_support
+15
View File
@@ -6,6 +6,21 @@ This plugin adds aliases and autocompletion for Laravel [Artisan](https://larave
plugins=(... laravel)
```
## Settings
By default the aliases run `php artisan`. To use a different PHP command, such as
`herd php` (which runs the PHP version the [Laravel Herd](https://herd.laravel.com) site
is isolated to, instead of Herd's global version) or a specific version like `php8.4`,
set this before Oh My Zsh is sourced:
```zsh
zstyle ':omz:plugins:laravel' php 'herd php'
```
`php` in the aliases below is then replaced with that command.
## Aliases
| Alias | Description |
|:-:|:-:|
| `artisan` | `php artisan` |
+44 -39
View File
@@ -1,51 +1,56 @@
#!zsh
alias artisan='php artisan'
alias bob='php artisan bob::build'
# Command used to run artisan, e.g. `herd php` or `php8.4` (default: `php`)
zstyle -s ':omz:plugins:laravel' php _omz_laravel_php || _omz_laravel_php=php
alias artisan="$_omz_laravel_php artisan"
alias bob="$_omz_laravel_php artisan bob::build"
# Development
alias pas='php artisan serve'
alias pad='php artisan dev'
alias pats='php artisan test'
alias pas="$_omz_laravel_php artisan serve"
alias pad="$_omz_laravel_php artisan dev"
alias pats="$_omz_laravel_php artisan test"
# Database
alias pam='php artisan migrate'
alias pamf='php artisan migrate:fresh'
alias pamfs='php artisan migrate:fresh --seed'
alias pamr='php artisan migrate:rollback'
alias pads='php artisan db:seed'
alias padw='php artisan db:wipe'
alias pam="$_omz_laravel_php artisan migrate"
alias pamf="$_omz_laravel_php artisan migrate:fresh"
alias pamfs="$_omz_laravel_php artisan migrate:fresh --seed"
alias pamr="$_omz_laravel_php artisan migrate:rollback"
alias pads="$_omz_laravel_php artisan db:seed"
alias padw="$_omz_laravel_php artisan db:wipe"
# Makers
alias pamm='php artisan make:model'
alias pamc='php artisan make:controller'
alias pams='php artisan make:seeder'
alias pamt='php artisan make:test'
alias pamfa='php artisan make:factory'
alias pamp='php artisan make:policy'
alias pame='php artisan make:event'
alias pamj='php artisan make:job'
alias paml='php artisan make:listener'
alias pamn='php artisan make:notification'
alias pampp='php artisan make:provider'
alias pamcl='php artisan make:class'
alias pamen='php artisan make:enum'
alias pami='php artisan make:interface'
alias pamtr='php artisan make:trait'
alias pamv='php artisan make:view'
alias pammig='php artisan make:migration'
alias pamm="$_omz_laravel_php artisan make:model"
alias pamc="$_omz_laravel_php artisan make:controller"
alias pams="$_omz_laravel_php artisan make:seeder"
alias pamt="$_omz_laravel_php artisan make:test"
alias pamfa="$_omz_laravel_php artisan make:factory"
alias pamp="$_omz_laravel_php artisan make:policy"
alias pame="$_omz_laravel_php artisan make:event"
alias pamj="$_omz_laravel_php artisan make:job"
alias paml="$_omz_laravel_php artisan make:listener"
alias pamn="$_omz_laravel_php artisan make:notification"
alias pampp="$_omz_laravel_php artisan make:provider"
alias pamcl="$_omz_laravel_php artisan make:class"
alias pamen="$_omz_laravel_php artisan make:enum"
alias pami="$_omz_laravel_php artisan make:interface"
alias pamtr="$_omz_laravel_php artisan make:trait"
alias pamv="$_omz_laravel_php artisan make:view"
alias pammig="$_omz_laravel_php artisan make:migration"
# Clears
alias pacac='php artisan cache:clear'
alias pacoc='php artisan config:clear'
alias pavic='php artisan view:clear'
alias paroc='php artisan route:clear'
alias paopc='php artisan optimize:clear'
alias pacac="$_omz_laravel_php artisan cache:clear"
alias pacoc="$_omz_laravel_php artisan config:clear"
alias pavic="$_omz_laravel_php artisan view:clear"
alias paroc="$_omz_laravel_php artisan route:clear"
alias paopc="$_omz_laravel_php artisan optimize:clear"
# queues
alias paqf='php artisan queue:failed'
alias paqft='php artisan queue:failed-table'
alias paql='php artisan queue:listen'
alias paqr='php artisan queue:retry'
alias paqt='php artisan queue:table'
alias paqw='php artisan queue:work'
alias paqf="$_omz_laravel_php artisan queue:failed"
alias paqft="$_omz_laravel_php artisan queue:failed-table"
alias paql="$_omz_laravel_php artisan queue:listen"
alias paqr="$_omz_laravel_php artisan queue:retry"
alias paqt="$_omz_laravel_php artisan queue:table"
alias paqw="$_omz_laravel_php artisan queue:work"
unset _omz_laravel_php
+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
+2 -2
View File
@@ -14,9 +14,9 @@ plugins=(... ruby)
| ------- | -------------------------------------- | ---------------------------------------------------- |
| rb | `ruby` | The Ruby command |
| sgem | `sudo gem` | Run sudo gem on the system ruby, not the active ruby |
| rfind | `find . -name "*.rb" \| xargs grep -n` | Find ruby file |
| rfind | `find . -name "*.rb" -print0 \| xargs -0 grep -n` | Find ruby file |
| rrun | `ruby -e` | Execute some code: E.g: `rrun "puts 'Hello world!'"` |
| rserver | `ruby -e httpd . -p 8080` | Start HTTP Webrick serving local directory/files |
| rserver | `ruby -run -e httpd . -p 8080` | Start HTTP Webrick serving local directory/files |
| gein | `gem install` | Install a gem into the local repository |
| geun | `gem uninstall` | Uninstall gems from the local repository |
| geli | `gem list` | Display gems installed locally |
+1 -1
View File
@@ -2,7 +2,7 @@
alias sgem='sudo gem'
# Find ruby file
alias rfind='find . -name "*.rb" | xargs grep -n'
alias rfind='find . -name "*.rb" -print0 | xargs -0 grep -n'
# Shorthand Ruby
alias rb="ruby"
+32
View File
@@ -0,0 +1,32 @@
#!/usr/bin/env zsh
emulate -LR zsh
source "${1:-${0:A:h:h}/ruby.plugin.zsh}"
test_dir=$(mktemp -d) || exit 1
trap 'command rm -rf -- "$test_dir"' EXIT
typeset -i failures=0 tests=0
typeset filename output
for filename in plain.rb 'file with spaces.rb' "single'quote.rb" 'double"quote.rb' $'line\nbreak.rb' 'unicode-ą.rb'; do
print -r -- 'needle' >| "$test_dir/$filename"
output=$(cd "$test_dir" && rfind needle 2> "$test_dir/stderr")
actual_status=$?
(( tests++ ))
if [[ $actual_status == 0 && $output == '1:needle' && ! -s "$test_dir/stderr" ]]; then
print -r -- "PASS: ${(qqq)filename}"
else
print -u2 -r -- "FAIL: ${(qqq)filename} (status $actual_status)"
print -u2 -r -- " stdout: ${(qqq)output}"
print -u2 -r -- " stderr: $(<"$test_dir/stderr")"
(( failures++ ))
fi
command rm -- "$test_dir/$filename"
done
print -r -- "$tests tests, $failures failures"
(( failures == 0 ))
@@ -1240,9 +1240,14 @@ _zsh_highlight_main_highlighter_check_path()
fi
tmp_path=$tmp_path:a
local candidate_new_tmp_path
while [[ $tmp_path != / ]]; do
[[ -n ${(M)ZSH_HIGHLIGHT_DIRS_BLACKLIST:#$tmp_path} ]] && return 1
tmp_path=$tmp_path:h
candidate_new_tmp_path=${tmp_path:h}
if [[ $candidate_new_tmp_path == $tmp_path ]]; then
break
fi
tmp_path=${candidate_new_tmp_path}
done
if (( in_command_position )); then
+4 -1
View File
@@ -274,11 +274,14 @@ function handle_update() {
# Ask for confirmation and only update on 'y', 'Y' or Enter
# Otherwise just show a reminder for how to update
# ㅛ (U+315B) is the character produced at the Y key position on the Korean
# Dubeolsik layout, so it is accepted too to avoid cancelling the update
# when a Korean IME is active.
printf "[oh-my-zsh] Would you like to update? [Y/n] "
read -r -k 1 option
[[ "$option" = $'\n' ]] || echo
case "$option" in
[yY$'\n']) update_ohmyzsh ;;
[yY$'\n']|$'ㅛ') update_ohmyzsh ;;
[nN]) update_last_updated_file ;&
*) echo "[oh-my-zsh] You can update manually by running \`omz update\`" ;;
esac
+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
}