mirror of
https://github.com/robbyrussell/oh-my-zsh.git
synced 2026-09-21 18:46:06 +02:00
Compare commits
22
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
76439de772 | ||
|
|
3bf157dc77 | ||
|
|
2e0c643117 | ||
|
|
bd77419b2a | ||
|
|
d217d05374 | ||
|
|
c891b750b0 | ||
|
|
6aa16696d5 | ||
|
|
8d10cc9488 | ||
|
|
bf77e350e0 | ||
|
|
07351f99fc | ||
|
|
39334a08e9 | ||
|
|
cba369e090 | ||
|
|
0da7919339 | ||
|
|
992edcc17d | ||
|
|
78cbb3fe5e | ||
|
|
76ac9fcddc | ||
|
|
17bbf5fdac | ||
|
|
704f7d59d8 | ||
|
|
0b235c54de | ||
|
|
426650fbe8 | ||
|
|
8a5b393088 | ||
|
|
87d4d40418 |
@@ -12,7 +12,7 @@ dependencies:
|
||||
plugins/gradle:
|
||||
repo: gradle/gradle-completion
|
||||
branch: master
|
||||
version: 63578c9bc56134b80766e88957ba0239e6be2343
|
||||
version: e7c881dbef7462bc35ad05328397e1582fe25cb8
|
||||
precopy: |
|
||||
set -e
|
||||
find . ! -name _gradle ! -name LICENSE -delete
|
||||
|
||||
@@ -16,7 +16,7 @@ jobs:
|
||||
contents: write # this is needed to push commits and branches
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
||||
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
- macos-latest
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
||||
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
- test
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
||||
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ jobs:
|
||||
if: github.repository == 'ohmyzsh/ohmyzsh'
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
||||
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
if: github.repository == 'ohmyzsh/ohmyzsh'
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
||||
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
- name: Authenticate as @ohmyzsh
|
||||
|
||||
@@ -36,7 +36,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Harden the runner (Audit all outbound calls)
|
||||
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
|
||||
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
|
||||
with:
|
||||
egress-policy: audit
|
||||
|
||||
|
||||
@@ -23,6 +23,14 @@ Twitter), and join us on [Discord](https://discord.gg/ohmyzsh).
|
||||
[](https://mstdn.social/@ohmyzsh)
|
||||
[](https://discord.gg/ohmyzsh)
|
||||
|
||||
<a href="https://commitgoods.com/collections/oh-my-zsh?utm_source=github">
|
||||
<picture>
|
||||
<source media="(prefers-color-scheme: dark)" srcset="https://cdn.shopify.com/s/files/1/0101/8752/files/commitgoods-badge-stacked-navy-dark.svg">
|
||||
<img src="https://cdn.shopify.com/s/files/1/0101/8752/files/commitgoods-badge-stacked-navy.svg" height="28" alt="Official merch on commitgoods.com">
|
||||
</picture>
|
||||
</a>
|
||||
|
||||
|
||||
<details>
|
||||
<summary>Table of Contents</summary>
|
||||
|
||||
@@ -163,6 +171,19 @@ adds any) and extra goodies that are included in that particular plugin.
|
||||
|
||||
### Themes
|
||||
|
||||
Preview installed themes in your current directory without changing your prompt or `.zshrc`:
|
||||
|
||||
```zsh
|
||||
omz theme browse # Interactive browser
|
||||
omz theme browse agn # Start with a theme-name filter
|
||||
omz theme preview agnoster # Print one isolated static preview
|
||||
```
|
||||
|
||||
In the browser, type to filter, use Up/Down or Ctrl-P/Ctrl-N to navigate, and press Enter
|
||||
for explicit use/save actions. Esc or Ctrl-C cancels without applying a theme.
|
||||
See the [theme browser guide](tools/theme-browser/README.md) for shortcuts, preview limitations,
|
||||
and local testing instructions.
|
||||
|
||||
We'll admit it. Early in the Oh My Zsh world, we may have gotten a bit too theme-happy. We have over one
|
||||
hundred and fifty themes now bundled. Most of them have
|
||||
[screenshots](https://github.com/ohmyzsh/ohmyzsh/wiki/Themes) on the wiki (We are working on updating this!).
|
||||
|
||||
+46
-1
@@ -51,7 +51,7 @@ function _omz {
|
||||
_describe 'command' subcmds ;;
|
||||
pr) subcmds=('clean:Delete all Pull Request branches' 'test:Test a Pull Request')
|
||||
_describe 'command' subcmds ;;
|
||||
theme) subcmds=('list:List themes' 'set:Set a theme in your .zshrc file' 'use:Load a theme')
|
||||
theme) subcmds=('browse:Browse theme previews' 'preview:Preview a theme without applying it' 'list:List themes' 'set:Set a theme in your .zshrc file' 'use:Load a theme')
|
||||
_describe 'command' subcmds ;;
|
||||
esac
|
||||
elif (( CURRENT == 4 )); then
|
||||
@@ -77,6 +77,10 @@ function _omz {
|
||||
local -a opts
|
||||
opts=('--enabled:List enabled plugins only')
|
||||
_describe -o 'options' opts ;;
|
||||
theme::(browse|preview))
|
||||
local -a themes
|
||||
themes=("${(@f)$(source "$ZSH/tools/theme-browser/preview.zsh"; _omz_theme_names)}")
|
||||
_describe 'theme' themes ;;
|
||||
theme::(set|use))
|
||||
local -aU themes
|
||||
themes=("$ZSH"/themes/*.zsh-theme(-.N:t:r) "$ZSH_CUSTOM"/**/*.zsh-theme(-.N:r:gs:"$ZSH_CUSTOM"/themes/:::gs:"$ZSH_CUSTOM"/:::))
|
||||
@@ -749,6 +753,8 @@ Usage: ${(j: :)${(s.::.)0#_}} <command> [options]
|
||||
|
||||
Available commands:
|
||||
|
||||
browse [filter] Browse installed themes without applying them
|
||||
preview <theme> Preview a theme in the current directory
|
||||
list List all available Oh My Zsh themes
|
||||
set <theme> Set a theme in your .zshrc file
|
||||
use <theme> Load a theme
|
||||
@@ -763,6 +769,45 @@ EOF
|
||||
$0::$command "$@"
|
||||
}
|
||||
|
||||
function _omz::theme::preview {
|
||||
if (( $# != 1 )); then
|
||||
print -u2 -r -- 'Usage: omz theme preview <theme>'
|
||||
return 1
|
||||
fi
|
||||
source "$ZSH/tools/theme-browser/preview.zsh"
|
||||
_omz_theme_preview "$1"
|
||||
}
|
||||
|
||||
function _omz::theme::browse {
|
||||
if (( $# > 1 )); then
|
||||
print -u2 -r -- 'Usage: omz theme browse [filter]'
|
||||
return 1
|
||||
fi
|
||||
if [[ ! -o interactive || ! -t 0 || ! -t 1 ]]; then
|
||||
print -u2 -r -- 'omz theme browse requires an interactive terminal; use omz theme preview <theme> instead.'
|
||||
return 1
|
||||
fi
|
||||
source "$ZSH/tools/theme-browser/preview.zsh"
|
||||
source "$ZSH/tools/theme-browser/browser.zsh"
|
||||
local selection action name
|
||||
selection=$(_omz_theme_browser "$1") || return $?
|
||||
[[ -n $selection ]] || return 0
|
||||
action=${selection%%$'\n'*}
|
||||
name=${selection#*$'\n'}
|
||||
_omz_theme_resolve "$name" >/dev/null || return 1
|
||||
case $action in
|
||||
use) _omz::theme::use "$name" ;;
|
||||
set)
|
||||
# The existing set command interpolates the name into shell and awk text.
|
||||
[[ $name != *[^a-zA-Z0-9_./-]* ]] || {
|
||||
print -u2 -r -- 'Cannot save this theme name safely; set ZSH_THEME manually.'
|
||||
return 1
|
||||
}
|
||||
_omz::theme::set "$name" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
function _omz::theme::list {
|
||||
local -a custom_themes builtin_themes
|
||||
custom_themes=("$ZSH_CUSTOM"/**/*.zsh-theme(-.N:r:gs:"$ZSH_CUSTOM"/themes/:::gs:"$ZSH_CUSTOM"/:::))
|
||||
|
||||
@@ -21,6 +21,7 @@ function chruby_prompt_info \
|
||||
azure_prompt_info \
|
||||
tf_prompt_info \
|
||||
conda_prompt_info \
|
||||
go_prompt_info \
|
||||
{
|
||||
return 1
|
||||
}
|
||||
|
||||
+17
-7
@@ -64,7 +64,7 @@ if [[ ! -w "$ZSH_CACHE_DIR" ]]; then
|
||||
fi
|
||||
|
||||
# Create cache and completions dir and add to $fpath
|
||||
mkdir -p "$ZSH_CACHE_DIR/completions"
|
||||
[[ -d "$ZSH_CACHE_DIR/completions" ]] || mkdir -p "$ZSH_CACHE_DIR/completions"
|
||||
(( ${fpath[(Ie)$ZSH_CACHE_DIR/completions]} )) || fpath=("$ZSH_CACHE_DIR/completions" $fpath)
|
||||
|
||||
# Check for updates on initial load...
|
||||
@@ -114,9 +114,17 @@ fi
|
||||
zcompdump_revision="#omz revision: $(builtin cd -q "$ZSH"; git rev-parse HEAD 2>/dev/null)"
|
||||
zcompdump_fpath="#omz fpath: $fpath"
|
||||
|
||||
# Check whether the zcompdump file carries the current OMZ metadata lines
|
||||
_omz_compdump_has_metadata() {
|
||||
local -a lines
|
||||
[[ -r "$ZSH_COMPDUMP" ]] || return 1
|
||||
lines=("${(@f)$(<"$ZSH_COMPDUMP")}")
|
||||
(( ${lines[(Ie)$zcompdump_revision]} && ${lines[(Ie)$zcompdump_fpath]} ))
|
||||
}
|
||||
|
||||
# Delete the zcompdump file if OMZ zcompdump metadata changed
|
||||
if ! command grep -q -Fx "$zcompdump_revision" "$ZSH_COMPDUMP" 2>/dev/null \
|
||||
|| ! command grep -q -Fx "$zcompdump_fpath" "$ZSH_COMPDUMP" 2>/dev/null; then
|
||||
zcompdump_refresh=0
|
||||
if ! _omz_compdump_has_metadata; then
|
||||
command rm -f "$ZSH_COMPDUMP"
|
||||
zcompdump_refresh=1
|
||||
fi
|
||||
@@ -134,9 +142,8 @@ else
|
||||
compinit -u -d "$ZSH_COMPDUMP"
|
||||
fi
|
||||
|
||||
# Append zcompdump metadata if missing
|
||||
if (( $zcompdump_refresh )) \
|
||||
|| ! command grep -q -Fx "$zcompdump_revision" "$ZSH_COMPDUMP" 2>/dev/null; then
|
||||
# Append zcompdump metadata if missing (compinit may have regenerated the file)
|
||||
if (( zcompdump_refresh )) || ! _omz_compdump_has_metadata; then
|
||||
# Use `tee` in case the $ZSH_COMPDUMP filename is invalid, to silence the error
|
||||
# See https://github.com/ohmyzsh/ohmyzsh/commit/dd1a7269#commitcomment-39003489
|
||||
tee -a "$ZSH_COMPDUMP" &>/dev/null <<EOF
|
||||
@@ -146,9 +153,12 @@ $zcompdump_fpath
|
||||
EOF
|
||||
fi
|
||||
unset zcompdump_revision zcompdump_fpath zcompdump_refresh
|
||||
unset -f _omz_compdump_has_metadata
|
||||
|
||||
# zcompile the completion dump file if the .zwc is older or missing.
|
||||
if command mkdir "${ZSH_COMPDUMP}.lock" 2>/dev/null; then
|
||||
# Test that first so the lock directory and zrecompile are skipped when it's fresh.
|
||||
if [[ ! "${ZSH_COMPDUMP}.zwc" -nt "$ZSH_COMPDUMP" ]] \
|
||||
&& command mkdir "${ZSH_COMPDUMP}.lock" 2>/dev/null; then
|
||||
zrecompile -q -p "$ZSH_COMPDUMP"
|
||||
command rm -rf "$ZSH_COMPDUMP.zwc.old" "${ZSH_COMPDUMP}.lock"
|
||||
fi
|
||||
|
||||
@@ -39,3 +39,18 @@ plugins=(... golang)
|
||||
| gov | `go vet` | Vet examines Go source code and reports suspicious constructs |
|
||||
| gove | `go version` | Prints Go version |
|
||||
| gow | `go work` | Work provides access to operations on workspaces |
|
||||
|
||||
## Prompt
|
||||
|
||||
This plugin provides the `go_prompt_info` function to add the installed Go
|
||||
version to your prompt.
|
||||
|
||||
For example:
|
||||
```
|
||||
PROMPT="%~$ "
|
||||
RPROMPT='$(go_prompt_info)'
|
||||
```
|
||||
changes your prompt to:
|
||||
```
|
||||
~/go/project$ ▋ 1.22.3
|
||||
```
|
||||
|
||||
@@ -36,3 +36,12 @@ alias gotofx='go tool fix'
|
||||
alias gov='go vet'
|
||||
alias gove='go version'
|
||||
alias gow='go work'
|
||||
|
||||
## prompt
|
||||
function go_prompt_info() {
|
||||
(( $+commands[go] )) || return
|
||||
local go_prompt
|
||||
go_prompt=$(go version | awk '{ print substr($3, 3) }')
|
||||
[[ -z "${go_prompt}" ]] && return
|
||||
echo "${ZSH_THEME_GO_PROMPT_PREFIX}${go_prompt:gs/%/%%}${ZSH_THEME_GO_PROMPT_SUFFIX}"
|
||||
}
|
||||
|
||||
+10
-4
@@ -284,6 +284,9 @@ __gradle_subcommand() {
|
||||
'-Dorg.gradle.debug.suspend=[]' \
|
||||
'-Dorg.gradle.debug=[]:org.gradle.debug:(true false)' \
|
||||
'-Dorg.gradle.dependency.verification=[Configures the dependency verification mode. Supported values are 'strict', 'lenient', or 'off'.]:org.gradle.dependency.verification:(strict lenient off)' \
|
||||
'-Dorg.gradle.isolated-projects.dangerously-ignore-problems=[]' \
|
||||
'-Dorg.gradle.isolated-projects.diagnostics=[]' \
|
||||
'-Dorg.gradle.isolated-projects=[Enables Isolated Projects. Projects are configured in parallel. Implies `--configuration-cache`.]' \
|
||||
'-Dorg.gradle.java.home=[]:org.gradle.java.home:_directories' \
|
||||
'-Dorg.gradle.java.installations.auto-detect=[]' \
|
||||
'-Dorg.gradle.java.installations.auto-download=[]' \
|
||||
@@ -299,8 +302,6 @@ __gradle_subcommand() {
|
||||
'-Dorg.gradle.problems.report=[Enables the HTML problems report.]' \
|
||||
'-Dorg.gradle.projectcachedir=[Specifies the project-specific cache directory. Default is .gradle in the root project directory.]:org.gradle.projectcachedir:_directories' \
|
||||
'-Dorg.gradle.tooling.parallel=[]' \
|
||||
'-Dorg.gradle.unsafe.isolated-projects.diagnostics=[]' \
|
||||
'-Dorg.gradle.unsafe.isolated-projects=[]' \
|
||||
'-Dorg.gradle.vfs.verbose=[]' \
|
||||
'-Dorg.gradle.vfs.watch=[Enables file system watching. Reuses file system data for subsequent builds.]:org.gradle.vfs.watch:(true false)' \
|
||||
'-Dorg.gradle.warning.mode=[Specifies which mode of warnings to generate. Supported values are 'all', 'fail', 'summary' (default), or 'none'.]' \
|
||||
@@ -328,12 +329,14 @@ __gradle_subcommand() {
|
||||
\*'--include-build[Includes the specified build in the composite.]:include build:_directories' \
|
||||
(--quiet,-q,--warn,-w,--debug,-d){-i,--info}'[Sets the log level to info.]' \
|
||||
\*{-I,--init-script}'[Specifies an initialization script.]:init script:_files -g \*.gradle(|.kts)' \
|
||||
(--no-isolated-projects)'--isolated-projects[Enables Isolated Projects. Projects are configured in parallel. Implies `--configuration-cache`. (incubating)]' \
|
||||
'--max-workers[Configures the maximum number of concurrent workers Gradle is allowed to use.]' \
|
||||
(--build-cache)'--no-build-cache[Disables the Gradle build cache.]' \
|
||||
(--configuration-cache)'--no-configuration-cache[Disables the configuration cache.]' \
|
||||
(--configure-on-demand)'--no-configure-on-demand[Disables the use of configuration on demand. (incubating)]' \
|
||||
(--continue)'--no-continue[Stops task execution after a task failure.]' \
|
||||
(--daemon)'--no-daemon[Runs the build without the Gradle daemon. Useful occasionally if you have configured Gradle to always run with the daemon by default.]' \
|
||||
(--isolated-projects)'--no-isolated-projects[Disables Isolated Projects. (incubating)]' \
|
||||
(--parallel)'--no-parallel[Disables parallel project execution.]' \
|
||||
(--problems-report)'--no-problems-report[Disables the HTML problems report. (incubating)]' \
|
||||
{-a,--no-rebuild}'[Disables rebuilding of project dependencies.]' \
|
||||
@@ -421,6 +424,9 @@ _gradle() {
|
||||
'-Dorg.gradle.debug.suspend=[]:->argument-expected' \
|
||||
'-Dorg.gradle.debug=[]:org.gradle.debug:(true false):->argument-expected' \
|
||||
'-Dorg.gradle.dependency.verification=[Configures the dependency verification mode. Supported values are 'strict', 'lenient', or 'off'.]:org.gradle.dependency.verification:(strict lenient off):->argument-expected' \
|
||||
'-Dorg.gradle.isolated-projects.dangerously-ignore-problems=[]:->argument-expected' \
|
||||
'-Dorg.gradle.isolated-projects.diagnostics=[]:->argument-expected' \
|
||||
'-Dorg.gradle.isolated-projects=[Enables Isolated Projects. Projects are configured in parallel. Implies `--configuration-cache`.]:->argument-expected' \
|
||||
'-Dorg.gradle.java.home=[]:org.gradle.java.home:_directories:->argument-expected' \
|
||||
'-Dorg.gradle.java.installations.auto-detect=[]:->argument-expected' \
|
||||
'-Dorg.gradle.java.installations.auto-download=[]:->argument-expected' \
|
||||
@@ -436,8 +442,6 @@ _gradle() {
|
||||
'-Dorg.gradle.problems.report=[Enables the HTML problems report.]:->argument-expected' \
|
||||
'-Dorg.gradle.projectcachedir=[Specifies the project-specific cache directory. Default is .gradle in the root project directory.]:org.gradle.projectcachedir:_directories:->argument-expected' \
|
||||
'-Dorg.gradle.tooling.parallel=[]:->argument-expected' \
|
||||
'-Dorg.gradle.unsafe.isolated-projects.diagnostics=[]:->argument-expected' \
|
||||
'-Dorg.gradle.unsafe.isolated-projects=[]:->argument-expected' \
|
||||
'-Dorg.gradle.vfs.verbose=[]:->argument-expected' \
|
||||
'-Dorg.gradle.vfs.watch=[Enables file system watching. Reuses file system data for subsequent builds.]:org.gradle.vfs.watch:(true false):->argument-expected' \
|
||||
'-Dorg.gradle.warning.mode=[Specifies which mode of warnings to generate. Supported values are 'all', 'fail', 'summary' (default), or 'none'.]:->argument-expected' \
|
||||
@@ -466,12 +470,14 @@ _gradle() {
|
||||
\*'--include-build[Includes the specified build in the composite.]:include build:_directories:->argument-expected' \
|
||||
(--quiet,-q,--warn,-w,--debug,-d){-i,--info}'[Sets the log level to info.]' \
|
||||
\*{-I,--init-script}'[Specifies an initialization script.]:init script:_files -g \*.gradle(|.kts):->argument-expected' \
|
||||
(--no-isolated-projects)'--isolated-projects[Enables Isolated Projects. Projects are configured in parallel. Implies `--configuration-cache`. (incubating)]' \
|
||||
'--max-workers[Configures the maximum number of concurrent workers Gradle is allowed to use.]:->argument-expected' \
|
||||
(--build-cache)'--no-build-cache[Disables the Gradle build cache.]' \
|
||||
(--configuration-cache)'--no-configuration-cache[Disables the configuration cache.]' \
|
||||
(--configure-on-demand)'--no-configure-on-demand[Disables the use of configuration on demand. (incubating)]' \
|
||||
(--continue)'--no-continue[Stops task execution after a task failure.]' \
|
||||
(--daemon)'--no-daemon[Runs the build without the Gradle daemon. Useful occasionally if you have configured Gradle to always run with the daemon by default.]' \
|
||||
(--isolated-projects)'--no-isolated-projects[Disables Isolated Projects. (incubating)]' \
|
||||
(--parallel)'--no-parallel[Disables parallel project execution.]' \
|
||||
(--problems-report)'--no-problems-report[Disables the HTML problems report. (incubating)]' \
|
||||
{-a,--no-rebuild}'[Disables rebuilding of project dependencies.]' \
|
||||
|
||||
@@ -19,35 +19,54 @@ plugins=(... jj)
|
||||
| jjbd | `jj bookmark delete` |
|
||||
| jjbf | `jj bookmark forget` |
|
||||
| jjbl | `jj bookmark list` |
|
||||
| jjblt | `jj bookmark list --tracked` |
|
||||
| jjbm | `jj bookmark move` |
|
||||
| jjbr | `jj bookmark rename` |
|
||||
| jjbs | `jj bookmark set` |
|
||||
| jjbt | `jj bookmark track` |
|
||||
| jjbu | `jj bookmark untrack` |
|
||||
| jjc | `jj commit` |
|
||||
| jjcfg | `jj config` |
|
||||
| jjcfgl | `jj config list` |
|
||||
| jjcmsg | `jj commit --message` |
|
||||
| jjd | `jj diff` |
|
||||
| jjdmsg | `jj desc --message` |
|
||||
| jjds | `jj desc` |
|
||||
| jjdst | `jj diff --stat` |
|
||||
| jje | `jj edit` |
|
||||
| jjev | `jj evolog` |
|
||||
| jjf | `jj file` |
|
||||
| jjfl | `jj file list` |
|
||||
| jjg | `jj git` |
|
||||
| jjgcl | `jj git clone` |
|
||||
| jjgf | `jj git fetch` |
|
||||
| jjgfa | `jj git fetch --all-remotes` |
|
||||
| jjgi | `jj git init` |
|
||||
| jjgp | `jj git push` |
|
||||
| jjgpa | `jj git push --all` |
|
||||
| jjgpd | `jj git push --deleted` |
|
||||
| jjgpt | `jj git push --tracked` |
|
||||
| jjgrl | `jj git remote list` |
|
||||
| jjl | `jj log` |
|
||||
| jjla | `jj log -r "all()"` |
|
||||
| jjn | `jj new` |
|
||||
| jjnt | `jj new "trunk()"` |
|
||||
| jjop | `jj op` |
|
||||
| jjopl | `jj op log` |
|
||||
| jjor | `jj op restore` |
|
||||
| jjrb | `jj rebase` |
|
||||
| jjrbm | `jj rebase -d "trunk()"` |
|
||||
| jjrs | `jj restore` |
|
||||
| jjrt | `cd "$(jj root \|\| echo .)"` |
|
||||
| jjs | `jj show` |
|
||||
| jjsp | `jj split` |
|
||||
| jjsq | `jj squash` |
|
||||
| jjst | `jj status` |
|
||||
| jju | `jj undo` |
|
||||
| jjw | `jj workspace` |
|
||||
| jjwa | `jj workspace add` |
|
||||
| jjwf | `jj workspace forget` |
|
||||
| jjwl | `jj workspace list` |
|
||||
|
||||
## Prompt usage
|
||||
|
||||
|
||||
@@ -45,34 +45,51 @@ alias jjbc='jj bookmark create'
|
||||
alias jjbd='jj bookmark delete'
|
||||
alias jjbf='jj bookmark forget'
|
||||
alias jjbl='jj bookmark list'
|
||||
alias jjblt='jj bookmark list --tracked'
|
||||
alias jjbm='jj bookmark move'
|
||||
alias jjbr='jj bookmark rename'
|
||||
alias jjbs='jj bookmark set'
|
||||
alias jjbt='jj bookmark track'
|
||||
alias jjbu='jj bookmark untrack'
|
||||
alias jjc='jj commit'
|
||||
alias jjcfg='jj config'
|
||||
alias jjcfgl='jj config list'
|
||||
alias jjcmsg='jj commit --message'
|
||||
alias jjd='jj diff'
|
||||
alias jjdmsg='jj desc --message'
|
||||
alias jjds='jj desc'
|
||||
alias jjdst='jj diff --stat'
|
||||
alias jje='jj edit'
|
||||
alias jjev='jj evolog'
|
||||
alias jjf='jj file'
|
||||
alias jjfl='jj file list'
|
||||
alias jjg='jj git'
|
||||
alias jjgcl='jj git clone'
|
||||
alias jjgf='jj git fetch'
|
||||
alias jjgfa='jj git fetch --all-remotes'
|
||||
alias jjgi='jj git init'
|
||||
alias jjgp='jj git push'
|
||||
alias jjgpa='jj git push --all'
|
||||
alias jjgpd='jj git push --deleted'
|
||||
alias jjgpt='jj git push --tracked'
|
||||
alias jjgrl='jj git remote list'
|
||||
alias jjl='jj log'
|
||||
alias jjla='jj log -r "all()"'
|
||||
alias jjn='jj new'
|
||||
alias jjnt='jj new "trunk()"'
|
||||
alias jjop='jj op'
|
||||
alias jjopl='jj op log'
|
||||
alias jjor='jj op restore'
|
||||
alias jjrb='jj rebase'
|
||||
alias jjrbm='jj rebase -d "trunk()"'
|
||||
alias jjrs='jj restore'
|
||||
alias jjrt='cd "$(jj root || echo .)"'
|
||||
alias jjs='jj show'
|
||||
alias jjsp='jj split'
|
||||
alias jjsq='jj squash'
|
||||
alias jjst='jj status'
|
||||
alias jju='jj undo'
|
||||
alias jjw='jj workspace'
|
||||
alias jjwa='jj workspace add'
|
||||
alias jjwf='jj workspace forget'
|
||||
alias jjwl='jj workspace list'
|
||||
|
||||
@@ -35,6 +35,7 @@ alias jdlr='juju debug-log --ms --replay'
|
||||
alias jcon='juju consume'
|
||||
alias jeb='juju export-bundle'
|
||||
alias jex='juju expose'
|
||||
alias junex='juju unexpose'
|
||||
alias jh='juju help'
|
||||
alias jkc='juju kill-controller -y -t 0'
|
||||
alias jm='juju models'
|
||||
|
||||
@@ -71,6 +71,7 @@ plugins=(... kubectl)
|
||||
| | | **Secret management** |
|
||||
| kgsec | `kubectl get secret` | Get secret for decoding |
|
||||
| kgseca | `kubectl get secret --all-namespaces` | List secrets across all namespaces |
|
||||
| kesec | `kubectl edit secret` | Edit secret resource |
|
||||
| kdsec | `kubectl describe secret` | Describe secret resource in detail |
|
||||
| kdelsec | `kubectl delete secret` | Delete the secret |
|
||||
| | | **Deployment management** |
|
||||
|
||||
@@ -93,6 +93,7 @@ alias kdelcm='kubectl delete configmap'
|
||||
# Secret management
|
||||
alias kgsec='kubectl get secret'
|
||||
alias kgseca='kubectl get secret --all-namespaces'
|
||||
alias kesec='kubectl edit secret'
|
||||
alias kdsec='kubectl describe secret'
|
||||
alias kdelsec='kubectl delete secret'
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ plugins=(... laravel)
|
||||
| `pami` | `php artisan make:interface` |
|
||||
| `pamtr` | `php artisan make:trait` |
|
||||
| `pamv` | `php artisan make:view` |
|
||||
| `pammig` | `php artisan make:migration` |
|
||||
|
||||
## Clears
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ alias pamen='php artisan make:enum'
|
||||
alias pami='php artisan make:interface'
|
||||
alias pamtr='php artisan make:trait'
|
||||
alias pamv='php artisan make:view'
|
||||
alias pammig='php artisan make:migration'
|
||||
|
||||
|
||||
# Clears
|
||||
|
||||
@@ -23,7 +23,7 @@ _togglePoetryShell() {
|
||||
|
||||
# Activate the environment if in a Poetry directory and no environment is currently active
|
||||
if [[ $in_poetry_dir -eq 1 ]] && [[ $poetry_active -ne 1 ]]; then
|
||||
venv_dir=$(poetry env info --path 2>/dev/null)
|
||||
local venv_dir=$(poetry env info --path 2>/dev/null)
|
||||
# Handle case where poetry returns "." for in-project virtual environments
|
||||
if [[ "$venv_dir" == "." ]]; then
|
||||
venv_dir="$PWD/.venv"
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
# - https://github.com/symfony/symfony/blob/5.4/src/Symfony/Component/Console/Resources/completion.bash
|
||||
#
|
||||
_sf_console() {
|
||||
local lastParam flagPrefix requestComp out comp
|
||||
local -a completions
|
||||
local lastParam out comp sf_cmd
|
||||
local -a completions flagPrefix requestComp inputs
|
||||
|
||||
# The user could have moved the cursor backwards on the command-line.
|
||||
# We need to trigger completion from the $CURRENT location, so we need
|
||||
@@ -29,11 +29,20 @@ _sf_console() {
|
||||
setopt local_options BASH_REMATCH
|
||||
if [[ "${lastParam}" =~ '-.*=' ]]; then
|
||||
# We are dealing with a flag with an =
|
||||
flagPrefix="-P ${BASH_REMATCH}"
|
||||
flagPrefix=(-P "${BASH_REMATCH}")
|
||||
fi
|
||||
|
||||
# Prepare the command to obtain completions
|
||||
requestComp="${words[0]} ${words[1]} _complete --no-interaction -szsh -a1 -c$((CURRENT-1))" i=""
|
||||
# Prepare the command to obtain completions. An alias is resolved here,
|
||||
# because the request is no longer read again by the shell.
|
||||
sf_cmd="${words[1]}"
|
||||
if [[ -n "${aliases[$sf_cmd]}" ]]; then
|
||||
requestComp=(${(z)aliases[$sf_cmd]})
|
||||
else
|
||||
requestComp=(${~sf_cmd})
|
||||
fi
|
||||
|
||||
requestComp+=(_complete --no-interaction -szsh -a1 "-c$((CURRENT-1))")
|
||||
|
||||
for w in ${words[@]}; do
|
||||
w=$(printf -- '%b' "$w")
|
||||
# remove quotes from typed values
|
||||
@@ -47,19 +56,18 @@ _sf_console() {
|
||||
fi
|
||||
# empty values are ignored
|
||||
if [ ! -z "$w" ]; then
|
||||
i="${i}-i${w} "
|
||||
inputs+=("-i$w")
|
||||
fi
|
||||
done
|
||||
|
||||
# Ensure at least 1 input
|
||||
if [ "${i}" = "" ]; then
|
||||
requestComp="${requestComp} -i\" \""
|
||||
else
|
||||
requestComp="${requestComp} ${i}"
|
||||
if (( ! $#inputs )); then
|
||||
inputs=(-i' ')
|
||||
fi
|
||||
|
||||
# Use eval to handle any environment variables and such
|
||||
out=$(eval ${requestComp} 2>/dev/null)
|
||||
# The request is run without being read again by the shell, so that a
|
||||
# "$(...)" or a backtick typed on the command line is not executed
|
||||
out=$(SHELL_VERBOSITY=0 "${requestComp[@]}" "${inputs[@]}" 2>/dev/null)
|
||||
|
||||
while IFS='\n' read -r comp; do
|
||||
if [ -n "$comp" ]; then
|
||||
@@ -75,7 +83,7 @@ _sf_console() {
|
||||
done < <(printf "%s\n" "${out[@]}")
|
||||
|
||||
# Let inbuilt _describe handle completions
|
||||
eval _describe "completions" completions $flagPrefix
|
||||
_describe "completions" completions "${flagPrefix[@]}"
|
||||
return $?
|
||||
}
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ case $state in
|
||||
(box)
|
||||
__vagrant-box
|
||||
;;
|
||||
(up|provision|port|package|destroy|reload|ssh|ssh-config|halt|resume|status)
|
||||
(up|provision|port|package|destroy|reload|ssh|ssh-config|halt|resume|status|suspend|rdp)
|
||||
_arguments ':feature:__vm_list'
|
||||
esac
|
||||
;;
|
||||
|
||||
@@ -39,7 +39,7 @@ if [[ ! $DISABLE_VENV_CD -eq 1 ]]; then
|
||||
# Get absolute path, resolving symlinks
|
||||
local PROJECT_ROOT="${PWD:A}"
|
||||
while [[ "$PROJECT_ROOT" != "/" && ! -e "$PROJECT_ROOT/.venv" \
|
||||
&& ! -d "$PROJECT_ROOT/.git" ]]; do
|
||||
&& ! -e "$PROJECT_ROOT/.git" ]]; do
|
||||
PROJECT_ROOT="${PROJECT_ROOT:h}"
|
||||
done
|
||||
|
||||
|
||||
@@ -51,6 +51,11 @@ Available search contexts are:
|
||||
| `npmpkg` | `https://www.npmjs.com/search?q=` |
|
||||
| `packagist` | `https://packagist.org/?query=` |
|
||||
| `gopkg` | `https://pkg.go.dev/search?m=package&q=` |
|
||||
| `maven` | `https://central.sonatype.com/search?q=` |
|
||||
| `pypi` | `https://pypi.org/search/?q=` |
|
||||
| `aur` | `https://aur.archlinux.org/packages?O=0&K=` |
|
||||
| `archwiki` | `https://wiki.archlinux.org/index.php?search=` |
|
||||
| `wikipedia` | `https://en.wikipedia.org/w/index.php?search=` |
|
||||
| `chatgpt` | `https://chatgpt.com/?q=` |
|
||||
| `claudeai` | `https://claude.ai/new?q=` |
|
||||
| `grokcom` | `https://grok.com/?q=` |
|
||||
|
||||
@@ -32,6 +32,11 @@ function web_search() {
|
||||
npmpkg "https://www.npmjs.com/search?q="
|
||||
packagist "https://packagist.org/?query="
|
||||
gopkg "https://pkg.go.dev/search?m=package&q="
|
||||
maven "https://central.sonatype.com/search?q="
|
||||
pypi "https://pypi.org/search/?q="
|
||||
aur "https://aur.archlinux.org/packages?O=0&K="
|
||||
archwiki "https://wiki.archlinux.org/index.php?search="
|
||||
wikipedia "https://en.wikipedia.org/w/index.php?search="
|
||||
chatgpt "https://chatgpt.com/?q="
|
||||
grok "https://grok.com/?q="
|
||||
claudeai "https://claude.ai/new?q="
|
||||
@@ -92,6 +97,11 @@ alias gems='web_search gems'
|
||||
alias npmpkg='web_search npmpkg'
|
||||
alias packagist='web_search packagist'
|
||||
alias gopkg='web_search gopkg'
|
||||
alias maven='web_search maven'
|
||||
alias pypi='web_search pypi'
|
||||
alias aur='web_search aur'
|
||||
alias archwiki='web_search archwiki'
|
||||
alias wikipedia='web_search wikipedia'
|
||||
alias chatgpt='web_search chatgpt'
|
||||
alias grokcom='web_search grok'
|
||||
alias claudeai='web_search claudeai'
|
||||
@@ -116,4 +126,3 @@ if [[ ${#ZSH_WEB_SEARCH_ENGINES} -gt 0 ]]; then
|
||||
done
|
||||
unset engines key
|
||||
fi
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ export LSCOLORS=dxFxCxDxBxegedabagacad
|
||||
|
||||
local git_branch='$(git_prompt_info)%{$reset_color%}$(git_remote_status)'
|
||||
|
||||
PROMPT="%{$fg[green]%}╭─%n@%m %{$reset_color%}%{$fg[yellow]%}in %~ %{$reset_color%}${git_branch}
|
||||
%{$fg[green]%}╰\$ %{$reset_color%}"
|
||||
PROMPT="%{%(#~$fg[red]~$fg[green])%}╭─%n@%m %{$reset_color%}%{$fg[yellow]%}in %~ %{$reset_color%}${git_branch}
|
||||
%{%(#~$fg[red]~$fg[green])%}╰%(#~#~\$) %{$reset_color%}"
|
||||
|
||||
ZSH_THEME_GIT_PROMPT_PREFIX="%{$fg_bold[yellow]%}on "
|
||||
ZSH_THEME_GIT_PROMPT_SUFFIX="%{$reset_color%}"
|
||||
|
||||
@@ -23,12 +23,32 @@ zstyle -s ':omz:update' mode update_mode || {
|
||||
# - the automatic update is disabled
|
||||
# - the current user doesn't have write permissions nor owns the $ZSH directory
|
||||
# - is not run from a tty
|
||||
# - git is unavailable on the system
|
||||
# - $ZSH is not a git repository
|
||||
if [[ "$update_mode" = disabled ]] \
|
||||
|| [[ ! -w "$ZSH" || ! -O "$ZSH" ]] \
|
||||
|| [[ ! -t 1 && ${POWERLEVEL9K_INSTANT_PROMPT:-off} == off ]] \
|
||||
|| ! command git --version >/dev/null 2>&1 \
|
||||
|| [[ ! -t 1 && ${POWERLEVEL9K_INSTANT_PROMPT:-off} == off ]]; then
|
||||
unset update_mode
|
||||
return
|
||||
fi
|
||||
|
||||
# Cancel update if it's not time to check yet. This runs before the git checks
|
||||
# below because those fork on every startup. Skipped when a background update
|
||||
# left a result that still needs to be reported (EXIT_STATUS is set).
|
||||
() {
|
||||
local LAST_EPOCH EXIT_STATUS ERROR epoch_target
|
||||
zmodload zsh/datetime
|
||||
source "${ZSH_CACHE_DIR}/.zsh-update" 2>/dev/null || return 1
|
||||
[[ -n "$LAST_EPOCH" && -z "$EXIT_STATUS" ]] || return 1
|
||||
zstyle -s ':omz:update' frequency epoch_target || epoch_target=${UPDATE_ZSH_DAYS:-13}
|
||||
(( ( EPOCHSECONDS / 60 / 60 / 24 - LAST_EPOCH ) < epoch_target ))
|
||||
} && {
|
||||
unset update_mode
|
||||
return
|
||||
}
|
||||
|
||||
# Cancel update if:
|
||||
# - git is unavailable on the system
|
||||
# - $ZSH is not a git repository
|
||||
if ! command git --version >/dev/null 2>&1 \
|
||||
|| (builtin cd -q "$ZSH"; ! command git rev-parse --is-inside-work-tree &>/dev/null); then
|
||||
unset update_mode
|
||||
return
|
||||
|
||||
+4
-3
@@ -528,9 +528,10 @@ print_success() {
|
||||
"$(fmt_code "$(fmt_link ".zshrc" "file://$zdot/.zshrc" --text)")" \
|
||||
"file to select plugins, themes, and options."
|
||||
printf '\n'
|
||||
printf '%s\n' "• Follow us on X: $(fmt_link @ohmyzsh https://x.com/ohmyzsh)"
|
||||
printf '%s\n' "• Join our Discord community: $(fmt_link "Discord server" https://discord.gg/ohmyzsh)"
|
||||
printf '%s\n' "• Get stickers, t-shirts, coffee mugs and more: $(fmt_link "CommitGoods Shop" https://commitgoods.com/collections/oh-my-zsh)"
|
||||
printf '%s\n' "• Follow along on X ($(fmt_link @ohmyzsh https://x.com/ohmyzsh)) or Bluesky ($(fmt_link @ohmyz.sh https://bsky.app/profile/ohmyz.sh))"
|
||||
printf '%s\n' "• Questions or ideas? Join the community on $(fmt_link Discord https://discord.gg/ohmyzsh)"
|
||||
printf '%s\n' "• Help support the project: sponsor us on $(fmt_link "Open Collective" https://opencollective.com/ohmyzsh),"
|
||||
printf '%s\n' " or grab stickers, shirts, and swag from $(fmt_link CommitGoods https://commitgoods.com/ohmyzsh)"
|
||||
printf '%s\n' $FMT_RESET
|
||||
}
|
||||
|
||||
|
||||
@@ -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'
|
||||
+12
-3
@@ -271,9 +271,18 @@ if LANG= git pull --quiet --rebase $remote $branch; then
|
||||
printf '%s %s %s %s /____/ %s %s %s %s\n' $RAINBOW $RESET
|
||||
printf '\n'
|
||||
printf "${BLUE}%s${RESET}\n\n" "$message"
|
||||
printf "${BLUE}${BOLD}%s %s${RESET}\n" "To keep up with the latest news and updates, follow us on X:" "$(fmt_link @ohmyzsh https://x.com/ohmyzsh)"
|
||||
printf "${BLUE}${BOLD}%s %s${RESET}\n" "Want to get involved in the community? Join our Discord:" "$(fmt_link "Discord server" https://discord.gg/ohmyzsh)"
|
||||
printf "${BLUE}${BOLD}%s %s${RESET}\n" "Get your Oh My Zsh swag at:" "$(fmt_link "CommitGoods Shop" https://commitgoods.com/collections/oh-my-zsh)"
|
||||
printf "${BLUE}${BOLD}%s %s %s %s${RESET}\n" \
|
||||
"Follow along on X" "($(fmt_link @ohmyzsh https://x.com/ohmyzsh))" \
|
||||
"or Bluesky" "($(fmt_link @ohmyz.sh https://bsky.app/profile/ohmyz.sh))"
|
||||
printf "${BLUE}${BOLD}%s %s${RESET}\n" \
|
||||
"Questions or ideas? Join the community on" \
|
||||
"$(fmt_link Discord https://discord.gg/ohmyzsh)"
|
||||
printf "${BLUE}${BOLD}%s %s,${RESET}\n" \
|
||||
"Help support the project: sponsor us on" \
|
||||
"$(fmt_link "Open Collective" https://opencollective.com/ohmyzsh)"
|
||||
printf "${BLUE}${BOLD}%s %s${RESET}\n" \
|
||||
"or grab stickers, shirts, and swag from" \
|
||||
"$(fmt_link CommitGoods https://commitgoods.com/ohmyzsh)"
|
||||
elif [[ $verbose_mode == minimal ]]; then
|
||||
printf "${BLUE}%s${RESET}\n" "$message"
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user