Compare commits

..
Author SHA1 Message Date
Robby Russell 76439de772 fix(cli): preserve literal theme browser output
Keep backslash sequences literal and enable prompt-percent rendering independently of the parent shell options. Add PTY coverage for both fixes and verify EXIT-hook preservation through the public CLI.
2026-09-09 06:13:21 -07:00
Robby Russell 3bf157dc77 refactor(cli): group theme browser files 2026-09-09 05:48:02 -07:00
Robby Russell 2e0c643117 feat(cli): add interactive theme browser 2026-09-08 19:53:23 -07:00
Lie Ryan bd77419b2a fix(virtualenvwrapper): stop the project-root walk at a .git file (#10030) 2026-09-08 15:56:43 -07:00
Berken Tekin d217d05374 feat(strug): distinguish the root shell in the prompt (#9723) 2026-09-08 14:36:55 -07:00
Robby Russell c891b750b0 core: Add merchandise link and image to README (#14081)
Added a link to official merchandise on commitgoods.com with responsive images.
2026-09-08 21:08:44 +02:00
Raihan Firdaus 6aa16696d5 feat(laravel): add pammig alias for php artisan make:migration (#14080) 2026-09-08 02:05:39 -07:00
Grant StephensandClaude Sonnet 5 8d10cc9488 feat(go): Create go_prompt_info to add Go version to theme prompts (#8924)
* Create go_prompt_info to add go version to prompts

* golang: move go_prompt_info to plugin, guard and quote output

Addresses review feedback: the prompt helper only belongs to Go
developers, so it moves out of lib/ (sourced for every shell) into
plugins/golang/golang.plugin.zsh. Adds a $+commands[go] guard so
users without Go installed don't spawn a process or see a stderr
error on every prompt render, quotes the version output the same
way the rvm/nvm helpers do so a % can't be read as a prompt escape,
and adds the dummy go_prompt_info stub to
lib/prompt_info_functions.zsh for themes that call it when the
plugin isn't loaded.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0193G8TyNqmNnv6v9HsVafGa

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-07 07:49:06 -07:00
dependabot[bot] bf77e350e0 chore(deps): bump step-security/harden-runner from 2.21.0 to 2.21.1 in the gha-minor group (#14060)
Signed-off-by: dependabot[bot] <support@github.com>
2026-09-07 10:19:48 +02:00
ohmyzsh[bot] 07351f99fc chore(gradle): update to version e7c881db (#14057) 2026-09-07 10:19:34 +02:00
Robby Russell 39334a08e9 perf(init): check zcompdump metadata without forking grep (#14061) 2026-09-07 10:18:49 +02:00
Robby Russell cba369e090 perf(update): check the update epoch before forking git (#14062) 2026-09-07 10:17:25 +02:00
Robby Russell 0da7919339 perf(init): skip the compdump lock and zrecompile when the .zwc is fresh (#14064) 2026-09-07 10:14:56 +02:00
Robby Russell 992edcc17d perf(init): only mkdir the cache completions dir when it's missing (#14065) 2026-09-07 10:12:56 +02:00
Yui(ゆい) 78cbb3fe5e fix(juju): add missing junex alias (#14076) 2026-09-07 10:09:00 +02:00
Robby Russell 76ac9fcddc chore(core): refresh links in install and upgrade (#14074) 2026-09-07 10:08:43 +02:00
Robby Russell 17bbf5fdac fix(poetry-env): scope venv_dir to the activation function (#14043) 2026-09-07 09:55:47 +02:00
Robby RussellandJan Červenka 704f7d59d8 fix(vagrant): complete VM names for suspend and rdp (#14039)
Co-authored-by: Jan Červenka <cervenka@raynet.cz>
2026-09-07 09:53:36 +02:00
Let Avocado 0b235c54de feat(web-search): add 8 new search engines (#13879)
* feat(web-search): add 8 new search engines

Add search engine aliases for:
- maven (Maven Central)
- mdn (MDN Web Docs)
- pd (pub.dev / Dart packages)
- pypi (Python packages)
- aur (Arch User Repository)
- archwiki (Arch Wiki)
- hf (Hugging Face)
- wikipedia (Wikipedia)

* fix(web-search): resolve alias conflicts
2026-09-06 13:09:02 -07:00
32 changed files with 1408 additions and 55 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ dependencies:
plugins/gradle:
repo: gradle/gradle-completion
branch: master
version: 63578c9bc56134b80766e88957ba0239e6be2343
version: e7c881dbef7462bc35ad05328397e1582fe25cb8
precopy: |
set -e
find . ! -name _gradle ! -name LICENSE -delete
+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@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.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@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.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@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.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@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.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@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
egress-policy: audit
- name: Authenticate as @ohmyzsh
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
steps:
- name: Harden the runner (Audit all outbound calls)
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
with:
egress-policy: audit
+21
View File
@@ -23,6 +23,14 @@ Twitter), and join us on [Discord](https://discord.gg/ohmyzsh).
[![Mastodon Follow](https://img.shields.io/mastodon/follow/111169632522566717?label=%40ohmyzsh&domain=https%3A%2F%2Fmstdn.social&logo=mastodon&style=flat)](https://mstdn.social/@ohmyzsh)
[![Discord server](https://img.shields.io/discord/642496866407284746)](https://discord.gg/ohmyzsh)
<a href="https://commitgoods.com/collections/oh-my-zsh?utm_source=github">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.shopify.com/s/files/1/0101/8752/files/commitgoods-badge-stacked-navy-dark.svg">
<img src="https://cdn.shopify.com/s/files/1/0101/8752/files/commitgoods-badge-stacked-navy.svg" height="28" alt="Official merch on commitgoods.com">
</picture>
</a>
<details>
<summary>Table of Contents</summary>
@@ -163,6 +171,19 @@ adds any) and extra goodies that are included in that particular plugin.
### Themes
Preview installed themes in your current directory without changing your prompt or `.zshrc`:
```zsh
omz theme browse # Interactive browser
omz theme browse agn # Start with a theme-name filter
omz theme preview agnoster # Print one isolated static preview
```
In the browser, type to filter, use Up/Down or Ctrl-P/Ctrl-N to navigate, and press Enter
for explicit use/save actions. Esc or Ctrl-C cancels without applying a theme.
See the [theme browser guide](tools/theme-browser/README.md) for shortcuts, preview limitations,
and local testing instructions.
We'll admit it. Early in the Oh My Zsh world, we may have gotten a bit too theme-happy. We have over one
hundred and fifty themes now bundled. Most of them have
[screenshots](https://github.com/ohmyzsh/ohmyzsh/wiki/Themes) on the wiki (We are working on updating this!).
+46 -1
View File
@@ -51,7 +51,7 @@ function _omz {
_describe 'command' subcmds ;;
pr) subcmds=('clean:Delete all Pull Request branches' 'test:Test a Pull Request')
_describe 'command' subcmds ;;
theme) subcmds=('list:List themes' 'set:Set a theme in your .zshrc file' 'use:Load a theme')
theme) subcmds=('browse:Browse theme previews' 'preview:Preview a theme without applying it' 'list:List themes' 'set:Set a theme in your .zshrc file' 'use:Load a theme')
_describe 'command' subcmds ;;
esac
elif (( CURRENT == 4 )); then
@@ -77,6 +77,10 @@ function _omz {
local -a opts
opts=('--enabled:List enabled plugins only')
_describe -o 'options' opts ;;
theme::(browse|preview))
local -a themes
themes=("${(@f)$(source "$ZSH/tools/theme-browser/preview.zsh"; _omz_theme_names)}")
_describe 'theme' themes ;;
theme::(set|use))
local -aU themes
themes=("$ZSH"/themes/*.zsh-theme(-.N:t:r) "$ZSH_CUSTOM"/**/*.zsh-theme(-.N:r:gs:"$ZSH_CUSTOM"/themes/:::gs:"$ZSH_CUSTOM"/:::))
@@ -749,6 +753,8 @@ Usage: ${(j: :)${(s.::.)0#_}} <command> [options]
Available commands:
browse [filter] Browse installed themes without applying them
preview <theme> Preview a theme in the current directory
list List all available Oh My Zsh themes
set <theme> Set a theme in your .zshrc file
use <theme> Load a theme
@@ -763,6 +769,45 @@ EOF
$0::$command "$@"
}
function _omz::theme::preview {
if (( $# != 1 )); then
print -u2 -r -- 'Usage: omz theme preview <theme>'
return 1
fi
source "$ZSH/tools/theme-browser/preview.zsh"
_omz_theme_preview "$1"
}
function _omz::theme::browse {
if (( $# > 1 )); then
print -u2 -r -- 'Usage: omz theme browse [filter]'
return 1
fi
if [[ ! -o interactive || ! -t 0 || ! -t 1 ]]; then
print -u2 -r -- 'omz theme browse requires an interactive terminal; use omz theme preview <theme> instead.'
return 1
fi
source "$ZSH/tools/theme-browser/preview.zsh"
source "$ZSH/tools/theme-browser/browser.zsh"
local selection action name
selection=$(_omz_theme_browser "$1") || return $?
[[ -n $selection ]] || return 0
action=${selection%%$'\n'*}
name=${selection#*$'\n'}
_omz_theme_resolve "$name" >/dev/null || return 1
case $action in
use) _omz::theme::use "$name" ;;
set)
# The existing set command interpolates the name into shell and awk text.
[[ $name != *[^a-zA-Z0-9_./-]* ]] || {
print -u2 -r -- 'Cannot save this theme name safely; set ZSH_THEME manually.'
return 1
}
_omz::theme::set "$name" ;;
*) return 1 ;;
esac
}
function _omz::theme::list {
local -a custom_themes builtin_themes
custom_themes=("$ZSH_CUSTOM"/**/*.zsh-theme(-.N:r:gs:"$ZSH_CUSTOM"/themes/:::gs:"$ZSH_CUSTOM"/:::))
+1
View File
@@ -21,6 +21,7 @@ function chruby_prompt_info \
azure_prompt_info \
tf_prompt_info \
conda_prompt_info \
go_prompt_info \
{
return 1
}
+17 -7
View File
@@ -64,7 +64,7 @@ if [[ ! -w "$ZSH_CACHE_DIR" ]]; then
fi
# Create cache and completions dir and add to $fpath
mkdir -p "$ZSH_CACHE_DIR/completions"
[[ -d "$ZSH_CACHE_DIR/completions" ]] || mkdir -p "$ZSH_CACHE_DIR/completions"
(( ${fpath[(Ie)$ZSH_CACHE_DIR/completions]} )) || fpath=("$ZSH_CACHE_DIR/completions" $fpath)
# Check for updates on initial load...
@@ -114,9 +114,17 @@ fi
zcompdump_revision="#omz revision: $(builtin cd -q "$ZSH"; git rev-parse HEAD 2>/dev/null)"
zcompdump_fpath="#omz fpath: $fpath"
# Check whether the zcompdump file carries the current OMZ metadata lines
_omz_compdump_has_metadata() {
local -a lines
[[ -r "$ZSH_COMPDUMP" ]] || return 1
lines=("${(@f)$(<"$ZSH_COMPDUMP")}")
(( ${lines[(Ie)$zcompdump_revision]} && ${lines[(Ie)$zcompdump_fpath]} ))
}
# Delete the zcompdump file if OMZ zcompdump metadata changed
if ! command grep -q -Fx "$zcompdump_revision" "$ZSH_COMPDUMP" 2>/dev/null \
|| ! command grep -q -Fx "$zcompdump_fpath" "$ZSH_COMPDUMP" 2>/dev/null; then
zcompdump_refresh=0
if ! _omz_compdump_has_metadata; then
command rm -f "$ZSH_COMPDUMP"
zcompdump_refresh=1
fi
@@ -134,9 +142,8 @@ else
compinit -u -d "$ZSH_COMPDUMP"
fi
# Append zcompdump metadata if missing
if (( $zcompdump_refresh )) \
|| ! command grep -q -Fx "$zcompdump_revision" "$ZSH_COMPDUMP" 2>/dev/null; then
# Append zcompdump metadata if missing (compinit may have regenerated the file)
if (( zcompdump_refresh )) || ! _omz_compdump_has_metadata; then
# Use `tee` in case the $ZSH_COMPDUMP filename is invalid, to silence the error
# See https://github.com/ohmyzsh/ohmyzsh/commit/dd1a7269#commitcomment-39003489
tee -a "$ZSH_COMPDUMP" &>/dev/null <<EOF
@@ -146,9 +153,12 @@ $zcompdump_fpath
EOF
fi
unset zcompdump_revision zcompdump_fpath zcompdump_refresh
unset -f _omz_compdump_has_metadata
# zcompile the completion dump file if the .zwc is older or missing.
if command mkdir "${ZSH_COMPDUMP}.lock" 2>/dev/null; then
# Test that first so the lock directory and zrecompile are skipped when it's fresh.
if [[ ! "${ZSH_COMPDUMP}.zwc" -nt "$ZSH_COMPDUMP" ]] \
&& command mkdir "${ZSH_COMPDUMP}.lock" 2>/dev/null; then
zrecompile -q -p "$ZSH_COMPDUMP"
command rm -rf "$ZSH_COMPDUMP.zwc.old" "${ZSH_COMPDUMP}.lock"
fi
+1 -20
View File
@@ -1,25 +1,6 @@
# Git version checking
autoload -Uz is-at-least
# Running `git version` forks on every startup, so cache the result for a day
# in $ZSH_CACHE_DIR, keyed on the git command path, mtime, and exec path.
() {
local cache="$ZSH_CACHE_DIR/git-version" key exec_path
local -a stat lines
zmodload -F zsh/stat b:zstat
zstat -A stat +mtime -- "$commands[git]" 2>/dev/null
exec_path="$(git --exec-path 2>/dev/null)"
key="$commands[git] $stat[1] $exec_path"
lines=("$cache"(Nm-1))
(( $#lines )) && lines=("${(@f)$(<"$cache")}")
if [[ "$lines[1]" = "$key" ]]; then
git_version="$lines[2]"
return
fi
git_version="${${(As: :)$(git version 2>/dev/null)}[3]}"
[[ ! -w "$ZSH_CACHE_DIR" ]] || print -rl -- "$key" "$git_version" >| "$cache"
}
git_version="${${(As: :)$(git version 2>/dev/null)}[3]}"
#
# Functions Current
+15
View File
@@ -39,3 +39,18 @@ plugins=(... golang)
| gov | `go vet` | Vet examines Go source code and reports suspicious constructs |
| gove | `go version` | Prints Go version |
| gow | `go work` | Work provides access to operations on workspaces |
## Prompt
This plugin provides the `go_prompt_info` function to add the installed Go
version to your prompt.
For example:
```
PROMPT="%~$ "
RPROMPT='$(go_prompt_info)'
```
changes your prompt to:
```
~/go/project$ ▋ 1.22.3
```
+9
View File
@@ -36,3 +36,12 @@ alias gotofx='go tool fix'
alias gov='go vet'
alias gove='go version'
alias gow='go work'
## prompt
function go_prompt_info() {
(( $+commands[go] )) || return
local go_prompt
go_prompt=$(go version | awk '{ print substr($3, 3) }')
[[ -z "${go_prompt}" ]] && return
echo "${ZSH_THEME_GO_PROMPT_PREFIX}${go_prompt:gs/%/%%}${ZSH_THEME_GO_PROMPT_SUFFIX}"
}
+10 -4
View File
@@ -284,6 +284,9 @@ __gradle_subcommand() {
'-Dorg.gradle.debug.suspend=[]' \
'-Dorg.gradle.debug=[]:org.gradle.debug:(true false)' \
'-Dorg.gradle.dependency.verification=[Configures the dependency verification mode. Supported values are 'strict', 'lenient', or 'off'.]:org.gradle.dependency.verification:(strict lenient off)' \
'-Dorg.gradle.isolated-projects.dangerously-ignore-problems=[]' \
'-Dorg.gradle.isolated-projects.diagnostics=[]' \
'-Dorg.gradle.isolated-projects=[Enables Isolated Projects. Projects are configured in parallel. Implies `--configuration-cache`.]' \
'-Dorg.gradle.java.home=[]:org.gradle.java.home:_directories' \
'-Dorg.gradle.java.installations.auto-detect=[]' \
'-Dorg.gradle.java.installations.auto-download=[]' \
@@ -299,8 +302,6 @@ __gradle_subcommand() {
'-Dorg.gradle.problems.report=[Enables the HTML problems report.]' \
'-Dorg.gradle.projectcachedir=[Specifies the project-specific cache directory. Default is .gradle in the root project directory.]:org.gradle.projectcachedir:_directories' \
'-Dorg.gradle.tooling.parallel=[]' \
'-Dorg.gradle.unsafe.isolated-projects.diagnostics=[]' \
'-Dorg.gradle.unsafe.isolated-projects=[]' \
'-Dorg.gradle.vfs.verbose=[]' \
'-Dorg.gradle.vfs.watch=[Enables file system watching. Reuses file system data for subsequent builds.]:org.gradle.vfs.watch:(true false)' \
'-Dorg.gradle.warning.mode=[Specifies which mode of warnings to generate. Supported values are 'all', 'fail', 'summary' (default), or 'none'.]' \
@@ -328,12 +329,14 @@ __gradle_subcommand() {
\*'--include-build[Includes the specified build in the composite.]:include build:_directories' \
(--quiet,-q,--warn,-w,--debug,-d){-i,--info}'[Sets the log level to info.]' \
\*{-I,--init-script}'[Specifies an initialization script.]:init script:_files -g \*.gradle(|.kts)' \
(--no-isolated-projects)'--isolated-projects[Enables Isolated Projects. Projects are configured in parallel. Implies `--configuration-cache`. (incubating)]' \
'--max-workers[Configures the maximum number of concurrent workers Gradle is allowed to use.]' \
(--build-cache)'--no-build-cache[Disables the Gradle build cache.]' \
(--configuration-cache)'--no-configuration-cache[Disables the configuration cache.]' \
(--configure-on-demand)'--no-configure-on-demand[Disables the use of configuration on demand. (incubating)]' \
(--continue)'--no-continue[Stops task execution after a task failure.]' \
(--daemon)'--no-daemon[Runs the build without the Gradle daemon. Useful occasionally if you have configured Gradle to always run with the daemon by default.]' \
(--isolated-projects)'--no-isolated-projects[Disables Isolated Projects. (incubating)]' \
(--parallel)'--no-parallel[Disables parallel project execution.]' \
(--problems-report)'--no-problems-report[Disables the HTML problems report. (incubating)]' \
{-a,--no-rebuild}'[Disables rebuilding of project dependencies.]' \
@@ -421,6 +424,9 @@ _gradle() {
'-Dorg.gradle.debug.suspend=[]:->argument-expected' \
'-Dorg.gradle.debug=[]:org.gradle.debug:(true false):->argument-expected' \
'-Dorg.gradle.dependency.verification=[Configures the dependency verification mode. Supported values are 'strict', 'lenient', or 'off'.]:org.gradle.dependency.verification:(strict lenient off):->argument-expected' \
'-Dorg.gradle.isolated-projects.dangerously-ignore-problems=[]:->argument-expected' \
'-Dorg.gradle.isolated-projects.diagnostics=[]:->argument-expected' \
'-Dorg.gradle.isolated-projects=[Enables Isolated Projects. Projects are configured in parallel. Implies `--configuration-cache`.]:->argument-expected' \
'-Dorg.gradle.java.home=[]:org.gradle.java.home:_directories:->argument-expected' \
'-Dorg.gradle.java.installations.auto-detect=[]:->argument-expected' \
'-Dorg.gradle.java.installations.auto-download=[]:->argument-expected' \
@@ -436,8 +442,6 @@ _gradle() {
'-Dorg.gradle.problems.report=[Enables the HTML problems report.]:->argument-expected' \
'-Dorg.gradle.projectcachedir=[Specifies the project-specific cache directory. Default is .gradle in the root project directory.]:org.gradle.projectcachedir:_directories:->argument-expected' \
'-Dorg.gradle.tooling.parallel=[]:->argument-expected' \
'-Dorg.gradle.unsafe.isolated-projects.diagnostics=[]:->argument-expected' \
'-Dorg.gradle.unsafe.isolated-projects=[]:->argument-expected' \
'-Dorg.gradle.vfs.verbose=[]:->argument-expected' \
'-Dorg.gradle.vfs.watch=[Enables file system watching. Reuses file system data for subsequent builds.]:org.gradle.vfs.watch:(true false):->argument-expected' \
'-Dorg.gradle.warning.mode=[Specifies which mode of warnings to generate. Supported values are 'all', 'fail', 'summary' (default), or 'none'.]:->argument-expected' \
@@ -466,12 +470,14 @@ _gradle() {
\*'--include-build[Includes the specified build in the composite.]:include build:_directories:->argument-expected' \
(--quiet,-q,--warn,-w,--debug,-d){-i,--info}'[Sets the log level to info.]' \
\*{-I,--init-script}'[Specifies an initialization script.]:init script:_files -g \*.gradle(|.kts):->argument-expected' \
(--no-isolated-projects)'--isolated-projects[Enables Isolated Projects. Projects are configured in parallel. Implies `--configuration-cache`. (incubating)]' \
'--max-workers[Configures the maximum number of concurrent workers Gradle is allowed to use.]:->argument-expected' \
(--build-cache)'--no-build-cache[Disables the Gradle build cache.]' \
(--configuration-cache)'--no-configuration-cache[Disables the configuration cache.]' \
(--configure-on-demand)'--no-configure-on-demand[Disables the use of configuration on demand. (incubating)]' \
(--continue)'--no-continue[Stops task execution after a task failure.]' \
(--daemon)'--no-daemon[Runs the build without the Gradle daemon. Useful occasionally if you have configured Gradle to always run with the daemon by default.]' \
(--isolated-projects)'--no-isolated-projects[Disables Isolated Projects. (incubating)]' \
(--parallel)'--no-parallel[Disables parallel project execution.]' \
(--problems-report)'--no-problems-report[Disables the HTML problems report. (incubating)]' \
{-a,--no-rebuild}'[Disables rebuilding of project dependencies.]' \
+1
View File
@@ -35,6 +35,7 @@ alias jdlr='juju debug-log --ms --replay'
alias jcon='juju consume'
alias jeb='juju export-bundle'
alias jex='juju expose'
alias junex='juju unexpose'
alias jh='juju help'
alias jkc='juju kill-controller -y -t 0'
alias jm='juju models'
+1
View File
@@ -43,6 +43,7 @@ plugins=(... laravel)
| `pami` | `php artisan make:interface` |
| `pamtr` | `php artisan make:trait` |
| `pamv` | `php artisan make:view` |
| `pammig` | `php artisan make:migration` |
## Clears
+1
View File
@@ -32,6 +32,7 @@ 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'
# Clears
+1 -1
View File
@@ -23,7 +23,7 @@ _togglePoetryShell() {
# Activate the environment if in a Poetry directory and no environment is currently active
if [[ $in_poetry_dir -eq 1 ]] && [[ $poetry_active -ne 1 ]]; then
venv_dir=$(poetry env info --path 2>/dev/null)
local venv_dir=$(poetry env info --path 2>/dev/null)
# Handle case where poetry returns "." for in-project virtual environments
if [[ "$venv_dir" == "." ]]; then
venv_dir="$PWD/.venv"
+1 -1
View File
@@ -127,7 +127,7 @@ case $state in
(box)
__vagrant-box
;;
(up|provision|port|package|destroy|reload|ssh|ssh-config|halt|resume|status)
(up|provision|port|package|destroy|reload|ssh|ssh-config|halt|resume|status|suspend|rdp)
_arguments ':feature:__vm_list'
esac
;;
@@ -39,7 +39,7 @@ if [[ ! $DISABLE_VENV_CD -eq 1 ]]; then
# Get absolute path, resolving symlinks
local PROJECT_ROOT="${PWD:A}"
while [[ "$PROJECT_ROOT" != "/" && ! -e "$PROJECT_ROOT/.venv" \
&& ! -d "$PROJECT_ROOT/.git" ]]; do
&& ! -e "$PROJECT_ROOT/.git" ]]; do
PROJECT_ROOT="${PROJECT_ROOT:h}"
done
+5
View File
@@ -51,6 +51,11 @@ Available search contexts are:
| `npmpkg` | `https://www.npmjs.com/search?q=` |
| `packagist` | `https://packagist.org/?query=` |
| `gopkg` | `https://pkg.go.dev/search?m=package&q=` |
| `maven` | `https://central.sonatype.com/search?q=` |
| `pypi` | `https://pypi.org/search/?q=` |
| `aur` | `https://aur.archlinux.org/packages?O=0&K=` |
| `archwiki` | `https://wiki.archlinux.org/index.php?search=` |
| `wikipedia` | `https://en.wikipedia.org/w/index.php?search=` |
| `chatgpt` | `https://chatgpt.com/?q=` |
| `claudeai` | `https://claude.ai/new?q=` |
| `grokcom` | `https://grok.com/?q=` |
+10 -1
View File
@@ -32,6 +32,11 @@ function web_search() {
npmpkg "https://www.npmjs.com/search?q="
packagist "https://packagist.org/?query="
gopkg "https://pkg.go.dev/search?m=package&q="
maven "https://central.sonatype.com/search?q="
pypi "https://pypi.org/search/?q="
aur "https://aur.archlinux.org/packages?O=0&K="
archwiki "https://wiki.archlinux.org/index.php?search="
wikipedia "https://en.wikipedia.org/w/index.php?search="
chatgpt "https://chatgpt.com/?q="
grok "https://grok.com/?q="
claudeai "https://claude.ai/new?q="
@@ -92,6 +97,11 @@ alias gems='web_search gems'
alias npmpkg='web_search npmpkg'
alias packagist='web_search packagist'
alias gopkg='web_search gopkg'
alias maven='web_search maven'
alias pypi='web_search pypi'
alias aur='web_search aur'
alias archwiki='web_search archwiki'
alias wikipedia='web_search wikipedia'
alias chatgpt='web_search chatgpt'
alias grokcom='web_search grok'
alias claudeai='web_search claudeai'
@@ -116,4 +126,3 @@ if [[ ${#ZSH_WEB_SEARCH_ENGINES} -gt 0 ]]; then
done
unset engines key
fi
+2 -2
View File
@@ -4,8 +4,8 @@ export LSCOLORS=dxFxCxDxBxegedabagacad
local git_branch='$(git_prompt_info)%{$reset_color%}$(git_remote_status)'
PROMPT="%{$fg[green]%}╭─%n@%m %{$reset_color%}%{$fg[yellow]%}in %~ %{$reset_color%}${git_branch}
%{$fg[green]%}╰\$ %{$reset_color%}"
PROMPT="%{%(#~$fg[red]~$fg[green])%}╭─%n@%m %{$reset_color%}%{$fg[yellow]%}in %~ %{$reset_color%}${git_branch}
%{%(#~$fg[red]~$fg[green])%}╰%(#~#~\$) %{$reset_color%}"
ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[yellow]%}on "
ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%}"
+24 -4
View File
@@ -23,12 +23,32 @@ zstyle -s ':omz:update' mode update_mode || {
# - the automatic update is disabled
# - the current user doesn't have write permissions nor owns the $ZSH directory
# - is not run from a tty
# - git is unavailable on the system
# - $ZSH is not a git repository
if [[ "$update_mode" = disabled ]] \
|| [[ ! -w "$ZSH" || ! -O "$ZSH" ]] \
|| [[ ! -t 1 && ${POWERLEVEL9K_INSTANT_PROMPT:-off} == off ]] \
|| ! command git --version >/dev/null 2>&1 \
|| [[ ! -t 1 && ${POWERLEVEL9K_INSTANT_PROMPT:-off} == off ]]; then
unset update_mode
return
fi
# Cancel update if it's not time to check yet. This runs before the git checks
# below because those fork on every startup. Skipped when a background update
# left a result that still needs to be reported (EXIT_STATUS is set).
() {
local LAST_EPOCH EXIT_STATUS ERROR epoch_target
zmodload zsh/datetime
source "${ZSH_CACHE_DIR}/.zsh-update" 2>/dev/null || return 1
[[ -n "$LAST_EPOCH" && -z "$EXIT_STATUS" ]] || return 1
zstyle -s ':omz:update' frequency epoch_target || epoch_target=${UPDATE_ZSH_DAYS:-13}
(( ( EPOCHSECONDS / 60 / 60 / 24 - LAST_EPOCH ) < epoch_target ))
} && {
unset update_mode
return
}
# Cancel update if:
# - git is unavailable on the system
# - $ZSH is not a git repository
if ! command git --version >/dev/null 2>&1 \
|| (builtin cd -q "$ZSH"; ! command git rev-parse --is-inside-work-tree &>/dev/null); then
unset update_mode
return
+4 -3
View File
@@ -528,9 +528,10 @@ print_success() {
"$(fmt_code "$(fmt_link ".zshrc" "file://$zdot/.zshrc" --text)")" \
"file to select plugins, themes, and options."
printf '\n'
printf '%s\n' "• Follow us on X: $(fmt_link @ohmyzsh https://x.com/ohmyzsh)"
printf '%s\n' "Join our Discord community: $(fmt_link "Discord server" https://discord.gg/ohmyzsh)"
printf '%s\n' "Get stickers, t-shirts, coffee mugs and more: $(fmt_link "CommitGoods Shop" https://commitgoods.com/collections/oh-my-zsh)"
printf '%s\n' "• Follow along on X ($(fmt_link @ohmyzsh https://x.com/ohmyzsh)) or Bluesky ($(fmt_link @ohmyz.sh https://bsky.app/profile/ohmyz.sh))"
printf '%s\n' "Questions or ideas? Join the community on $(fmt_link Discord https://discord.gg/ohmyzsh)"
printf '%s\n' "Help support the project: sponsor us on $(fmt_link "Open Collective" https://opencollective.com/ohmyzsh),"
printf '%s\n' " or grab stickers, shirts, and swag from $(fmt_link CommitGoods https://commitgoods.com/ohmyzsh)"
printf '%s\n' $FMT_RESET
}
+55
View File
@@ -0,0 +1,55 @@
# Theme Browser
Preview installed themes without changing your current prompt:
```zsh
omz theme browse
omz theme browse agn
omz theme preview agnoster
```
The browser filters theme names as you type and renders the highlighted theme using
the current directory, including synchronous Git information.
## Controls
| Key | Action |
| --- | --- |
| Up / Down, Ctrl-P / Ctrl-N | Previous / next theme |
| Page Up / Page Down | Move one page |
| Printable text | Filter names |
| Backspace | Delete the last filter character |
| Ctrl-U | Clear the filter |
| Enter | Open selection actions |
| Esc / Ctrl-C | Cancel without applying or saving |
| `u` in selection actions | Use the theme for this session |
| `s` in selection actions | Save as default and reload the shell |
| `b` or Enter in selection actions | Return to browsing |
Using a theme changes the current session. It does not remove hooks or widgets installed
by the previous theme. Saving updates `ZSH_THEME` in `.zshrc` and reloads the shell.
## Limitations
- Previews run in a fresh shell, so plugin-dependent and interactive themes may be incomplete.
- Right prompts are labeled and shown separately rather than positioned by ZLE.
- The browser requires an interactive terminal with alternate-screen support.
- Theme code is isolated from your active shell, but it is not sandboxed. Preview only trusted themes.
## Local Testing
From this checkout, launch a disposable interactive shell:
```zsh
env ZSH="$PWD" zsh -dfi
source "$ZSH/lib/cli.zsh"
omz theme preview robbyrussell
omz theme browse
```
Run the regression suites with:
```zsh
zsh -df tools/theme-browser/tests/preview_test.zsh
zsh -df tools/theme-browser/tests/browser_test.zsh
```
+170
View File
@@ -0,0 +1,170 @@
# Loaded only by `omz theme browse`. The UI runs in a subshell; stdout is a
# two-line data handoff, while all terminal I/O goes through a private tty fd.
function _omz_theme_browser() (
emulate -L zsh
setopt extendedglob promptpercent
# Displayed names, filter text, and worker output are data, not prompt code.
unsetopt promptsubst
zmodload zsh/terminfo && zmodload zsh/zpty && zmodload zsh/system && zmodload zsh/datetime || return 1
local tty saved filter=$1 key suffix name previous='' preview='' chunk
local mode=browse action='' pty=omz-browser-$sysparams[pid] record pgid
local -a names matches lines size
local -i selected=1 page=1 height=0 width=0 rows=0 cols=0 active=0 dirty=1 i row cancelled=0
local -F render_after=0
[[ -n $terminfo[smcup] && -n $terminfo[rmcup] && -n $terminfo[cup] ]] || {
print -u2 -r -- 'Theme browser requires a terminal with alternate-screen support.'
return 1
}
exec {tty}<>/dev/tty || return 1
saved=$(command stty -g <&$tty) || return 1
names=("${(@f)$(_omz_theme_names)}")
names=("${(@)names:#}")
trap 'cancelled=1' INT TERM HUP
trap 'dirty=1' WINCH
function _omz_browser_stop {
if (( active )); then
[[ $pgid == <2-> ]] && kill -HUP -- -$pgid 2>/dev/null
# Let the preview supervisor clean up its nested worker group first.
local -i wait_count=0
while zpty -t "$pty" && (( wait_count++ < 100 )); do
zselect -t 1
done
zpty -d "$pty" 2>/dev/null
active=0
fi
}
function _omz_browser_line {
local text=${1//$'\t'/ } formatted='' MATCH MBEGIN MEND
text=${text//\%/%%}
# Tell prompt truncation that SGR has zero display width. All other terminal
# controls have already been removed by the preview backend.
while [[ $text =~ $'\e\\[[0-9;:]*m' ]]; do
formatted+=${text[1,$(( MBEGIN - 1 ))]}"%{"$MATCH"%}"
text=${text[$(( MEND + 1 )),-1]}
done
formatted+=$text
echoti cup $row 0 >&$tty
print -Prn -u $tty -- "%${width}<..<${formatted}%<<%f%k%b"
(( row++ ))
}
{
zmodload zsh/zselect || return 1
command stty -echo -icanon min 0 time 0 <&$tty || return 1
print -rn -u $tty -- "$terminfo[smcup]$terminfo[civis]"
while (( ! cancelled )); do
size=(${=$(command stty size <&$tty)})
rows=${size[1]:-24} cols=${size[2]:-80}
if (( height != rows || width != cols - 1 )); then
height=$rows width=$(( cols - 1 )) dirty=1
fi
matches=()
for name in "${names[@]}"; do
[[ ${(L)name} == *${(L)filter}* ]] && matches+=("$name")
done
(( selected > $#matches )) && selected=$#matches
(( selected < 1 )) && selected=1
name=${matches[$selected]}
if [[ $name != "$previous" ]]; then
_omz_browser_stop
previous=$name preview='' dirty=1
render_after=$(( EPOCHREALTIME + 0.12 ))
fi
# Coalesce typing and key repeats rather than starting a worker per key.
if (( ! active && EPOCHREALTIME >= render_after )); then
if [[ -n $name ]]; then
# zpty joins arguments as shell text; quote the literal theme name.
zpty -b "$pty" _omz_theme_preview "${(q)name}" || return 1
active=1
pgid=''
for record in "${(@f)$(zpty)}"; do
[[ $record == \(<->\)\ "$pty":* ]] && pgid=${${record#\(}%%\)*}
done
fi
fi
if (( active )); then
while zpty -r -t "$pty" chunk; do
preview+=${chunk//$'\r'/}
dirty=1
done
fi
page=$(( height > 24 ? 8 : 4 ))
if (( dirty )); then
print -rn -u $tty -- "$terminfo[clear]"
row=0
if (( width < 39 || height < 16 )); then
_omz_browser_line 'Resize to at least 40 columns x 16 rows.'
_omz_browser_line 'Esc / Ctrl-C: cancel'
else
_omz_browser_line 'Oh My Zsh theme browser | Esc / Ctrl-C: cancel'
_omz_browser_line "Filter: ${(V)filter} ($#matches themes)"
if [[ $mode == actions ]]; then
_omz_browser_line '[u] Use in session [s] Save + reload [b] Back'
_omz_browser_line 'Use keeps old theme hooks. Save edits .zshrc + reloads.'
else
_omz_browser_line 'Arrows / Ctrl-P,N: move | PgUp,Dn | Enter: actions'
_omz_browser_line 'Type to filter | Backspace: delete | Ctrl-U: clear'
fi
for (( i = ((selected - 1) / page) * page + 1; i <= $#matches && row < page + 4; i++ )); do
if (( i == selected )); then
_omz_browser_line "> ${(V)matches[$i]}"
else
_omz_browser_line " ${(V)matches[$i]}"
fi
done
_omz_browser_line '--- Static preview (right prompt shown separately) ---'
lines=("${(@f)preview}")
if [[ -z $preview ]]; then
[[ -n $name ]] && lines=('Rendering...') || lines=('No matching themes.')
fi
for chunk in "${lines[@]}"; do
(( row < height - 1 )) || break
_omz_browser_line "$chunk"
done
fi
dirty=0
fi
key=''
read -r -k 1 -t 0.1 -u $tty key || continue
if [[ $key == $'\e' ]]; then
suffix=''
while read -r -k 1 -t 0.03 -u $tty chunk; do
suffix+=$chunk
[[ $chunk == [A-Za-z~] || ${#suffix} -ge 8 ]] && break
done
[[ -n $suffix ]] || break
key=$'\e'$suffix
fi
[[ $key == $'\x03' ]] && break
if [[ $mode == actions ]]; then
case $key in
u) action=use; break ;;
s) action=set; break ;;
b|$'\r'|$'\n') mode=browse ;;
esac
else
case $key in
$'\e[A'|$'\eOA'|$'\x10') (( selected-- )) ;;
$'\e[B'|$'\eOB'|$'\x0e') (( selected++ )) ;;
$'\e[5~') (( selected -= page )) ;;
$'\e[6~') (( selected += page )) ;;
$'\x7f'|$'\b') filter=${filter[1,-2]}; selected=1 ;;
$'\x15') filter=''; selected=1 ;;
$'\r'|$'\n') [[ -n $name ]] && mode=actions ;;
[[:print:]]) filter+=$key; selected=1 ;;
esac
fi
dirty=1
done
} always {
_omz_browser_stop
print -rn -u $tty -- $'\e[0m'"$terminfo[cnorm]$terminfo[rmcup]"
command stty "$saved" <&$tty
exec {tty}>&-
}
(( cancelled )) && return 130
[[ -n $action ]] && print -rl -- "$action" "$name"
return 0
)
+88
View File
@@ -0,0 +1,88 @@
# Invoked only by preview.zsh in a fresh zsh -df process. Themes and hooks
# are intentionally sourced/run at top level (not inside a setup function).
emulate -R zsh
if [[ $1 == --supervise ]]; then
# This process is the private PTY's session leader. Keep the actual worker's
# standard descriptors non-TTY, and report its status outside theme state.
typeset -r _omz_preview_tmp=$2
shift 2
unsetopt monitor
command "$commands[zsh]" -df "${0:A}" "$@" \
< /dev/null > "$_omz_preview_tmp/output" 2>&1
print -r -- $? > "$_omz_preview_tmp/result"
exit
fi
typeset -r _omz_preview_root=$1 _omz_preview_theme=$2 _omz_preview_status=$3
export ZSH=${ZSH:-$_omz_preview_root}
ZSH_THEME=$4
ZSH_CUSTOM=$5
fpath=("$_omz_preview_root/functions" $fpath)
autoload -Uz colors add-zsh-hook vcs_info
colors
setopt prompt_subst prompt_percent
zstyle ':omz:alpha:lib:git' async-prompt no
ZSH_THEME_GIT_PROMPT_PREFIX='git:('
ZSH_THEME_GIT_PROMPT_SUFFIX=')'
ZSH_THEME_GIT_PROMPT_DIRTY='*'
ZSH_THEME_GIT_PROMPT_CLEAN=''
ZSH_THEME_RUBY_PROMPT_PREFIX='('
ZSH_THEME_RUBY_PROMPT_SUFFIX=')'
source "$_omz_preview_root/lib/git.zsh"
source "$_omz_preview_root/lib/vcs_info.zsh"
source "$_omz_preview_root/lib/bzr.zsh"
source "$_omz_preview_root/lib/nvm.zsh"
source "$_omz_preview_root/lib/prompt_info_functions.zsh"
source "$_omz_preview_root/lib/spectrum.zsh"
# Check syntax before accepting a final false conditional as a successful load.
# This subprocess shares the worker's execution deadline and process group.
command "$commands[zsh]" -dfn "$_omz_preview_theme" || {
print -u2 -r -- 'theme preview: theme syntax check failed'
exit 1
}
# Keep zsh's PROMPT/PS1 and RPROMPT/RPS1 aliases intact (dieter sets RPS1).
PROMPT=''
RPROMPT=''
source "$_omz_preview_theme"
typeset -i _omz_preview_load_status=$?
# Status 1 may just be a final conditional; require an initialized prompt below.
# Higher statuses include syntax/runtime errors and missing commands.
if (( _omz_preview_load_status > 1 )); then
print -u2 -r -- "theme preview: theme could not be loaded (exit $_omz_preview_load_status)"
exit 1
fi
function _omz_preview_set_status { return "$_omz_preview_status" }
typeset -i _omz_preview_hook_status=0
if (( $+functions[precmd] )); then
_omz_preview_set_status
precmd
_omz_preview_hook_status=$?
if (( _omz_preview_hook_status )); then
print -u2 -r -- "theme preview: precmd failed (exit $_omz_preview_hook_status); remaining hooks skipped"
fi
fi
for _omz_preview_hook in "${precmd_functions[@]}"; do
(( _omz_preview_hook_status )) && break
if (( $+functions[$_omz_preview_hook] )); then
_omz_preview_set_status
"$_omz_preview_hook"
_omz_preview_hook_status=$?
if (( _omz_preview_hook_status )); then
print -u2 -r -- "theme preview: precmd hook $_omz_preview_hook failed (exit $_omz_preview_hook_status); remaining hooks skipped"
fi
fi
done
if (( _omz_preview_load_status )) && [[ -z $PROMPT && -z $RPROMPT ]]; then
print -u2 -r -- "theme preview: theme did not initialize a prompt (load exit $_omz_preview_load_status)"
exit 1
fi
# Native prompt expansion handles PROMPT_SUBST once, followed by percent escapes.
# An explicit (e) pass here would re-execute literal substitutions from helpers.
print -r -- "Left prompt (status $_omz_preview_status):"
_omz_preview_set_status
print -Pr -- "${PROMPT-}"
print -r -- 'Right prompt (shown separately):'
_omz_preview_set_status
print -Pr -- "${RPROMPT-}"
(( _omz_preview_hook_status )) && exit 1
exit 42
+217
View File
@@ -0,0 +1,217 @@
# Static previews execute theme code as the current user, not in a sandbox.
# A private PTY supplies a process group without requiring shell job control.
typeset -g _OMZ_THEME_PREVIEW_DIR=${${(%):-%x}:A:h}
function _omz_theme_names() (
emulate -L zsh
setopt extendedglob
local root=${ZSH:-${_OMZ_THEME_PREVIEW_DIR:h:h}}
local custom=${ZSH_CUSTOM:-$root/custom} dir file name
local -a names files
for dir in "$custom" "$root/themes"; do
if [[ $dir == "$custom" ]]; then
files=("$dir"/**/*.zsh-theme(N-.))
else
files=("$dir"/*.zsh-theme(N-.))
fi
for file in "${files[@]}"; do
name=${${file#"$dir/"}%.zsh-theme}
[[ $dir == "$custom" ]] && name=${name#themes/}
[[ -n $name && $name != random && /$name/ != */(.|..)/* && $name != /* && $name != *[[:cntrl:]\\]* ]] || continue
names+=("$name")
done
done
(( $#names )) && print -rl -- ${(ou)names}
return 0
)
function _omz_theme_resolve() (
emulate -L zsh
setopt extendedglob
local name=$1 root=${ZSH:-${_OMZ_THEME_PREVIEW_DIR:h:h}}
local custom=${ZSH_CUSTOM:-$root/custom} dir
if (( $# != 1 )) || [[ -z $name || $name == random || /$name/ == */(.|..)/* || $name == /* || $name == *//* || $name == *[[:cntrl:]\\]* ]]; then
print -u2 -r -- 'theme preview: invalid or non-selectable theme name'
return 2
fi
for dir in "$custom" "$custom/themes" "$root/themes"; do
if [[ -f "$dir/$name.zsh-theme" ]]; then
print -r -- "${dir:A}/$name.zsh-theme"
return 0
fi
done
print -u2 -r -- 'theme preview: theme not found'
return 1
)
# One sample, with the requested exit status (0 by default). All execution and
# state changes are confined to this subshell and the fresh -df worker.
# For asynchronous callers, cancel the job group, not just zsh's waiting wrapper.
function _omz_theme_preview() (
emulate -L zsh
local theme sample=${2:-0} backend=$_OMZ_THEME_PREVIEW_DIR
if (( $# < 1 || $# > 2 )) || [[ $sample != <0-255> ]]; then
print -u2 -r -- 'theme preview: usage: _omz_theme_preview name [status 0..255]'
return 2
fi
theme=$(_omz_theme_resolve "$1") || return $?
if ! zmodload zsh/zpty || ! zmodload zsh/system || ! zmodload zsh/datetime || ! zmodload zsh/zselect; then
print -u2 -r -- 'theme preview: required zsh modules unavailable (zpty, system, datetime, zselect)'
return 1
fi
local tmp fifo_fd pty=omz-preview-$sysparams[pid] record pgid raw='' chunk error=''
local selected=$1 custom=${ZSH_CUSTOM:-${ZSH:-${backend:h:h}}/custom}
local -i active=0 cancelled=0 bytes=0 count remaining result=1
local -F deadline
trap 'cancelled=130' INT
trap 'cancelled=143' TERM
trap 'cancelled=129' HUP
{
tmp=$(command mktemp -d "${TMPDIR:-/tmp}/omz-preview.XXXXXXXX") || return 1
command mkfifo "$tmp/output" || return 1
sysopen -r -o nonblock,cloexec -u fifo_fd "$tmp/output" || return 1
# Exec immediately: a forked shell function could run inherited EXIT traps
# after returning. Both the launcher and theme worker must start fresh.
local -a launch=("$commands[zsh]" -df "$backend/preview-worker.zsh" --supervise
"$tmp" "${backend:h:h}" "$theme" "$sample" "$selected" "$custom")
deadline=$(( EPOCHREALTIME + 3 ))
if ! zpty -b "$pty" exec "${(@q)launch}"; then
print -u2 -r -- 'theme preview: could not allocate a private PTY'
return 1
fi
active=1
# The builtin listing supplies the session leader even before the child
# starts; this avoids a PID-handshake race during immediate cancellation.
for record in "${(@f)$(zpty)}"; do
if [[ $record == \(<->\)\ "$pty":* ]]; then
pgid=${${record#\(}%%\)*}
break
fi
done
if [[ $pgid != <2-> ]]; then
print -u2 -r -- 'theme preview: could not identify worker process group'
return 1
fi
while true; do
if (( cancelled )); then
error='cancelled'
break
fi
if (( EPOCHREALTIME >= deadline )); then
error='timed out after 3 seconds'
break
fi
remaining=$(( 32001 - bytes ))
if sysread -i "$fifo_fd" -s $(( remaining < 4096 ? remaining : 4096 )) -t 0.02 -c count chunk; then
raw+=$chunk
(( bytes += count ))
if (( bytes > 32000 )); then
error='output exceeded 32 KiB'
break
fi
elif [[ -s "$tmp/result" ]]; then
result=$(<"$tmp/result")
# The worker's dedicated completion status distinguishes a theme's
# early exit (including exit 0) from rendering both prompts.
[[ $result == 42 ]] || error="worker did not complete preview (exit $result)"
break
elif ! zpty -t "$pty"; then
error='worker supervisor exited before completing preview'
break
else
zselect -t 1
fi
done
} always {
# Clean up the whole group even on success. Deliberately detached processes
# are outside this boundary. Delete only our PTY, not any inherited ones.
if [[ $pgid == <2-> ]]; then
kill -TERM -- -$pgid 2>/dev/null
zselect -t 5
kill -KILL -- -$pgid 2>/dev/null
fi
(( active )) && zpty -d "$pty" 2>/dev/null
[[ -n $fifo_fd ]] && exec {fifo_fd}<&-
[[ -n $tmp ]] && command rm -rf -- "$tmp"
}
# Bound by bytes before splitting into locale-aware characters. No escape is
# emitted until it is a complete numeric SGR; all string controls are dropped,
# including their payloads and incomplete sequences at the output boundary.
unsetopt multibyte
raw=${raw[1,32000]}
setopt multibyte
local char safe='' state=text sgr=''
local -i code valid=1
for char in "${(@s::)raw}"; do
if (( cancelled )); then
error='cancelled'
break
fi
printf -v code '%d' "'$char" 2>/dev/null
case $state in
string|string-escape)
if [[ $char == $'\a' || $char == $'\xc2\x9c' || $char == $'\x9c' || ( $state == string-escape && $char == \\ ) ]]; then
state=text
elif [[ $char == $'\e' ]]; then
state=string-escape
else
state=string
fi
;;
escape)
case $char in
'[') state=csi; sgr=''; valid=1 ;;
']'|P|X|'^'|_) state=string ;;
$'\e') ;;
*)
if (( code >= 32 && code <= 47 )); then
state=intermediate
else
state=text
fi
;;
esac
;;
intermediate)
(( code >= 32 && code <= 47 )) || state=text
;;
csi)
if [[ $char == [0-9\;:] ]]; then
sgr+=$char
elif (( code >= 64 && code <= 126 )); then
[[ $char == m && $valid == 1 ]] && safe+=$'\e['${sgr}m
state=text
elif [[ $char == $'\e' ]]; then
state=escape
else
valid=0
fi
;;
text)
case $char in
$'\e') state=escape ;;
$'\xc2\x9b') state=csi; sgr=''; valid=1 ;;
# Drop raw 8-bit CSI instead of expanding it to two bytes. Filtering
# can then never enlarge the byte-bounded input buffer.
$'\x9b') state=csi; sgr=''; valid=0 ;;
$'\xc2\x90'|$'\xc2\x98'|$'\xc2\x9d'|$'\xc2\x9e'|$'\xc2\x9f'|$'\x90'|$'\x98'|$'\x9d'|$'\x9e'|$'\x9f') state=string ;;
$'\n'|$'\t') safe+=$char ;;
*)
# Reject format/directional controls even on libc implementations
# that classify them as printable. Keep private-use font glyphs.
if [[ $char == [[:print:]] ]] && ! (( code >= 0x200b && code <= 0x200f || code >= 0x2028 && code <= 0x202e || code >= 0x2060 && code <= 0x206f || code == 0xfeff )); then
safe+=$char
fi
;;
esac
;;
esac
done
(( cancelled )) && error='cancelled'
print -rn -- "$safe"$'\e[0m\n'
[[ -n $error ]] && print -u2 -r -- "theme preview: $error"
(( cancelled )) && return $cancelled
[[ -z $error ]]
)
+406
View File
@@ -0,0 +1,406 @@
#!/usr/bin/env zsh
# Run with: zsh -df tools/theme-browser/tests/browser_test.zsh
# Optionally pass one group, e.g. `slow-interrupt`, to run it alone.
# Each browser runs under a real controlling PTY, with no user startup files.
emulate -R zsh
if [[ $1 == --child ]]; then
if [[ $2 == completion ]]; then
function compdef { print -r -- "REGISTERED:${(j.:.)@}"; }
fi
source "$ZSH/lib/cli.zsh"
# Match normal OMZ sessions without sourcing any user startup/configuration.
setopt promptsubst
[[ -z $BROWSER_TEST_NO_PROMPT_PERCENT ]] || unsetopt promptpercent
print -r -- "LAZY:$+functions[_omz_theme_browser]:$+functions[_omz_theme_preview]"
if [[ $2 == completion ]]; then
# Capture the real completion function's candidates without loading the
# user's completion setup or replacing any CLI/backend functions.
function _describe { print -rl -- "DESCRIBE:$1" "${(@P)2}"; }
CURRENT=3 words=(omz theme '')
print -r -- SUBCOMMANDS
_omz
print -r -- END_SUBCOMMANDS
for action in browse preview set use; do
CURRENT=4 words=(omz theme "$action" '')
print -r -- "CANDIDATES:$action"
_omz
print -r -- "END:$action"
done
print -r -- "STILL_LAZY:$+functions[_omz_theme_browser]:$+functions[_omz_theme_preview]"
print -r -- COMPLETIONS_FINISHED
exit
fi
if [[ $2 == reject* ]]; then
case $2 in
reject-stdin) omz theme browse </dev/null ;;
reject-stdout) omz theme browse >/dev/null ;;
*) omz theme browse ;;
esac
result=$?
print -r -- "REJECT:$result:$+functions[_omz_theme_browser]:$+functions[_omz_theme_preview]"
exit
fi
function _omz::theme::use { calls+=("use:$1"); ZSH_THEME=$1; }
function _omz::theme::set { calls+=("set:$1"); ZSH_THEME=$1; }
function browser_test_hook { : parent-hook; }
function precmd { : parent-precmd; }
typeset -a calls=() precmd_functions=(browser_test_hook) chpwd_functions=(browser_test_hook)
ZSH_THEME=parent-theme PROMPT=parent-prompt RPROMPT=parent-rprompt
typeset original_hook=$functions[browser_test_hook] original_precmd=$functions[precmd]
typeset original_options=$(setopt) original_pwd=$PWD
stty rows 24 cols 100
# PENDIN is a transient kernel input-reprocessing flag, not a user mode.
typeset original_tty=$(stty -a)
original_tty=${${original_tty//-pendin/}//pendin/}
tty > "$TEST_SCRATCH/tty"
print -r -- READY
# An interactive script otherwise aborts on SIGINT instead of returning to
# the next prompt as an interactive command loop would.
trap ':' INT
case $BROWSER_TEST_EXIT_TRAP in
function) function TRAPEXIT { print -r -- EXIT_HOOK; } ;;
string) trap 'print -r -- EXIT_HOOK' EXIT ;;
esac
typeset original_exit_trap=${functions[TRAPEXIT]-}
omz theme browse "$2"
result=$?
if [[ -n $BROWSER_TEST_EXIT_TRAP ]]; then
[[ ${functions[TRAPEXIT]-} == "$original_exit_trap" ]] && print -r -- EXIT_FUNCTION_UNCHANGED
trap
trap - EXIT
fi
typeset restored_tty=$(stty -a)
restored_tty=${${restored_tty//-pendin/}//pendin/}
if [[ $restored_tty == "$original_tty" ]]; then
print -r -- TTY_RESTORED
else
print -r -- "TTY_MISMATCH:before=$original_tty after=$restored_tty"
fi
if [[ $PROMPT == parent-prompt && $RPROMPT == parent-rprompt &&
$functions[browser_test_hook] == "$original_hook" &&
$functions[precmd] == "$original_precmd" &&
${(j:,:)precmd_functions} == browser_test_hook &&
${(j:,:)chpwd_functions} == browser_test_hook &&
-o promptsubst && $(setopt) == "$original_options" && $PWD == "$original_pwd" &&
${BROWSER_TEST_LEAK-unset} == unset ]]; then
print -r -- PARENT_UNCHANGED
fi
print -r -- "RESULT:$result:${(j:,:)calls}:$ZSH_THEME"
print -r -- FINISHED
exit 0
fi
setopt err_exit pipe_fail
[[ -n $BROWSER_TEST_TRACE ]] && setopt xtrace
zmodload zsh/zpty
zmodload zsh/datetime
zmodload zsh/zselect
typeset -r repo=${0:A:h:h:h:h} self=${0:A}
typeset scratch=$(mktemp -d "${TMPDIR:-/tmp}/omz-browser-test.XXXXXXXX")
scratch=${scratch:A}
trap 'zpty -d 2>/dev/null; command rm -rf -- "$scratch"' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
mkdir -p "$scratch/home" "$scratch/custom/themes/nested" "$scratch/tmp"
export ZSH=$repo ZSH_CUSTOM=$scratch/custom HOME=$scratch/home ZDOTDIR=$scratch/home
export TMPDIR=$scratch/tmp TEST_SCRATCH=$scratch TERM=xterm-256color
unset ZSH_THEME
# Even a mistakenly enabled startup-file load must never reach real dotfiles.
print -r -- 'print STARTUP_RAN; exit 99' > "$HOME/.zshenv"
print -r -- 'PROMPT="nested fixture"' > "$ZSH_CUSTOM/themes/nested/browser-fixture.zsh-theme"
for number in {01..10}; do
print -r -- 'typeset -g BROWSER_TEST_LEAK=bad
ZSH_THEME=fixture-theme
precmd_functions=() chpwd_functions=()
function precmd { PROMPT="%F{red}FIXTURE_PREVIEW%f" }
RPROMPT="fixture-right"' > "$ZSH_CUSTOM/zz-omz-test-$number.zsh-theme"
done
print -r -- 'sleep 30 &
print -r -- $! > "$TEST_SCRATCH/slow-pid"
wait
PROMPT="SLOW_FINISHED"' > "$ZSH_CUSTOM/zz-omz-test-slow.zsh-theme"
typeset filter_probe='$(print HIT > $TEST_SCRATCH/filter-executed)'
typeset preview_probe='$(print HIT > $TEST_SCRATCH/preview-executed)'
# Emit literal preview output, rather than asking the worker to evaluate a
# prompt expression. Only a second evaluation by the browser can execute it.
print -rl -- "print -r -- ${(q)preview_probe}" 'PROMPT="literal-preview-ready"' > "$ZSH_CUSTOM/literal-browser.zsh-theme"
typeset backslash_probe='literal:\e[2J\n\c:end'
print -rl -- "print -r -- ${(q)backslash_probe}" 'PROMPT="backslash-preview-ready"' > "$ZSH_CUSTOM/backslash-browser.zsh-theme"
typeset buffer='' transcript='' chunk='' label=''
typeset -F browser_finished_at=0
function cleanup_pty {
local record group
for record in "${(@f)$(zpty)}"; do
[[ $record == \(<->\)\ browser:* ]] || continue
group=${${record#\(}%%\)*}
kill -HUP -- -$group 2>/dev/null || true
zselect -t 20 || true
# A renderer regression can loop without checking its cancellation flag.
kill -KILL -- -$group 2>/dev/null || true
done
zpty -d 2>/dev/null || true
return 0
}
function fail {
print -u2 -r -- "FAIL: $label: $1"
print -u2 -r -- "PTY transcript, last 4000 characters (escaped): ${(V)transcript[-4000,-1]}"
exit 1
}
function check {
"$@" || fail "$1 assertion failed"
}
function await {
local wanted=$1
local -F deadline=$(( EPOCHREALTIME + ${2:-6} ))
[[ $buffer == *"$wanted"* ]] && return 0
while (( EPOCHREALTIME < deadline )); do
if zpty -r browser chunk; then
buffer+=$chunk transcript+=$chunk
[[ $buffer == *"$wanted"* ]] && return 0
else
zselect -t 1 || true
fi
done
fail "timed out waiting for ${(V)wanted}"
}
function send {
buffer=''
zpty -w -n browser "$1"
}
function start {
label=$1 buffer='' transcript=''
local -a launch=("$commands[zsh]" -dfi "$self" --child "$2")
zpty -b browser exec "${(@q)launch}"
await 'LAZY:0:0'
await "$3"
}
function finish {
await FINISHED
# zpty -d can spend a second reaping an already-completed child. It is
# harness teardown, not browser cancellation/terminal-restoration latency.
browser_finished_at=$EPOCHREALTIME
check test "${transcript#*TTY_RESTORED}" != "$transcript"
check test "${transcript#*PARENT_UNCHANGED}" != "$transcript"
check test "${transcript#*RESULT:$1}" != "$transcript"
check test "${transcript#*$'\e[?1049l'}" != "$transcript"
check test "${transcript#*$'\e[?25h'}" != "$transcript"
check test "${transcript#*STARTUP_RAN}" = "$transcript"
zpty -d browser
[[ -z $BROWSER_TEST_TIMING ]] || print -r -- "TIMING: PTY deletion $(( EPOCHREALTIME - browser_finished_at ))s"
}
function test_completion {
start 'completion registration, commands, theme candidates, lazy loading' completion COMPLETIONS_FINISHED
check test "${transcript#*REGISTERED:_omz:omz}" != "$transcript"
local section=${${transcript#*SUBCOMMANDS}%END_SUBCOMMANDS*} action
check test "${section#*browse:Browse theme previews}" != "$section"
check test "${section#*preview:Preview a theme without applying it}" != "$section"
for action in browse preview set use; do
section=${${transcript#*CANDIDATES:$action}%END:$action*}
check test "${section#*DESCRIBE:theme}" != "$section"
for name in robbyrussell zz-omz-test-01 nested/browser-fixture; do
check test "${section#*$name}" != "$section"
done
done
check test "${transcript#*STILL_LAZY:0:0}" != "$transcript"
check test "${transcript#*STARTUP_RAN}" = "$transcript"
zpty -d browser
}
function test_rejection {
# Noninteractive rejection even with both descriptors attached to a PTY.
label=noninteractive
typeset -a launch=("$commands[zsh]" -df "$self" --child reject)
zpty -b browser exec "${(@q)launch}"
await 'REJECT:1:0:0'
check test "${transcript#*requires an interactive terminal}" != "$transcript"
zpty -d browser
for mode in stdin stdout; do
start "redirected $mode" "reject-$mode" 'REJECT:1:0:0'
check test "${transcript#*requires an interactive terminal}" != "$transcript"
zpty -d browser
done
label='noninteractive and redirected-I/O rejection'
}
function test_navigation {
start 'filter, navigation, actions/back, Esc isolation' zz-omz-test- '> zz-omz-test-01'
await FIXTURE_PREVIEW
send $'\e[B'; await '> zz-omz-test-02'
send $'\e[A'; await '> zz-omz-test-01'
send $'\x0e'; await '> zz-omz-test-02'
send $'\x10'; await '> zz-omz-test-01'
send $'\e[6~'; await '> zz-omz-test-05'
send $'\e[5~'; await '> zz-omz-test-01'
send '09'; await '> zz-omz-test-09'
send $'\x7f'; await '(9 themes)'
send $'\x15'; await 'Filter: ('
send 'ZZ-OMZ-TEST-03'; await '> zz-omz-test-03'
send $'\r'; await '[u] Use in session'
send b; await 'Enter: actions'
send $'\x15no-such-omz-fixture'; await 'No matching themes.'
send $'\r'; await 'No matching themes.'
send $'\e'
finish '0::parent-theme'
}
function test_use {
start 'explicit use dispatch' zz-omz-test-01 '> zz-omz-test-01'
send $'\r'; await '[u] Use in session'
send u
finish '0:use:zz-omz-test-01:zz-omz-test-01'
}
function test_promptsubst {
start 'literal filter/preview substitutions with parent promptsubst enabled' literal-browser '> literal-browser'
await "$preview_probe"
await literal-preview-ready
check test ! -e "$scratch/preview-executed"
send $'\x15'"$filter_probe"
await "Filter: $filter_probe"
await 'No matching themes.'
check test ! -e "$scratch/filter-executed"
send $'\x15literal-browser'
await "$preview_probe"
await literal-preview-ready
send $'\e'
finish '0::parent-theme'
check test ! -e "$scratch/filter-executed"
check test ! -e "$scratch/preview-executed"
}
function test_save {
start 'explicit save dispatch (stubbed)' zz-omz-test-02 '> zz-omz-test-02'
send $'\r'; await '[u] Use in session'
send s
finish '0:set:zz-omz-test-02:zz-omz-test-02'
}
function test_backslashes {
start 'literal backslashes in preview and filter' backslash-browser '> backslash-browser'
await "$backslash_probe"
await backslash-preview-ready
send $'\x15'"$backslash_probe"
await "Filter: $backslash_probe"
await 'No matching themes.'
send $'\e'
finish '0::parent-theme'
}
function test_promptpercent {
local -x BROWSER_TEST_NO_PROMPT_PERCENT=1
start 'renderer enables promptpercent without changing parent options' zz-omz-test-01 '> zz-omz-test-01'
await FIXTURE_PREVIEW
check test "${transcript#*'%<<'}" = "$transcript"
send $'\e'
finish '0::parent-theme'
}
function test_exittrap {
local -x BROWSER_TEST_EXIT_TRAP
local action
for BROWSER_TEST_EXIT_TRAP in function string; do
for action in use set cancel; do
start "inherited $BROWSER_TEST_EXIT_TRAP EXIT trap: $action" zz-omz-test-01 '> zz-omz-test-01'
await FIXTURE_PREVIEW
if [[ $action == cancel ]]; then
send $'\e'
finish '0::parent-theme'
else
send $'\r'; await '[u] Use in session'
send "${action[1]}"
finish "0:$action:zz-omz-test-01:zz-omz-test-01"
fi
check test "${transcript#*EXIT_FUNCTION_UNCHANGED}" != "$transcript"
if [[ $BROWSER_TEST_EXIT_TRAP == string ]]; then
check test "${transcript#*"trap -- 'print -r -- EXIT_HOOK' EXIT"}" != "$transcript"
fi
done
done
}
function test_interrupt {
start 'Ctrl-C restores terminal' zz-omz-test-01 '> zz-omz-test-01'
send $'\x03'
finish '130::parent-theme'
}
function test_resize {
start 'resize small and back' zz-omz-test-01 '> zz-omz-test-01'
typeset terminal=$(<"$scratch/tty")
terminal=${terminal//$'\r'/}
buffer=''
stty rows 10 cols 30 < "$terminal"
await '40 columns x 16 rows.'
buffer=''
stty rows 24 cols 100 < "$terminal"
await '> zz-omz-test-01'
send $'\e'
finish '0::parent-theme'
}
function test_slow {
local cancellation=$1
command rm -f "$scratch/slow-pid"
start "slow preview cancellation: $cancellation" zz-omz-test-slow '> zz-omz-test-slow'
for attempt in {1..150}; do
[[ -s "$scratch/slow-pid" ]] && break
zselect -t 1 || true
done
check test -s "$scratch/slow-pid"
typeset -F started=$EPOCHREALTIME
case $cancellation in
navigation)
send $'\x15zz-omz-test-01'
await FIXTURE_PREVIEW 2
[[ -z $BROWSER_TEST_TIMING ]] || print -r -- "TIMING: navigation preview $(( EPOCHREALTIME - started ))s"
(( EPOCHREALTIME - started < 2 )) || fail 'replacement preview exceeded 2 seconds'
send $'\e'
finish '0::parent-theme'
;;
escape) send $'\e'; finish '0::parent-theme' ;;
interrupt) send $'\x03'; finish '130::parent-theme' ;;
esac
[[ -z $BROWSER_TEST_TIMING ]] || print -r -- "TIMING: browser completed $(( browser_finished_at - started ))s; including teardown $(( EPOCHREALTIME - started ))s"
(( browser_finished_at - started < 2 )) || fail "browser completion exceeded 2 seconds: $(( browser_finished_at - started ))s"
typeset child=$(<"$scratch/slow-pid") child_state
for attempt in {1..100}; do
child_state=$(command ps -o stat= -p "$child" 2>/dev/null || true)
[[ -z "${child_state//[ ZN+]/}" ]] && break
zselect -t 1 || true
done
[[ -z $BROWSER_TEST_TIMING ]] || print -r -- "TIMING: descendant $child state=${child_state:-absent}, cleanup polls=$attempt"
[[ -z "${child_state//[ ZN+]/}" ]] || fail "preview descendant $child still running: $child_state"
}
# Keep independent regressions running after a failed UI session.
unsetopt err_exit
typeset -i failures=0
for scenario in rejection completion navigation promptsubst backslashes promptpercent exittrap use save interrupt resize slow-navigation slow-escape slow-interrupt; do
[[ -z $1 || $scenario == "$1" ]] || continue
(
setopt err_exit
trap 'cleanup_pty' EXIT
if [[ $scenario == slow-* ]]; then
test_slow "${scenario#slow-}"
else
test_$scenario
fi
print -r -- "PASS: $label"
)
(( $? == 0 )) || (( failures++ ))
done
typeset -a leftovers
for attempt in {1..100}; do
leftovers=("$TMPDIR"/omz-preview.*(N))
(( $#leftovers == 0 )) && break
zselect -t 1 || true
done
if (( $#leftovers )); then
print -u2 -r -- "FAIL: $#leftovers preview temporary directories remain"
(( failures++ ))
fi
print -r -- "Theme browser regression groups failed: $failures"
(( failures == 0 ))
+282
View File
@@ -0,0 +1,282 @@
#!/usr/bin/env zsh
# Run with: zsh -df tools/theme-browser/tests/preview_test.zsh
emulate -R zsh
setopt err_exit pipe_fail
typeset -r repo=${0:A:h:h:h:h}
source "$repo/tools/theme-browser/preview.zsh"
typeset scratch=$(mktemp -d "${TMPDIR:-/tmp}/omz-preview-test.XXXXXXXX")
scratch=${scratch:A}
trap 'command rm -rf -- "$scratch"' EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
mkdir "$scratch/tmp"
export TMPDIR=$scratch/tmp
function check {
if ! "$@"; then
print -u2 -r -- "FAIL: ${(j: :)@}"
exit 1
fi
}
export ZSH=$scratch/omz ZSH_CUSTOM=$scratch/custom
mkdir -p "$ZSH/themes" "$ZSH_CUSTOM/themes/nested" "$scratch/home" "$scratch/work"
print -r -- "PROMPT='bundled'" > "$ZSH/themes/same.zsh-theme"
print -r -- "PROMPT='custom themes'" > "$ZSH_CUSTOM/themes/same.zsh-theme"
print -r -- "PROMPT='custom root'" > "$ZSH_CUSTOM/same.zsh-theme"
print -r -- "PROMPT='z'" > "$ZSH/themes/zebra.zsh-theme"
print -r -- "PROMPT='a'" > "$ZSH/themes/alpha.zsh-theme"
print -r -- "PROMPT='bad'" > "$ZSH/themes/"$'bad\nname.zsh-theme'
print -r -- "PROMPT='random'" > "$ZSH/themes/random.zsh-theme"
print -r -- "PROMPT='nested'" > "$ZSH_CUSTOM/themes/nested/example.zsh-theme"
check test "$(_omz_theme_names)" = $'alpha\nnested/example\nsame\nzebra'
check test "$(_omz_theme_resolve nested/example)" = "$ZSH_CUSTOM/themes/nested/example.zsh-theme"
check test "$(_omz_theme_resolve same)" = "$ZSH_CUSTOM/same.zsh-theme"
rm "$ZSH_CUSTOM/same.zsh-theme"
check test "$(_omz_theme_resolve same)" = "$ZSH_CUSTOM/themes/same.zsh-theme"
rm "$ZSH_CUSTOM/themes/same.zsh-theme"
check test "$(_omz_theme_resolve same)" = "$ZSH/themes/same.zsh-theme"
for name in ../same /same . .. nested/../same nested/./example nested//example random $'bad\nname' 'bad\name' missing ''; do
if _omz_theme_resolve "$name" > /dev/null 2>&1; then
print -u2 -r -- 'FAIL: accepted invalid/missing name'
exit 1
fi
done
# The fixtures intentionally write only inside this private, always-cleaned tree.
print -r -- 'print STARTUP_RAN; exit 99' > "$scratch/home/.zshenv"
export ZDOTDIR=$scratch/home PREVIEW_ENV=preserved
print -r -- '
typeset -g PREVIEW_LEAK=bad
export PREVIEW_ENV=changed
alias preview_leak=true
setopt noclobber
function precmd { PROMPT="hook:$PWD:$PREVIEW_ENV:%?" }
function second_hook { RPROMPT="second-hook:%?" }
precmd_functions=(second_hook)
' > "$ZSH_CUSTOM/isolation.zsh-theme"
typeset before_pwd=$PWD before_options=$(setopt) before_env=$PREVIEW_ENV
typeset output=$(_omz_theme_preview isolation 7)
check test "$PWD" = "$before_pwd"
check test "$(setopt)" = "$before_options"
check test "$PREVIEW_ENV" = "$before_env"
check test "${PREVIEW_LEAK-unset}" = unset
check test "${aliases[preview_leak]-unset}" = unset
check test "${functions[second_hook]-unset}" = unset
check test "${output#*hook:${PWD}:changed:7}" != "$output"
check test "${output#*second-hook:7}" != "$output"
check test "${output#*STARTUP_RAN}" = "$output"
print -r -- 'PROMPT="env:$PREVIEW_ENV cwd:$PWD %(?.success.failure)"' > "$ZSH_CUSTOM/status.zsh-theme"
output=$(_omz_theme_preview status)
check test "${output#*env:preserved cwd:${PWD} success}" != "$output"
output=$(_omz_theme_preview status 1)
check test "${output#*failure}" != "$output"
if _omz_theme_preview status nope >/dev/null 2>&1; then exit 1; fi
# Values produced by parameters or helpers are prompt text, not shell code for
# a second substitution pass. Check both sides and the helper's incoming status.
export PREVIEW_REEXECUTED=$scratch/reexecuted
export PREVIEW_LITERAL='$(print reexecuted > "$PREVIEW_REEXECUTED")'
print -r -- '
function literal_helper { print -r -- "helper-status:$? $PREVIEW_LITERAL" }
PROMPT='"'"'$PREVIEW_LITERAL $(literal_helper) %?'"'"'
RPROMPT='"'"'$(literal_helper) $PREVIEW_LITERAL %?'"'"'
' > "$ZSH_CUSTOM/literal.zsh-theme"
output=$(_omz_theme_preview literal 7)
check test ! -e "$PREVIEW_REEXECUTED"
check test "${output#*"$PREVIEW_LITERAL"}" != "$output"
check test "${output#*helper-status:7}" != "$output"
check test "${output#*"$PREVIEW_LITERAL 7"}" != "$output"
print -r -- 'unsetopt promptsubst; PROMPT='"'"'$PREVIEW_LITERAL %?'"'" > "$ZSH_CUSTOM/no-subst.zsh-theme"
output=$(_omz_theme_preview no-subst 7)
check test "${output#*'$PREVIEW_LITERAL 7'}" != "$output"
print -r -- 'PROMPT="conditional prompt"; [[ -n ${OMZ_PREVIEW_UNSET_SSH-} ]] && RPROMPT="remote"' > "$ZSH_CUSTOM/final-false.zsh-theme"
output=$(_omz_theme_preview final-false)
check test "${output#*conditional prompt}" != "$output"
print -r -- 'PROMPT=""' > "$ZSH_CUSTOM/empty.zsh-theme"
output=$(_omz_theme_preview empty)
print -r -- 'RPROMPT="right-only"; false' > "$ZSH_CUSTOM/right-only.zsh-theme"
output=$(_omz_theme_preview right-only)
check test "${output#*right-only}" != "$output"
# A helper can fail inside command substitution without making print -P fail.
# Its runtime diagnostic must remain visible in the sanitized partial preview.
print -r -- 'PROMPT='"'"'$( _omz_preview_missing_helper )'"'" > "$ZSH_CUSTOM/helper-failure.zsh-theme"
output=$(_omz_theme_preview helper-failure 2>&1)
check test "${output#*command not found: _omz_preview_missing_helper}" != "$output"
# A failing precmd (or array hook) stops later hooks, as in interactive zsh.
# Preserve any partial preview, but report failure rather than silently claiming
# that an incomplete initialization is a faithful preview.
for hook_setup in 'precmd() { return 3 }; precmd_functions=(must_not_run)' \
'failed_hook() { return 4 }; precmd_functions=(failed_hook must_not_run)' \
'failed_hook() { _omz_preview_missing_command }; precmd_functions=(failed_hook must_not_run)'; do
print -rl -- 'PROMPT="partial prompt"; must_not_run() { print UNEXPECTED_HOOK; }' "$hook_setup" > "$ZSH_CUSTOM/hook-failure.zsh-theme"
if _omz_theme_preview hook-failure >"$scratch/hook-error" 2>&1; then
print -u2 -r -- 'FAIL: accepted failing precmd hook'
exit 1
fi
output=$(<"$scratch/hook-error")
check test "${output#*UNEXPECTED_HOOK}" = "$output"
check test "${output#*remaining hooks skipped}" != "$output"
check test "${output#*partial prompt}" != "$output"
done
# No language runtime, timeout utility, or process-inspection utility is needed
# by the backend. The worker still has zsh's usual autoload/module search paths.
mkdir "$scratch/bin"
for name in zsh mktemp mkfifo rm; do
ln -s "$commands[$name]" "$scratch/bin/$name"
done
output=$(PATH="$scratch/bin" _omz_theme_preview status)
check test "${output#*success}" != "$output"
print -r -- '[[ ! -t 0 && ! -t 1 && ! -t 2 && ! -o interactive ]] || exit 9; PROMPT="non-tty"' > "$ZSH_CUSTOM/non-tty.zsh-theme"
output=$(_omz_theme_preview non-tty)
check test "${output#*non-tty}" != "$output"
print -r -- 'print -n -- "\e]52;c;SECRET\a\e[2J\r\b\ePSECRET\e\\"; PROMPT="%F{red}safe%f"' > "$ZSH_CUSTOM/escapes.zsh-theme"
output=$(_omz_theme_preview escapes)
check test "${output#*SECRET}" = "$output"
check test "${output#*$'\e[2J'}" = "$output"
check test "${output#*$'\r'}" = "$output"
check test "${output#*$'\e[31m'safe}" != "$output"
for code in 'return 1' 'exit 0' 'exit 7' 'if then' 'PROMPT="partial"; if then' \
'PROMPT="partial"; return 2' 'PROMPT="partial"; _omz_preview_missing_command'; do
print -r -- "$code" > "$ZSH_CUSTOM/broken.zsh-theme"
if _omz_theme_preview broken >"$scratch/error" 2>&1; then
print -u2 -r -- "FAIL: accepted broken theme: $code"
exit 1
fi
done
print -r -- 'while true; do print -r -- xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx; done' > "$ZSH_CUSTOM/flood.zsh-theme"
if _omz_theme_preview flood >"$scratch/flood" 2>&1; then exit 1; fi
check test "$(wc -c < "$scratch/flood")" -le 32768
print -r -- 'printf "\377%.0s" {1..11000}; PROMPT="safe"' > "$ZSH_CUSTOM/bytes.zsh-theme"
_omz_theme_preview bytes >"$scratch/bytes" 2>&1
check test "$(wc -c < "$scratch/bytes")" -le 32768
check test "${$(<"$scratch/bytes")#*$'\xff'}" = "$(<"$scratch/bytes")"
print -r -- 'printf "\233m%.0s" {1..12000}; PROMPT="safe"' > "$ZSH_CUSTOM/csi-bytes.zsh-theme"
_omz_theme_preview csi-bytes >"$scratch/csi-bytes" 2>&1
check test "$(wc -c < "$scratch/csi-bytes")" -le 32768
print -r -- 'sleep 30 & print -r -- $! > "$PREVIEW_PID_FILE"; wait' > "$ZSH_CUSTOM/slow.zsh-theme"
export PREVIEW_PID_FILE=$scratch/child
zmodload zsh/datetime
typeset -F started=$EPOCHREALTIME
if _omz_theme_preview slow >"$scratch/timeout" 2>&1; then exit 1; fi
check test $(( EPOCHREALTIME - started < 5 )) = 1
check test "${$(<"$scratch/timeout")#*timed out}" != "$(<"$scratch/timeout")"
typeset child=$(<"$scratch/child")
# A killed orphan may briefly remain a zombie until the system reaps it.
typeset child_state=$(command ps -o stat= -p "$child" 2>/dev/null || true)
check test -z "${child_state//[ ZN+]/}"
for signal in INT TERM HUP; do
rm -f "$PREVIEW_PID_FILE"
_omz_theme_preview slow >"$scratch/cancel" 2>&1 &
typeset preview_job=$!
for attempt in {1..100}; do
[[ -s "$PREVIEW_PID_FILE" ]] && break
sleep 0.01
done
check test -s "$PREVIEW_PID_FILE"
# zsh adds a waiting wrapper around a backgrounded subshell function.
# Signal the supervisor itself; terminal interrupts reach it via the job group.
typeset supervisor='' candidate='' parent=''
for candidate parent in ${(z)$(command ps -A -o pid= -o ppid=)}; do
if [[ $parent == $preview_job ]]; then
supervisor=$candidate
break
fi
done
check test -n "$supervisor"
kill -$signal "$supervisor"
typeset result=0
wait "$preview_job" || result=$?
if (( result != 130 && result != 143 && result != 129 )); then
print -u2 -r -- "Unexpected cancellation result: $result: $(<"$scratch/cancel")"
fi
if [[ $signal == INT ]]; then
check test "$result" = 130
elif [[ $signal == TERM ]]; then
check test "$result" = 143
else
check test "$result" = 129
fi
child=$(<"$PREVIEW_PID_FILE")
child_state=$(command ps -o stat= -p "$child" 2>/dev/null || true)
check test -z "${child_state//[ ZN+]/}"
done
print -r -- 'sleep 30 & print -r -- $! > "$PREVIEW_PID_FILE"; PROMPT="background"' > "$ZSH_CUSTOM/background.zsh-theme"
output=$(_omz_theme_preview background)
child=$(<"$PREVIEW_PID_FILE")
child_state=$(command ps -o stat= -p "$child" 2>/dev/null || true)
check test -z "${child_state//[ ZN+]/}"
# Match the browser's nested-PTY invocation, preserving other owned PTYs.
zmodload zsh/zpty
zpty -b unrelated 'exec sleep 30'
typeset -a nested_command=("$commands[zsh]" -dfc 'source "$1"; _omz_theme_preview status' zsh "$repo/tools/theme-browser/preview.zsh")
zpty -b browser exec "${(@q)nested_command}"
output=''
for attempt in {1..300}; do
if zpty -r browser chunk; then
output+=$chunk
elif ! zpty -t browser; then
break
else
sleep 0.01
fi
done
check test "${output#*success}" != "$output"
check zpty -t unrelated
zpty -d browser unrelated
# Cancellation by the browser's outer process group must also clean the
# backend's separate inner PTY group and private FIFO directory.
rm -f "$PREVIEW_PID_FILE"
nested_command=("$commands[zsh]" -dfc 'source "$1"; _omz_theme_preview slow' zsh "$repo/tools/theme-browser/preview.zsh")
zpty -b browser exec "${(@q)nested_command}"
typeset browser_group record
for record in "${(@f)$(zpty)}"; do
[[ $record == \(<->\)\ browser:* ]] && browser_group=${${record#\(}%%\)*}
done
check test -n "$browser_group"
for attempt in {1..100}; do
[[ -s "$PREVIEW_PID_FILE" ]] && break
sleep 0.01
done
check test -s "$PREVIEW_PID_FILE"
child=$(<"$PREVIEW_PID_FILE")
kill -TERM -- -$browser_group
for attempt in {1..100}; do
child_state=$(command ps -o stat= -p "$child" 2>/dev/null || true)
[[ -z "${child_state//[ ZN+]/}" ]] && break
sleep 0.01
done
check test -z "${child_state//[ ZN+]/}"
zpty -d browser
# Real themes exercise synchronous git, top-level locals, command substitution,
# and hook-driven prompt construction against an isolated repository.
export ZSH=$repo
git -C "$scratch/work" init -q
git -C "$scratch/work" symbolic-ref HEAD refs/heads/preview-branch
output=$(zsh -dfc 'builtin cd "$1"; source tools/theme-browser/preview.zsh; builtin cd "$2"; _omz_theme_preview robbyrussell' zsh "$repo" "$scratch/work")
check test "${output#*preview-branch}" != "$output"
for name in robbyrussell dieter agnoster bira nicoulaj half-life; do
output=$(builtin cd "$scratch/work"; _omz_theme_preview "$name" 7)
check test "${output#*Left prompt}" != "$output"
check test "${output#*Right prompt}" != "$output"
check test "${output#*preview-branch}" != "$output"
if [[ $name == dieter || $name == bira ]]; then
check test "${output#*7 }" != "$output"
fi
done
typeset -a leftovers=("$TMPDIR"/omz-preview.*(N))
check test "$#leftovers" = 0
print -r -- 'PASS: theme preview resolution, isolation, single-pass expansion, conditional loads, hook failures, status, sanitization, limits, cancellation, descendant cleanup, and six bundled themes'
+12 -3
View File
@@ -271,9 +271,18 @@ if LANG= git pull --quiet --rebase $remote $branch; then
printf '%s %s %s %s /____/ %s %s %s %s\n' $RAINBOW $RESET
printf '\n'
printf "${BLUE}%s${RESET}\n\n" "$message"
printf "${BLUE}${BOLD}%s %s${RESET}\n" "To keep up with the latest news and updates, follow us on X:" "$(fmt_link @ohmyzsh https://x.com/ohmyzsh)"
printf "${BLUE}${BOLD}%s %s${RESET}\n" "Want to get involved in the community? Join our Discord:" "$(fmt_link "Discord server" https://discord.gg/ohmyzsh)"
printf "${BLUE}${BOLD}%s %s${RESET}\n" "Get your Oh My Zsh swag at:" "$(fmt_link "CommitGoods Shop" https://commitgoods.com/collections/oh-my-zsh)"
printf "${BLUE}${BOLD}%s %s %s %s${RESET}\n" \
"Follow along on X" "($(fmt_link @ohmyzsh https://x.com/ohmyzsh))" \
"or Bluesky" "($(fmt_link @ohmyz.sh https://bsky.app/profile/ohmyz.sh))"
printf "${BLUE}${BOLD}%s %s${RESET}\n" \
"Questions or ideas? Join the community on" \
"$(fmt_link Discord https://discord.gg/ohmyzsh)"
printf "${BLUE}${BOLD}%s %s,${RESET}\n" \
"Help support the project: sponsor us on" \
"$(fmt_link "Open Collective" https://opencollective.com/ohmyzsh)"
printf "${BLUE}${BOLD}%s %s${RESET}\n" \
"or grab stickers, shirts, and swag from" \
"$(fmt_link CommitGoods https://commitgoods.com/ohmyzsh)"
elif [[ $verbose_mode == minimal ]]; then
printf "${BLUE}%s${RESET}\n" "$message"
fi