mirror of
https://github.com/robbyrussell/oh-my-zsh.git
synced 2026-09-23 19:46:05 +02:00
Compare commits
3
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76439de772 | ||
|
|
3bf157dc77 | ||
|
|
2e0c643117 |
@@ -171,6 +171,19 @@ adds any) and extra goodies that are included in that particular plugin.
|
|||||||
|
|
||||||
### Themes
|
### 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
|
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
|
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!).
|
[screenshots](https://github.com/ohmyzsh/ohmyzsh/wiki/Themes) on the wiki (We are working on updating this!).
|
||||||
|
|||||||
+46
-1
@@ -51,7 +51,7 @@ function _omz {
|
|||||||
_describe 'command' subcmds ;;
|
_describe 'command' subcmds ;;
|
||||||
pr) subcmds=('clean:Delete all Pull Request branches' 'test:Test a Pull Request')
|
pr) subcmds=('clean:Delete all Pull Request branches' 'test:Test a Pull Request')
|
||||||
_describe 'command' subcmds ;;
|
_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 ;;
|
_describe 'command' subcmds ;;
|
||||||
esac
|
esac
|
||||||
elif (( CURRENT == 4 )); then
|
elif (( CURRENT == 4 )); then
|
||||||
@@ -77,6 +77,10 @@ function _omz {
|
|||||||
local -a opts
|
local -a opts
|
||||||
opts=('--enabled:List enabled plugins only')
|
opts=('--enabled:List enabled plugins only')
|
||||||
_describe -o 'options' opts ;;
|
_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))
|
theme::(set|use))
|
||||||
local -aU themes
|
local -aU themes
|
||||||
themes=("$ZSH"/themes/*.zsh-theme(-.N:t:r) "$ZSH_CUSTOM"/**/*.zsh-theme(-.N:r:gs:"$ZSH_CUSTOM"/themes/:::gs:"$ZSH_CUSTOM"/:::))
|
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:
|
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
|
list List all available Oh My Zsh themes
|
||||||
set <theme> Set a theme in your .zshrc file
|
set <theme> Set a theme in your .zshrc file
|
||||||
use <theme> Load a theme
|
use <theme> Load a theme
|
||||||
@@ -763,6 +769,45 @@ EOF
|
|||||||
$0::$command "$@"
|
$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 {
|
function _omz::theme::list {
|
||||||
local -a custom_themes builtin_themes
|
local -a custom_themes builtin_themes
|
||||||
custom_themes=("$ZSH_CUSTOM"/**/*.zsh-theme(-.N:r:gs:"$ZSH_CUSTOM"/themes/:::gs:"$ZSH_CUSTOM"/:::))
|
custom_themes=("$ZSH_CUSTOM"/**/*.zsh-theme(-.N:r:gs:"$ZSH_CUSTOM"/themes/:::gs:"$ZSH_CUSTOM"/:::))
|
||||||
|
|||||||
+4
-10
@@ -14,16 +14,10 @@ FX=(
|
|||||||
reverse "%{[07m%}" no-reverse "%{[27m%}"
|
reverse "%{[07m%}" no-reverse "%{[27m%}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Build the tables with array operations instead of a 256-iteration loop
|
for color in {000..255}; do
|
||||||
() {
|
FG[$color]="%{[38;5;${color}m%}"
|
||||||
setopt localoptions noksharrays
|
BG[$color]="%{[48;5;${color}m%}"
|
||||||
local -a codes fg bg
|
done
|
||||||
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
|
# Show all 256 colors with color number
|
||||||
function spectrum_ls() {
|
function spectrum_ls() {
|
||||||
|
|||||||
+1
-6
@@ -31,12 +31,7 @@ function title {
|
|||||||
else
|
else
|
||||||
# Try to use terminfo to set the title if the feature is available
|
# Try to use terminfo to set the title if the feature is available
|
||||||
if (( ${+terminfo[fsl]} && ${+terminfo[tsl]} )); then
|
if (( ${+terminfo[fsl]} && ${+terminfo[tsl]} )); then
|
||||||
# Emit the capabilities with echoti so terminfo does its own parameter
|
print -Pn "${terminfo[tsl]}$1${terminfo[fsl]}"
|
||||||
# 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
|
||||||
fi
|
fi
|
||||||
;;
|
;;
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ function aws_regions() {
|
|||||||
function aws_profiles() {
|
function aws_profiles() {
|
||||||
aws --no-cli-pager configure list-profiles 2> /dev/null && return
|
aws --no-cli-pager configure list-profiles 2> /dev/null && return
|
||||||
[[ -r "${AWS_CONFIG_FILE:-$HOME/.aws/config}" ]] || return 1
|
[[ -r "${AWS_CONFIG_FILE:-$HOME/.aws/config}" ]] || return 1
|
||||||
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/'
|
grep --color=never -Eo '\[.*\]' "${AWS_CONFIG_FILE:-$HOME/.aws/config}" | sed -E 's/^[[:space:]]*\[(profile)?[[:space:]]*([^[:space:]]+)\][[:space:]]*$/\2/g'
|
||||||
}
|
}
|
||||||
|
|
||||||
function _aws_regions() {
|
function _aws_regions() {
|
||||||
|
|||||||
@@ -99,21 +99,18 @@ $ find . -type f 2>/dev/null
|
|||||||
## File extension aliases
|
## File extension aliases
|
||||||
|
|
||||||
These are special aliases that are triggered when a file name is passed as the command. For example,
|
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 `open_command`, running `file.pdf` opens that file with
|
if the pdf file extension is aliased to `acroread` (a popular Linux pdf reader), when running `file.pdf`
|
||||||
whatever application your system uses for PDFs.
|
that file will be open with `acroread`.
|
||||||
|
|
||||||
### Reading Docs
|
### Reading Docs
|
||||||
|
|
||||||
These use `open_command`, which picks the right opener for your platform: `open` on macOS,
|
| Alias | Command | Description |
|
||||||
`xdg-open` on Linux, `cygstart` on Cygwin.
|
| ----- | ---------- | ---------------------------------- |
|
||||||
|
| pdf | `acroread` | Opens up a document using acroread |
|
||||||
| Alias | Command | Description |
|
| ps | `gv` | Opens up a .ps file using gv |
|
||||||
| ----- | -------------- | ----------------------------------------------- |
|
| dvi | `xdvi` | Opens up a .dvi file using xdvi |
|
||||||
| pdf | `open_command` | Opens up a .pdf file in your default viewer |
|
| chm | `xchm` | Opens up a .chm file using xchm |
|
||||||
| ps | `open_command` | Opens up a .ps file in your default viewer |
|
| djvu | `djview` | Opens up a .djvu file using djview |
|
||||||
| 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
|
### Listing files inside a packed file
|
||||||
|
|
||||||
|
|||||||
@@ -71,9 +71,12 @@ if is-at-least 4.2.0; then
|
|||||||
_media_fts=(ape avi flv m4a mkv mov mp3 mpeg mpg ogg ogm rm wav webm)
|
_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
|
for ft in $_media_fts; do alias -s $ft=mplayer; done
|
||||||
|
|
||||||
# read documents with the platform's default application
|
#read documents
|
||||||
_doc_fts=(pdf ps dvi chm djvu)
|
alias -s pdf=acroread
|
||||||
for ft in $_doc_fts; do alias -s $ft=open_command; done
|
alias -s ps=gv
|
||||||
|
alias -s dvi=xdvi
|
||||||
|
alias -s chm=xchm
|
||||||
|
alias -s djvu=djview
|
||||||
|
|
||||||
#list what's inside packed file
|
#list what's inside packed file
|
||||||
alias -s zip="unzip -l"
|
alias -s zip="unzip -l"
|
||||||
|
|||||||
@@ -1,33 +1,14 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
|
|
||||||
emacsfun() {
|
emacsfun() {
|
||||||
local cmd frames arg tty
|
local cmd frames
|
||||||
|
|
||||||
# Build the Emacs Lisp command to check for suitable frames
|
# 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
|
# See https://www.gnu.org/software/emacs/manual/html_node/elisp/Frames.html#index-framep
|
||||||
for arg in "$@"; do
|
case "$*" in
|
||||||
case "$arg" in
|
*-t*|*--tty*|*-nw*) cmd="(memq 't (mapcar 'framep (frame-list)))" ;; # if != nil, there are tty frames
|
||||||
-t|--tty|-nw) tty=1; break ;;
|
*) cmd="(delete 't (mapcar 'framep (frame-list)))" ;; # if != nil, there are graphical terminals (x, w32, ns)
|
||||||
esac
|
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
|
# Check if there are suitable frames
|
||||||
frames="$(emacsclient -a '' -n -e "$cmd" 2>/dev/null |sed 's/.*\x07//g' )"
|
frames="$(emacsclient -a '' -n -e "$cmd" 2>/dev/null |sed 's/.*\x07//g' )"
|
||||||
|
|||||||
@@ -15,6 +15,9 @@ This plugin does not add any aliases.
|
|||||||
This plugin caches the completion script and is automatically updated when the
|
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.
|
plugin is loaded, which is usually when you start up a new terminal emulator.
|
||||||
|
|
||||||
The completion script is stored at `$ZSH_CACHE_DIR/completions/_gh`. If an
|
The cache is stored at:
|
||||||
update fails, the plugin keeps the last successfully generated completion
|
|
||||||
script.
|
- `$ZSH/plugins/gh/_gh` completions script
|
||||||
|
|
||||||
|
- `$ZSH_CACHE_DIR/gh_version` version of GitHub CLI, used to invalidate
|
||||||
|
the cache.
|
||||||
|
|||||||
@@ -13,12 +13,6 @@ fi
|
|||||||
|
|
||||||
zmodload -F zsh/files b:zf_mv
|
zmodload -F zsh/files b:zf_mv
|
||||||
() {
|
() {
|
||||||
local completion_file="$ZSH_CACHE_DIR/completions/_gh"
|
local TMPPREFIX="$ZSH_CACHE_DIR/completions/_gh"
|
||||||
local completion_tmp="${completion_file}.$$.${RANDOM}"
|
zf_mv -f -- =( gh completion --shell zsh ) "$TMPPREFIX"
|
||||||
|
|
||||||
if gh completion --shell zsh >| "$completion_tmp" && [[ -s "$completion_tmp" ]]; then
|
|
||||||
zf_mv -f -- "$completion_tmp" "$completion_file"
|
|
||||||
fi
|
|
||||||
|
|
||||||
command rm -f -- "$completion_tmp"
|
|
||||||
} &|
|
} &|
|
||||||
|
|||||||
+36
-34
@@ -1,43 +1,45 @@
|
|||||||
# github plugin
|
# github plugin
|
||||||
|
|
||||||
> [!WARNING]
|
This plugin supports working with GitHub from the command line. It provides a few things:
|
||||||
> 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.
|
|
||||||
|
|
||||||
The plugin provides compatibility for existing `hub` users by:
|
* 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.
|
||||||
|
|
||||||
- aliasing `git` to `hub` when `hub` is installed;
|
### Functions
|
||||||
- adding completion for `hub`; and
|
|
||||||
- defining convenience functions for creating repositories.
|
|
||||||
|
|
||||||
## Migrating to GitHub CLI
|
* `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
|
||||||
|
|
||||||
Install [GitHub CLI](https://cli.github.com/), replace `github` with `gh` in
|
|
||||||
your plugin list, and restart your shell:
|
## 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`.
|
||||||
|
|
||||||
```zsh
|
```zsh
|
||||||
plugins=(... gh)
|
if (( ! ${fpath[(I)/usr/local/share/zsh/site-functions]} )); then
|
||||||
|
FPATH=/usr/local/share/zsh/site-functions:$FPATH
|
||||||
|
fi
|
||||||
```
|
```
|
||||||
|
|
||||||
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.
|
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
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
|
# Set up hub wrapper for git, if it is available; https://github.com/github/hub
|
||||||
if (( $+commands[hub] )); then
|
if (( $+commands[hub] )); then
|
||||||
alias git=hub
|
alias git=hub
|
||||||
@@ -29,7 +26,7 @@ empty_gh() { # [NAME_OF_REPO]
|
|||||||
# This function will add all non-hidden files to git.
|
# This function will add all non-hidden files to git.
|
||||||
new_gh() { # [DIRECTORY]
|
new_gh() { # [DIRECTORY]
|
||||||
emulate -L zsh
|
emulate -L zsh
|
||||||
local repo="${1:-.}"
|
local repo="$1"
|
||||||
cd "$repo" \
|
cd "$repo" \
|
||||||
|| return
|
|| return
|
||||||
|
|
||||||
@@ -45,7 +42,7 @@ new_gh() { # [DIRECTORY]
|
|||||||
|| return
|
|| return
|
||||||
hub create \
|
hub create \
|
||||||
|| return
|
|| return
|
||||||
git push -u origin HEAD \
|
git push -u origin master \
|
||||||
|| return
|
|| return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,19 +52,18 @@ new_gh() { # [DIRECTORY]
|
|||||||
# to your GitHub.
|
# to your GitHub.
|
||||||
exist_gh() { # [DIRECTORY]
|
exist_gh() { # [DIRECTORY]
|
||||||
emulate -L zsh
|
emulate -L zsh
|
||||||
local repo="${1:-.}"
|
local repo=$1
|
||||||
cd "$repo" \
|
cd "$repo"
|
||||||
|| return
|
|
||||||
|
|
||||||
hub create \
|
hub create \
|
||||||
|| return
|
|| return
|
||||||
git push -u origin HEAD
|
git push -u origin master
|
||||||
}
|
}
|
||||||
|
|
||||||
# git.io "GitHub URL"
|
# git.io "GitHub URL"
|
||||||
#
|
#
|
||||||
# Shorten GitHub url, example:
|
# 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
|
# source: https://github.com/nvogel/dotzsh
|
||||||
# documentation: https://github.com/blog/985-git-io-github-url-shortener
|
# documentation: https://github.com/blog/985-git-io-github-url-shortener
|
||||||
#
|
#
|
||||||
@@ -78,3 +74,4 @@ git.io() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# End Functions #############################################################
|
# End Functions #############################################################
|
||||||
|
|
||||||
|
|||||||
@@ -1,163 +0,0 @@
|
|||||||
# 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`.
|
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
# 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"
|
|
||||||
} &|
|
|
||||||
@@ -55,7 +55,7 @@ plugins=(... jj)
|
|||||||
| jjopl | `jj op log` |
|
| jjopl | `jj op log` |
|
||||||
| jjor | `jj op restore` |
|
| jjor | `jj op restore` |
|
||||||
| jjrb | `jj rebase` |
|
| jjrb | `jj rebase` |
|
||||||
| jjrbm | `jj rebase -o "trunk()"` |
|
| jjrbm | `jj rebase -d "trunk()"` |
|
||||||
| jjrs | `jj restore` |
|
| jjrs | `jj restore` |
|
||||||
| jjrt | `cd "$(jj root \|\| echo .)"` |
|
| jjrt | `cd "$(jj root \|\| echo .)"` |
|
||||||
| jjs | `jj show` |
|
| jjs | `jj show` |
|
||||||
@@ -120,10 +120,10 @@ that.
|
|||||||
|
|
||||||
### Git async-prompt compatibility
|
### 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
|
If you use a wrapper function that calls `git_prompt_info` (as shown above), it won't work with
|
||||||
git async-prompt mode. This is because async-prompt only registers its background worker when it detects
|
the default git async-prompt mode. This is because async-prompt only registers its background worker
|
||||||
`$(git_prompt_info)` literally in your prompt variables. A wrapper like `$(_my_theme_vcs_info)` won't match,
|
when it detects `$(git_prompt_info)` literally in your prompt variables. A wrapper like
|
||||||
so the async output stays empty.
|
`$(_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:
|
To fix this, add one of the following to your `.zshrc` **before** Oh My Zsh is sourced:
|
||||||
|
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ alias jjop='jj op'
|
|||||||
alias jjopl='jj op log'
|
alias jjopl='jj op log'
|
||||||
alias jjor='jj op restore'
|
alias jjor='jj op restore'
|
||||||
alias jjrb='jj rebase'
|
alias jjrb='jj rebase'
|
||||||
alias jjrbm='jj rebase -o "trunk()"'
|
alias jjrbm='jj rebase -d "trunk()"'
|
||||||
alias jjrs='jj restore'
|
alias jjrs='jj restore'
|
||||||
alias jjrt='cd "$(jj root || echo .)"'
|
alias jjrt='cd "$(jj root || echo .)"'
|
||||||
alias jjs='jj show'
|
alias jjs='jj show'
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ plugins=(... kubectl)
|
|||||||
| kcsc | `kubectl config set-context` | Set a context entry in kubeconfig |
|
| kcsc | `kubectl config set-context` | Set a context entry in kubeconfig |
|
||||||
| kcdc | `kubectl config delete-context` | Delete the specified context from the kubeconfig |
|
| kcdc | `kubectl config delete-context` | Delete the specified context from the kubeconfig |
|
||||||
| kccc | `kubectl config current-context` | Display the current-context |
|
| 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 |
|
| kcgc | `kubectl config get-contexts` | List of contexts available |
|
||||||
| | | **General aliases** |
|
| | | **General aliases** |
|
||||||
| kdel | `kubectl delete` | Delete resources by filenames, stdin, resources and names, or by resources and label selector |
|
| kdel | `kubectl delete` | Delete resources by filenames, stdin, resources and names, or by resources and label selector |
|
||||||
@@ -131,7 +130,7 @@ plugins=(... kubectl)
|
|||||||
| kdss | `kubectl describe statefulset` | Describe statefulset resource in detail |
|
| kdss | `kubectl describe statefulset` | Describe statefulset resource in detail |
|
||||||
| kdelss | `kubectl delete statefulset` | Delete the statefulset |
|
| kdelss | `kubectl delete statefulset` | Delete the statefulset |
|
||||||
| ksss | `kubectl scale statefulset` | Scale a statefulset |
|
| ksss | `kubectl scale statefulset` | Scale a statefulset |
|
||||||
| krsss | `kubectl rollout status statefulset` | Check the rollout status of a statefulset |
|
| krsss | `kubectl rollout status statefulset` | Check the rollout status of a deployment |
|
||||||
| krrss | `kubectl rollout restart statefulset` | Rollout restart a statefulset |
|
| krrss | `kubectl rollout restart statefulset` | Rollout restart a statefulset |
|
||||||
| | | **Service Accounts management** |
|
| | | **Service Accounts management** |
|
||||||
| kdsa | `kubectl describe sa` | Describe a service account in details |
|
| kdsa | `kubectl describe sa` | Describe a service account in details |
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ alias kcuc='kubectl config use-context'
|
|||||||
alias kcsc='kubectl config set-context'
|
alias kcsc='kubectl config set-context'
|
||||||
alias kcdc='kubectl config delete-context'
|
alias kcdc='kubectl config delete-context'
|
||||||
alias kccc='kubectl config current-context'
|
alias kccc='kubectl config current-context'
|
||||||
alias kcrc='kubectl config rename-context'
|
|
||||||
|
|
||||||
# List all contexts
|
# List all contexts
|
||||||
alias kcgc='kubectl config get-contexts'
|
alias kcgc='kubectl config get-contexts'
|
||||||
|
|||||||
@@ -139,6 +139,7 @@ function rdsl() { _rails_db_command db:schema:load "$@" }
|
|||||||
|
|
||||||
# legacy stuff
|
# legacy stuff
|
||||||
alias sc='ruby script/console'
|
alias sc='ruby script/console'
|
||||||
|
alias sd='ruby script/destroy'
|
||||||
alias sd='ruby script/server --debugger'
|
alias sd='ruby script/server --debugger'
|
||||||
alias sg='ruby script/generate'
|
alias sg='ruby script/generate'
|
||||||
alias sp='ruby script/plugin'
|
alias sp='ruby script/plugin'
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
# Load bracketed-paste-magic if zsh version is >= 5.1
|
# Load bracketed-paste-magic if zsh version is >= 5.1
|
||||||
autoload -Uz is-at-least
|
autoload -Uz is-at-least
|
||||||
if is-at-least 5.1; then
|
if is-at-least 5.1; then
|
||||||
|
set zle_bracketed_paste # Explicitly restore this zsh default
|
||||||
autoload -Uz bracketed-paste-magic
|
autoload -Uz bracketed-paste-magic
|
||||||
zle -N bracketed-paste bracketed-paste-magic
|
zle -N bracketed-paste bracketed-paste-magic
|
||||||
return ### The rest of this file is NOT executed on zsh version >= 5.1 ###
|
return ### The rest of this file is NOT executed on zsh version >= 5.1 ###
|
||||||
|
|||||||
@@ -21,18 +21,6 @@ To enable **agent forwarding support** add the following to your zshrc file:
|
|||||||
zstyle :omz:plugins:ssh-agent agent-forwarding yes
|
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`
|
### `helper`
|
||||||
|
|
||||||
To set an **external helper** to ask for the passwords and possibly store
|
To set an **external helper** to ask for the passwords and possibly store
|
||||||
|
|||||||
@@ -1,28 +1,16 @@
|
|||||||
# Get the filename to store/lookup the environment from
|
# Get the filename to store/lookup the environment from
|
||||||
ssh_env_cache="$HOME/.ssh/environment-$SHORT_HOST"
|
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() {
|
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
|
# Check if ssh-agent is already running
|
||||||
if [[ -f "$ssh_env_cache" ]]; then
|
if [[ -f "$ssh_env_cache" ]]; then
|
||||||
. "$ssh_env_cache" > /dev/null
|
. "$ssh_env_cache" > /dev/null
|
||||||
|
|
||||||
# Test if $SSH_AUTH_SOCK is visible
|
# Test if $SSH_AUTH_SOCK is visible
|
||||||
_is_agent_running && return 0
|
zmodload zsh/net/socket
|
||||||
|
if [[ -S "$SSH_AUTH_SOCK" ]] && zsocket "$SSH_AUTH_SOCK" 2>/dev/null; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [[ ! -d "$HOME/.ssh" ]]; then
|
if [[ ! -d "$HOME/.ssh" ]]; then
|
||||||
@@ -136,4 +124,4 @@ if ! zstyle -t :omz:plugins:ssh-agent lazy; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
unset agent_forwarding ssh_env_cache
|
unset agent_forwarding ssh_env_cache
|
||||||
unfunction _start_agent _add_identities _is_agent_running
|
unfunction _start_agent _add_identities
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ Related: [#9798](https://github.com/ohmyzsh/ohmyzsh/pull/9798).
|
|||||||
| Alias | Commands | Description |
|
| Alias | Commands | Description |
|
||||||
| ----- | ------------------- | ---------------------------------------- |
|
| ----- | ------------------- | ---------------------------------------- |
|
||||||
| zar | `sudo zypper ar` | add a repository |
|
| zar | `sudo zypper ar` | add a repository |
|
||||||
|
| zcl | `sudo zypper clean` | clean cache |
|
||||||
| zlr | `zypper lr` | list repositories |
|
| zlr | `zypper lr` | list repositories |
|
||||||
| zmr | `sudo zypper mr` | modify repositories |
|
| zmr | `sudo zypper mr` | modify repositories |
|
||||||
| znr | `sudo zypper nr` | rename repositories (for the alias only) |
|
| znr | `sudo zypper nr` | rename repositories (for the alias only) |
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ alias zwp='zypper --no-refresh wp'
|
|||||||
|
|
||||||
#Repositories commands
|
#Repositories commands
|
||||||
alias zar='sudo zypper ar'
|
alias zar='sudo zypper ar'
|
||||||
|
alias zcl='sudo zypper clean'
|
||||||
alias zlr='zypper lr'
|
alias zlr='zypper lr'
|
||||||
alias zmr='sudo zypper mr'
|
alias zmr='sudo zypper mr'
|
||||||
alias znr='sudo zypper nr'
|
alias znr='sudo zypper nr'
|
||||||
|
|||||||
@@ -45,7 +45,6 @@ plugins=(... systemd)
|
|||||||
| `sc-set-environment` | `sudo systemctl set-environment` | Set one or more systemd manager environment variables |
|
| `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-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` | `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-enable-now` | `sudo systemctl enable --now` | Enable and start unit(s) |
|
||||||
| `sc-disable-now` | `sudo systemctl disable --now` | Disable and stop 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) |
|
| `sc-mask-now` | `sudo systemctl mask --now` | Mask and stop unit(s) |
|
||||||
|
|||||||
@@ -97,10 +97,6 @@ alias scu-mask-now="scu-mask --now"
|
|||||||
alias scu-failed='systemctl --user --failed'
|
alias scu-failed='systemctl --user --failed'
|
||||||
alias sc-failed='systemctl --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 {
|
function systemd_prompt_info {
|
||||||
local unit
|
local unit
|
||||||
for unit in "$@"; do
|
for unit in "$@"; do
|
||||||
|
|||||||
@@ -314,11 +314,8 @@ prompt_dir() {
|
|||||||
|
|
||||||
# Virtualenv: current working virtualenv
|
# Virtualenv: current working virtualenv
|
||||||
prompt_virtualenv() {
|
prompt_virtualenv() {
|
||||||
# Skip the conda segment when conda is already showing the environment itself,
|
if [ -n "$CONDA_DEFAULT_ENV" ]; then
|
||||||
# i.e. when changeps1 is left on. This mirrors how VIRTUAL_ENV_DISABLE_PROMPT
|
prompt_segment magenta $CURRENT_FG "🐍 $CONDA_DEFAULT_ENV"
|
||||||
# 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
|
fi
|
||||||
if [[ -n "$VIRTUAL_ENV" && -n "$VIRTUAL_ENV_DISABLE_PROMPT" ]]; then
|
if [[ -n "$VIRTUAL_ENV" && -n "$VIRTUAL_ENV_DISABLE_PROMPT" ]]; then
|
||||||
prompt_segment "$AGNOSTER_VENV_BG" "$AGNOSTER_VENV_FG" "(${VIRTUAL_ENV:t:gs/%/%%})"
|
prompt_segment "$AGNOSTER_VENV_BG" "$AGNOSTER_VENV_FG" "(${VIRTUAL_ENV:t:gs/%/%%})"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ function theme_precmd {
|
|||||||
if (( promptsize + rubypromptsize + pwdsize + venvpromptsize + condapromptsize > TERMWIDTH )); then
|
if (( promptsize + rubypromptsize + pwdsize + venvpromptsize + condapromptsize > TERMWIDTH )); then
|
||||||
(( PR_PWDLEN = TERMWIDTH - promptsize ))
|
(( PR_PWDLEN = TERMWIDTH - promptsize ))
|
||||||
elif [[ "${langinfo[CODESET]}" = UTF-8 ]]; then
|
elif [[ "${langinfo[CODESET]}" = UTF-8 ]]; then
|
||||||
PR_FILLBAR="\${(l:$(( TERMWIDTH - (promptsize + rubypromptsize + pwdsize + venvpromptsize + condapromptsize ) ))::─:)}"
|
PR_FILLBAR="\${(l:$(( TERMWIDTH - (promptsize + rubypromptsize + pwdsize + venvpromptsize + condapromptsize ) ))::${PR_HBAR}:)}"
|
||||||
else
|
else
|
||||||
PR_FILLBAR="${PR_SHIFT_IN}\${(l:$(( TERMWIDTH - (promptsize + rubypromptsize + pwdsize + venvpromptsize + condapromptsize ) ))::${altchar[q]:--}:)}${PR_SHIFT_OUT}"
|
PR_FILLBAR="${PR_SHIFT_IN}\${(l:$(( TERMWIDTH - (promptsize + rubypromptsize + pwdsize + venvpromptsize + condapromptsize ) ))::${altchar[q]:--}:)}${PR_SHIFT_OUT}"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
)
|
||||||
@@ -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
|
||||||
@@ -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 ]]
|
||||||
|
)
|
||||||
@@ -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 ))
|
||||||
@@ -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'
|
||||||
Reference in New Issue
Block a user