Compare commits

..
Author SHA1 Message Date
Robby Russell acc11bc5f4 fix(github): quote deprecation notice safely 2026-09-10 05:36:16 -07:00
Robby Russell d03d367f82 fix(gh): preserve valid completion cache on failure 2026-09-10 05:33:39 -07:00
Robby Russell eccb32c94c refactor(github)!: deprecate legacy hub plugin
BREAKING CHANGE: the github plugin is deprecated and will be removed in a future release. Use the gh plugin for GitHub CLI completion.
2026-09-10 05:33:32 -07:00
Robby Russell 24d629f7f1 fix: remove shadowed alias definitions and two related defects (#14053) 2026-09-10 13:54:45 +02:00
Robby Russell d6f3106269 fix(emacs): drop --no-wait when opening a terminal frame (#14055) 2026-09-10 13:51:47 +02:00
Robby RussellandJohn Pocock e6561f578f fix(agnoster): respect conda changeps1 and escape env name (#14042)
Co-authored-by: John Pocock <john_pocock@me.com>
2026-09-10 13:49:39 +02:00
Robby Russell 9bb15d681a fix(common-aliases): open documents with the platform's opener (#14051) 2026-09-10 13:48:50 +02:00
Robby Russell ee2acd0e44 fix(ssh-agent): add honor-existing to reuse a running agent (#14049) 2026-09-10 13:47:34 +02:00
Jason KurianandRobby Russell d5fa35e453 fix(jj): deprecated destination arg with onto (#13951)
Co-authored-by: Robby Russell <robby@planetargon.com>
2026-09-10 13:21:29 +02:00
Robby Russell 2c911e3f80 feat(herdr): add plugin for herdr (#14079) 2026-09-10 13:16:55 +02:00
cd320b5506 feat(kubectl): add kcrc alias for config rename-context (#14087)
Co-authored-by: Sarath Somana <86789968+sarathsom@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-09 16:46:40 -03:00
Robby RussellandFran Rogers e8b1cca662 fix(termsupport): set title correctly on terminals with parameterized tsl (#14052)
Co-authored-by: Fran Rogers <fran@violuma.net>
2026-09-09 17:12:33 +02:00
Robby Russell 8e5f198226 perf(spectrum): build the FG and BG tables without a 256-iteration loop (#14073) 2026-09-09 17:03:49 +02:00
Robby RussellandGalen Guyer 391cafc4cf feat(systemd): add sc-edit-full (#14050)
Co-authored-by: Galen Guyer <galen@galenguyer.com>
2026-09-09 17:02:41 +02:00
Robby Russell 3079e2f0b5 fix(jonathan): use a literal dash in the UTF-8 fill bar (#14054) 2026-09-09 17:01:18 +02:00
129de5a05f docs(kubectl): fix krsss description to say statefulset (#14083)
Co-authored-by: Evy Bongers <28902567+EvyBongers@users.noreply.github.com>
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-08 19:20:18 -07:00
34 changed files with 392 additions and 1373 deletions
-13
View File
@@ -171,19 +171,6 @@ 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!).
+1 -46
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=('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')
theme) subcmds=('list:List themes' 'set:Set a theme in your .zshrc file' 'use:Load a theme')
_describe 'command' subcmds ;;
esac
elif (( CURRENT == 4 )); then
@@ -77,10 +77,6 @@ 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"/:::))
@@ -753,8 +749,6 @@ 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
@@ -769,45 +763,6 @@ 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"/:::))
+10 -4
View File
@@ -14,10 +14,16 @@ FX=(
reverse "%{%}" no-reverse "%{%}"
)
for color in {000..255}; do
FG[$color]="%{[38;5;${color}m%}"
BG[$color]="%{[48;5;${color}m%}"
done
# Build the tables with array operations instead of a 256-iteration loop
() {
setopt localoptions noksharrays
local -a codes fg bg
codes=({000..255})
fg=( "%{"$'\e'"[38;5;"${^codes}"m%}" )
bg=( "%{"$'\e'"[48;5;"${^codes}"m%}" )
FG=( ${codes:^fg} )
BG=( ${codes:^bg} )
}
# Show all 256 colors with color number
function spectrum_ls() {
+6 -1
View File
@@ -31,7 +31,12 @@ function title {
else
# Try to use terminfo to set the title if the feature is available
if (( ${+terminfo[fsl]} && ${+terminfo[tsl]} )); then
print -Pn "${terminfo[tsl]}$1${terminfo[fsl]}"
# Emit the capabilities with echoti so terminfo does its own parameter
# substitution: some terminals (vt320) take a column argument, and
# running prompt expansion over the capability string mangles it.
echoti tsl 0
print -Pn "$1"
echoti fsl
fi
fi
;;
+1 -1
View File
@@ -241,7 +241,7 @@ function aws_regions() {
function aws_profiles() {
aws --no-cli-pager configure list-profiles 2> /dev/null && return
[[ -r "${AWS_CONFIG_FILE:-$HOME/.aws/config}" ]] || return 1
grep --color=never -Eo '\[.*\]' "${AWS_CONFIG_FILE:-$HOME/.aws/config}" | sed -E 's/^[[:space:]]*\[(profile)?[[:space:]]*([^[:space:]]+)\][[:space:]]*$/\2/g'
command grep -Eo '^[[:space:]]*\[[[:space:]]*(profile[[:space:]]+)?[^][:space:]]+[[:space:]]*\]' "${AWS_CONFIG_FILE:-$HOME/.aws/config}" | command sed -E 's/^[[:space:]]*\[[[:space:]]*(profile[[:space:]]+)?([^][:space:]]+)[[:space:]]*\]$/\2/'
}
function _aws_regions() {
+12 -9
View File
@@ -99,18 +99,21 @@ $ find . -type f 2>/dev/null
## File extension aliases
These are special aliases that are triggered when a file name is passed as the command. For example,
if the pdf file extension is aliased to `acroread` (a popular Linux pdf reader), when running `file.pdf`
that file will be open with `acroread`.
if the pdf file extension is aliased to `open_command`, running `file.pdf` opens that file with
whatever application your system uses for PDFs.
### Reading Docs
| Alias | Command | Description |
| ----- | ---------- | ---------------------------------- |
| pdf | `acroread` | Opens up a document using acroread |
| ps | `gv` | Opens up a .ps file using gv |
| dvi | `xdvi` | Opens up a .dvi file using xdvi |
| chm | `xchm` | Opens up a .chm file using xchm |
| djvu | `djview` | Opens up a .djvu file using djview |
These use `open_command`, which picks the right opener for your platform: `open` on macOS,
`xdg-open` on Linux, `cygstart` on Cygwin.
| Alias | Command | Description |
| ----- | -------------- | ----------------------------------------------- |
| pdf | `open_command` | Opens up a .pdf file in your default viewer |
| ps | `open_command` | Opens up a .ps file in your default viewer |
| dvi | `open_command` | Opens up a .dvi file in your default viewer |
| chm | `open_command` | Opens up a .chm file in your default viewer |
| djvu | `open_command` | Opens up a .djvu file in your default viewer |
### Listing files inside a packed file
@@ -71,12 +71,9 @@ if is-at-least 4.2.0; then
_media_fts=(ape avi flv m4a mkv mov mp3 mpeg mpg ogg ogm rm wav webm)
for ft in $_media_fts; do alias -s $ft=mplayer; done
#read documents
alias -s pdf=acroread
alias -s ps=gv
alias -s dvi=xdvi
alias -s chm=xchm
alias -s djvu=djview
# read documents with the platform's default application
_doc_fts=(pdf ps dvi chm djvu)
for ft in $_doc_fts; do alias -s $ft=open_command; done
#list what's inside packed file
alias -s zip="unzip -l"
+24 -5
View File
@@ -1,14 +1,33 @@
#!/bin/sh
emacsfun() {
local cmd frames
local cmd frames arg tty
# Build the Emacs Lisp command to check for suitable frames
# See https://www.gnu.org/software/emacs/manual/html_node/elisp/Frames.html#index-framep
case "$*" in
*-t*|*--tty*|*-nw*) cmd="(memq 't (mapcar 'framep (frame-list)))" ;; # if != nil, there are tty frames
*) cmd="(delete 't (mapcar 'framep (frame-list)))" ;; # if != nil, there are graphical terminals (x, w32, ns)
esac
for arg in "$@"; do
case "$arg" in
-t|--tty|-nw) tty=1; break ;;
esac
done
if [ -n "$tty" ]; then
cmd="(memq 't (mapcar 'framep (frame-list)))" # if != nil, there are tty frames
else
cmd="(delete 't (mapcar 'framep (frame-list)))" # if != nil, there are graphical terminals (x, w32, ns)
fi
# A tty frame needs the client to stay attached, so drop the --no-wait the
# `emacs` alias adds. Otherwise emacsclient returns at once and nothing opens.
if [ -n "$tty" ]; then
for arg do
shift
case "$arg" in
--no-wait) continue ;;
esac
set -- "$@" "$arg"
done
fi
# Check if there are suitable frames
frames="$(emacsclient -a '' -n -e "$cmd" 2>/dev/null |sed 's/.*\x07//g' )"
+3 -6
View File
@@ -15,9 +15,6 @@ This plugin does not add any aliases.
This plugin caches the completion script and is automatically updated when the
plugin is loaded, which is usually when you start up a new terminal emulator.
The cache is stored at:
- `$ZSH/plugins/gh/_gh` completions script
- `$ZSH_CACHE_DIR/gh_version` version of GitHub CLI, used to invalidate
the cache.
The completion script is stored at `$ZSH_CACHE_DIR/completions/_gh`. If an
update fails, the plugin keeps the last successfully generated completion
script.
+8 -2
View File
@@ -13,6 +13,12 @@ fi
zmodload -F zsh/files b:zf_mv
() {
local TMPPREFIX="$ZSH_CACHE_DIR/completions/_gh"
zf_mv -f -- =( gh completion --shell zsh ) "$TMPPREFIX"
local completion_file="$ZSH_CACHE_DIR/completions/_gh"
local completion_tmp="${completion_file}.$$.${RANDOM}"
if gh completion --shell zsh >| "$completion_tmp" && [[ -s "$completion_tmp" ]]; then
zf_mv -f -- "$completion_tmp" "$completion_file"
fi
command rm -f -- "$completion_tmp"
} &|
+34 -36
View File
@@ -1,45 +1,43 @@
# github plugin
This plugin supports working with GitHub from the command line. It provides a few things:
> [!WARNING]
> This plugin is deprecated and will be removed in a future release. It supports
> the legacy [`hub`](https://github.com/mislav/hub) CLI, not the current GitHub
> CLI. For `gh` completion, use the [`gh` plugin](../gh) instead.
* Sets up the `hub` wrapper and completions for the `git` command if you have [`hub`](https://github.com/github/hub) installed.
* Completion for the [`github` Ruby gem](https://github.com/defunkt/github-gem).
* Convenience functions for working with repos and URLs.
The plugin provides compatibility for existing `hub` users by:
### Functions
- aliasing `git` to `hub` when `hub` is installed;
- adding completion for `hub`; and
- defining convenience functions for creating repositories.
* `empty_gh` - Creates a new empty repo (with a `README.md`) and pushes it to GitHub
* `new_gh` - Initializes an existing directory as a repo and pushes it to GitHub
* `exist_gh` - Takes an existing repo and pushes it to GitHub
## Migrating to GitHub CLI
## Installation
[Hub](https://github.com/github/hub) needs to be installed if you want to use it. On OS X with Homebrew, this can be done with `brew install hub`. The `hub` completion definition needs to be added to your `$FPATH` before initializing OMZ.
The [`github` Ruby gem](https://github.com/defunkt/github-gem) needs to be installed if you want to use it.
### Configuration
These settings affect `github`'s behavior.
#### Environment variables
* `$GITHUB_USER`
* `$GITHUB_PASSWORD`
#### Git configuration options
* `github.user` - GitHub username for repo operations
See `man hub` for more details.
### Homebrew installation note
If you have installed `hub` using Homebrew, its completions may not be on your `$FPATH` if you are using the system `zsh`. Homebrew installs `zsh` completion definitions to `/usr/local/share/zsh/site-functions`, which will be on `$FPATH` for the Homebrew-installed `zsh`, but not for the system `zsh`. If you want it to work with the system `zsh`, add this to your `~/.zshrc` before it sources `oh-my-zsh.sh`.
Install [GitHub CLI](https://cli.github.com/), replace `github` with `gh` in
your plugin list, and restart your shell:
```zsh
if (( ! ${fpath[(I)/usr/local/share/zsh/site-functions]} )); then
FPATH=/usr/local/share/zsh/site-functions:$FPATH
fi
plugins=(... gh)
```
The `gh` plugin supplies completion for the installed GitHub CLI. It does not
alias `git`, because `gh` is not a Git wrapper.
The closest replacement for creating a GitHub repository from an existing
local repository is:
```zsh
gh repo create --source=. --push
```
## Deprecated functions
- `empty_gh` - creates a new repository with a `README.md` and pushes it to GitHub
- `new_gh` - initializes an existing directory and pushes it to GitHub
- `exist_gh` - pushes an existing Git repository to GitHub
These functions remain available during the deprecation period and require
`hub`. They will be removed with the plugin.
If you continue using `hub`, see its documentation for authentication and
configuration details.
+10 -7
View File
@@ -1,3 +1,6 @@
print -Pru2 -- '%F{yellow}[oh-my-zsh] The %Bgithub%b plugin is deprecated and will be removed in a future release.'
print -Pru2 -- 'It supports the legacy %Bhub%b CLI. Use the %Bgh%b plugin for GitHub CLI completion.%f'
# Set up hub wrapper for git, if it is available; https://github.com/github/hub
if (( $+commands[hub] )); then
alias git=hub
@@ -26,7 +29,7 @@ empty_gh() { # [NAME_OF_REPO]
# This function will add all non-hidden files to git.
new_gh() { # [DIRECTORY]
emulate -L zsh
local repo="$1"
local repo="${1:-.}"
cd "$repo" \
|| return
@@ -42,7 +45,7 @@ new_gh() { # [DIRECTORY]
|| return
hub create \
|| return
git push -u origin master \
git push -u origin HEAD \
|| return
}
@@ -52,18 +55,19 @@ new_gh() { # [DIRECTORY]
# to your GitHub.
exist_gh() { # [DIRECTORY]
emulate -L zsh
local repo=$1
cd "$repo"
local repo="${1:-.}"
cd "$repo" \
|| return
hub create \
|| return
git push -u origin master
git push -u origin HEAD
}
# git.io "GitHub URL"
#
# Shorten GitHub url, example:
# https://github.com/nvogel/dotzsh > https://git.io/8nU25w
# https://github.com/nvogel/dotzsh > https://git.io/8nU25w
# source: https://github.com/nvogel/dotzsh
# documentation: https://github.com/blog/985-git-io-github-url-shortener
#
@@ -74,4 +78,3 @@ git.io() {
}
# End Functions #############################################################
+163
View File
@@ -0,0 +1,163 @@
# herdr plugin
This plugin adds completion, aliases, an interactive session picker and a prompt
function for [herdr](https://herdr.dev), the runtime your
coding agents live on. herdr keeps agent terminals running in the background across
projects and machines, so you can close your terminal, reconnect later, and pick up
where the agents left off.
To use it, add `herdr` to the plugins array in your zshrc file:
```zsh
plugins=(... herdr)
```
Install herdr first if you have not already:
```sh
curl -fsSL https://herdr.dev/install.sh | sh # or: brew install herdr
```
## Learning herdr from the terminal
The fastest way to explore herdr is tab completion. Every command group and flag shows a
short description, so you can discover what is available without leaving the shell:
```
$ herdr <TAB>
agent -- Control and inspect agent panes
session -- Manage named persistent sessions
workspace -- Manage workspaces over the socket API
worktree -- Manage Git worktree-backed workspaces
...
$ herdr agent <TAB>
list -- List agents
prompt -- Submit a prompt to an agent
start -- Start a supported interactive agent in an existing pane
wait -- Wait until an agent reaches one of the requested states
...
```
A few other ways to learn as you go:
- `herdr <group>` with no subcommand prints that group's help, for example `herdr pane`.
- `herdr --skill` prints the instructions herdr gives to agents that drive it. It is a
good tour of the full command surface.
- Most control commands return JSON, so you can pipe them into `jq` in scripts.
## Common workflows
### Start and reconnect
```sh
herdr # launch, or attach to the default session
herdr --session work # use or create a named session
herdr session list # see running and stopped sessions
herdr session attach work # reconnect to a named session
herdr status # check the local client and running server
herdr update # install the latest release
```
### Organize work into workspaces and tabs
```sh
herdr workspace list
herdr workspace create --cwd ~/projects/app
herdr tab create
herdr pane split --direction right
herdr worktree create --branch feature/login # open a Git worktree in its own workspace
```
### Run and watch agents
```sh
herdr agent list # what is running, and is it idle, working or blocked
herdr agent start reviewer --kind claude --pane w1:p2
herdr agent prompt reviewer "review the last commit" --wait
herdr agent read reviewer # read the agent's terminal output
herdr agent wait reviewer --until idle
herdr agent attach reviewer # jump straight into that terminal
```
### Work across machines
```sh
herdr --remote user@build-box # attach through SSH with your local keybindings
```
### Keep the server healthy
```sh
herdr config check # validate config.toml
herdr server reload-config # apply config changes without restarting panes
herdr server stop
herdr channel set preview # switch between stable and preview releases
herdr integration status # which agent CLIs have herdr hooks installed
```
See the [CLI reference](https://herdr.dev/docs/cli-reference/) for every command and flag.
## Aliases
| Alias | Command |
| ------- | ----------------------------- |
| `hrdr` | `herdr` |
| `hrdrst` | `herdr status` |
| `hrdrup` | `herdr update` |
| `hrdrsl` | `herdr session list` |
| `hrdrsa` | `herdr session attach` |
| `hrdrr` | `herdr --remote` |
| `hrdral` | `herdr agent list` |
| `hrdrwl` | `herdr workspace list` |
| `hrdrwc` | `herdr workspace create` |
| `hrdrwt` | `herdr worktree create` |
| `hrdrps` | `herdr pane split` |
| `hrdrrc` | `herdr server reload-config` |
Tab completion works through the aliases too, so `hrdr agent <TAB>` completes the same
way `herdr agent <TAB>` does.
## Session picker
`hrdrs` lists your herdr sessions and attaches to the one you pick. It uses
[fzf](https://github.com/junegunn/fzf) when it is installed and falls back to a numbered
menu otherwise, so it works everywhere.
```
$ hrdrs
name status directory
1) default running /Users/me/.config/herdr
2) work stopped /Users/me/projects/app
Attach to session: 2
```
## Prompt
This plugin provides the `herdr_prompt_info` function to show the current pane ID in
your prompt when the shell is running inside herdr. It reads environment variables only,
so it adds no cost to prompt rendering.
For example:
```
PROMPT="%~$ "
RPROMPT='$(herdr_prompt_info)'
```
changes your prompt inside a herdr pane to:
```
~/projects/app$ ▋ w1:p4
```
and shows nothing outside herdr.
Wrap it with `ZSH_THEME_HERDR_PROMPT_PREFIX` and `ZSH_THEME_HERDR_PROMPT_SUFFIX`, for
example:
```
ZSH_THEME_HERDR_PROMPT_PREFIX="%{$fg[cyan]%}[herdr "
ZSH_THEME_HERDR_PROMPT_SUFFIX="]%{$reset_color%}"
```
## Cache
This plugin caches the completion script and automatically updates it when the plugin is
loaded, which is usually when you start a new terminal emulator.
The cache is stored at `$ZSH_CACHE_DIR/completions/_herdr`.
+68
View File
@@ -0,0 +1,68 @@
# Aliases, helpers and completion for herdr (https://herdr.dev), the runtime for coding agents.
# PROMPT
# Defined before the herdr check so themes can reference it on machines without herdr.
function herdr_prompt_info {
[[ "$HERDR_ENV" == 1 ]] || return
echo "${ZSH_THEME_HERDR_PROMPT_PREFIX}${HERDR_PANE_ID:gs/%/%%}${ZSH_THEME_HERDR_PROMPT_SUFFIX}"
}
if (( ! $+commands[herdr] )); then
return
fi
# ALIASES
alias hrdr='herdr'
alias hrdrst='herdr status'
alias hrdrup='herdr update'
alias hrdrsl='herdr session list'
alias hrdrsa='herdr session attach'
alias hrdrr='herdr --remote'
alias hrdral='herdr agent list'
alias hrdrwl='herdr workspace list'
alias hrdrwc='herdr workspace create'
alias hrdrwt='herdr worktree create'
alias hrdrps='herdr pane split'
alias hrdrrc='herdr server reload-config'
# FUNCTIONS
# Pick a session and attach to it, with fzf if available or a numbered menu otherwise.
function hrdrs {
setopt localoptions extendedglob
local -a lines
lines=("${(@f)$(herdr session list 2>/dev/null)}")
lines=("${(@)lines%%[[:space:]]#[^[:space:]]#}") # drop the socket column
if (( $#lines < 2 )); then
print "hrdrs: no herdr sessions found" >&2
return 1
fi
local choice
if (( $+commands[fzf] )); then
choice=$(print -l -- "${lines[@]}" | fzf --header-lines=1 --prompt='herdr session> ') || return
else
print -- "${lines[1]}"
local PS3="Attach to session: "
select choice in "${lines[@]:1}"; do
[[ -n "$choice" ]] && break
done
fi
[[ -n "$choice" ]] || return 1
herdr session attach "${${(z)choice}[1]}"
}
# COMPLETION
# If the completion file doesn't exist yet, we need to autoload it and
# bind it to `herdr`. Otherwise, compinit will have already done that.
if [[ ! -f "$ZSH_CACHE_DIR/completions/_herdr" ]]; then
typeset -g -A _comps
autoload -Uz _herdr
_comps[herdr]=_herdr
fi
zmodload -F zsh/files b:zf_mv
() {
local TMPPREFIX="$ZSH_CACHE_DIR/completions/_herdr"
zf_mv -f -- =( herdr completion zsh < /dev/null 2> /dev/null ) "$TMPPREFIX"
} &|
+5 -5
View File
@@ -55,7 +55,7 @@ plugins=(... jj)
| jjopl | `jj op log` |
| jjor | `jj op restore` |
| jjrb | `jj rebase` |
| jjrbm | `jj rebase -d "trunk()"` |
| jjrbm | `jj rebase -o "trunk()"` |
| jjrs | `jj restore` |
| jjrt | `cd "$(jj root \|\| echo .)"` |
| jjs | `jj show` |
@@ -120,10 +120,10 @@ that.
### Git async-prompt compatibility
If you use a wrapper function that calls `git_prompt_info` (as shown above), it won't work with
the default git async-prompt mode. This is because async-prompt only registers its background worker
when it detects `$(git_prompt_info)` literally in your prompt variables. A wrapper like
`$(_my_theme_vcs_info)` won't match, so the async output stays empty.
If you use a wrapper function that calls `git_prompt_info` (as shown above), it won't work with the default
git async-prompt mode. This is because async-prompt only registers its background worker when it detects
`$(git_prompt_info)` literally in your prompt variables. A wrapper like `$(_my_theme_vcs_info)` won't match,
so the async output stays empty.
To fix this, add one of the following to your `.zshrc` **before** Oh My Zsh is sourced:
+1 -1
View File
@@ -81,7 +81,7 @@ alias jjop='jj op'
alias jjopl='jj op log'
alias jjor='jj op restore'
alias jjrb='jj rebase'
alias jjrbm='jj rebase -d "trunk()"'
alias jjrbm='jj rebase -o "trunk()"'
alias jjrs='jj restore'
alias jjrt='cd "$(jj root || echo .)"'
alias jjs='jj show'
+2 -1
View File
@@ -23,6 +23,7 @@ plugins=(... kubectl)
| kcsc | `kubectl config set-context` | Set a context entry in kubeconfig |
| kcdc | `kubectl config delete-context` | Delete the specified context from the kubeconfig |
| kccc | `kubectl config current-context` | Display the current-context |
| kcrc | `kubectl config rename-context` | Rename a context from the kubeconfig file |
| kcgc | `kubectl config get-contexts` | List of contexts available |
| | | **General aliases** |
| kdel | `kubectl delete` | Delete resources by filenames, stdin, resources and names, or by resources and label selector |
@@ -130,7 +131,7 @@ plugins=(... kubectl)
| kdss | `kubectl describe statefulset` | Describe statefulset resource in detail |
| kdelss | `kubectl delete statefulset` | Delete the statefulset |
| ksss | `kubectl scale statefulset` | Scale a statefulset |
| krsss | `kubectl rollout status statefulset` | Check the rollout status of a deployment |
| krsss | `kubectl rollout status statefulset` | Check the rollout status of a statefulset |
| krrss | `kubectl rollout restart statefulset` | Rollout restart a statefulset |
| | | **Service Accounts management** |
| kdsa | `kubectl describe sa` | Describe a service account in details |
+1
View File
@@ -36,6 +36,7 @@ alias kcuc='kubectl config use-context'
alias kcsc='kubectl config set-context'
alias kcdc='kubectl config delete-context'
alias kccc='kubectl config current-context'
alias kcrc='kubectl config rename-context'
# List all contexts
alias kcgc='kubectl config get-contexts'
-1
View File
@@ -139,7 +139,6 @@ function rdsl() { _rails_db_command db:schema:load "$@" }
# legacy stuff
alias sc='ruby script/console'
alias sd='ruby script/destroy'
alias sd='ruby script/server --debugger'
alias sg='ruby script/generate'
alias sp='ruby script/plugin'
-1
View File
@@ -11,7 +11,6 @@
# Load bracketed-paste-magic if zsh version is >= 5.1
autoload -Uz is-at-least
if is-at-least 5.1; then
set zle_bracketed_paste # Explicitly restore this zsh default
autoload -Uz bracketed-paste-magic
zle -N bracketed-paste bracketed-paste-magic
return ### The rest of this file is NOT executed on zsh version >= 5.1 ###
+12
View File
@@ -21,6 +21,18 @@ To enable **agent forwarding support** add the following to your zshrc file:
zstyle :omz:plugins:ssh-agent agent-forwarding yes
```
### `honor-existing`
If your session already provides an ssh-agent — a desktop keyring, `gpg-agent`,
KeePassXC, or an agent forwarded over SSH — the plugin normally ignores it and
starts one of its own, which leaves you with two agents. Set this to reuse the
running agent instead, and only start a new one when `$SSH_AUTH_SOCK` is empty
or does not answer:
```zsh
zstyle :omz:plugins:ssh-agent honor-existing yes
```
### `helper`
To set an **external helper** to ask for the passwords and possibly store
+17 -5
View File
@@ -1,16 +1,28 @@
# Get the filename to store/lookup the environment from
ssh_env_cache="$HOME/.ssh/environment-$SHORT_HOST"
# Test if $SSH_AUTH_SOCK points at a listening agent
function _is_agent_running() {
local REPLY
[[ -n "$SSH_AUTH_SOCK" && -S "$SSH_AUTH_SOCK" ]] || return 1
zmodload zsh/net/socket 2>/dev/null || return 1
zsocket "$SSH_AUTH_SOCK" 2>/dev/null || return 1
exec {REPLY}>&- # close the probe connection
return 0
}
function _start_agent() {
# Reuse an agent the session already provides, if enabled
if zstyle -t :omz:plugins:ssh-agent honor-existing && _is_agent_running; then
return 0
fi
# Check if ssh-agent is already running
if [[ -f "$ssh_env_cache" ]]; then
. "$ssh_env_cache" > /dev/null
# Test if $SSH_AUTH_SOCK is visible
zmodload zsh/net/socket
if [[ -S "$SSH_AUTH_SOCK" ]] && zsocket "$SSH_AUTH_SOCK" 2>/dev/null; then
return 0
fi
_is_agent_running && return 0
fi
if [[ ! -d "$HOME/.ssh" ]]; then
@@ -124,4 +136,4 @@ if ! zstyle -t :omz:plugins:ssh-agent lazy; then
fi
unset agent_forwarding ssh_env_cache
unfunction _start_agent _add_identities
unfunction _start_agent _add_identities _is_agent_running
-1
View File
@@ -71,7 +71,6 @@ Related: [#9798](https://github.com/ohmyzsh/ohmyzsh/pull/9798).
| Alias | Commands | Description |
| ----- | ------------------- | ---------------------------------------- |
| zar | `sudo zypper ar` | add a repository |
| zcl | `sudo zypper clean` | clean cache |
| zlr | `zypper lr` | list repositories |
| zmr | `sudo zypper mr` | modify repositories |
| znr | `sudo zypper nr` | rename repositories (for the alias only) |
-1
View File
@@ -38,7 +38,6 @@ alias zwp='zypper --no-refresh wp'
#Repositories commands
alias zar='sudo zypper ar'
alias zcl='sudo zypper clean'
alias zlr='zypper lr'
alias zmr='sudo zypper mr'
alias znr='sudo zypper nr'
+1
View File
@@ -45,6 +45,7 @@ plugins=(... systemd)
| `sc-set-environment` | `sudo systemctl set-environment` | Set one or more systemd manager environment variables |
| `sc-unset-environment` | `sudo systemctl unset-environment` | Unset one or more systemd manager environment variables |
| `sc-edit` | `sudo systemctl edit` | Edit a drop-in snippet or a whole replacement file with `--full` |
| `sc-edit-full` | `sudo systemctl edit --full` | Edit the whole unit file instead of a drop-in snippet |
| `sc-enable-now` | `sudo systemctl enable --now` | Enable and start unit(s) |
| `sc-disable-now` | `sudo systemctl disable --now` | Disable and stop unit(s) |
| `sc-mask-now` | `sudo systemctl mask --now` | Mask and stop unit(s) |
+4
View File
@@ -97,6 +97,10 @@ alias scu-mask-now="scu-mask --now"
alias scu-failed='systemctl --user --failed'
alias sc-failed='systemctl --failed'
# --full commands
alias sc-edit-full="sc-edit --full"
alias scu-edit-full="scu-edit --full"
function systemd_prompt_info {
local unit
for unit in "$@"; do
+5 -2
View File
@@ -314,8 +314,11 @@ prompt_dir() {
# Virtualenv: current working virtualenv
prompt_virtualenv() {
if [ -n "$CONDA_DEFAULT_ENV" ]; then
prompt_segment magenta $CURRENT_FG "🐍 $CONDA_DEFAULT_ENV"
# Skip the conda segment when conda is already showing the environment itself,
# i.e. when changeps1 is left on. This mirrors how VIRTUAL_ENV_DISABLE_PROMPT
# is handled below, and stops the name appearing twice in the prompt.
if [[ -n "$CONDA_DEFAULT_ENV" && -z "$CONDA_PROMPT_MODIFIER" ]]; then
prompt_segment magenta $CURRENT_FG "🐍 ${CONDA_DEFAULT_ENV:t:gs/%/%%}"
fi
if [[ -n "$VIRTUAL_ENV" && -n "$VIRTUAL_ENV_DISABLE_PROMPT" ]]; then
prompt_segment "$AGNOSTER_VENV_BG" "$AGNOSTER_VENV_FG" "(${VIRTUAL_ENV:t:gs/%/%%})"
+1 -1
View File
@@ -14,7 +14,7 @@ function theme_precmd {
if (( promptsize + rubypromptsize + pwdsize + venvpromptsize + condapromptsize > TERMWIDTH )); then
(( PR_PWDLEN = TERMWIDTH - promptsize ))
elif [[ "${langinfo[CODESET]}" = UTF-8 ]]; then
PR_FILLBAR="\${(l:$(( TERMWIDTH - (promptsize + rubypromptsize + pwdsize + venvpromptsize + condapromptsize ) ))::${PR_HBAR}:)}"
PR_FILLBAR="\${(l:$(( TERMWIDTH - (promptsize + rubypromptsize + pwdsize + venvpromptsize + condapromptsize ) )):::)}"
else
PR_FILLBAR="${PR_SHIFT_IN}\${(l:$(( TERMWIDTH - (promptsize + rubypromptsize + pwdsize + venvpromptsize + condapromptsize ) ))::${altchar[q]:--}:)}${PR_SHIFT_OUT}"
fi
-55
View File
@@ -1,55 +0,0 @@
# 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
@@ -1,170 +0,0 @@
# 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
@@ -1,88 +0,0 @@
# 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
@@ -1,217 +0,0 @@
# 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
@@ -1,406 +0,0 @@
#!/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
@@ -1,282 +0,0 @@
#!/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'