mirror of
https://github.com/robbyrussell/oh-my-zsh.git
synced 2026-09-21 18:46:06 +02:00
refactor(cli): group theme browser files
This commit is contained in:
@@ -181,7 +181,7 @@ 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
|
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.
|
for explicit use/save actions. Esc or Ctrl-C cancels without applying a theme.
|
||||||
See the [theme browser guide](tools/theme-browser.md) for shortcuts, preview limitations,
|
See the [theme browser guide](tools/theme-browser/README.md) for shortcuts, preview limitations,
|
||||||
and local testing instructions.
|
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
|
||||||
|
|||||||
+4
-4
@@ -79,7 +79,7 @@ function _omz {
|
|||||||
_describe -o 'options' opts ;;
|
_describe -o 'options' opts ;;
|
||||||
theme::(browse|preview))
|
theme::(browse|preview))
|
||||||
local -a themes
|
local -a themes
|
||||||
themes=("${(@f)$(source "$ZSH/tools/theme-preview.zsh"; _omz_theme_names)}")
|
themes=("${(@f)$(source "$ZSH/tools/theme-browser/preview.zsh"; _omz_theme_names)}")
|
||||||
_describe 'theme' themes ;;
|
_describe 'theme' themes ;;
|
||||||
theme::(set|use))
|
theme::(set|use))
|
||||||
local -aU themes
|
local -aU themes
|
||||||
@@ -774,7 +774,7 @@ function _omz::theme::preview {
|
|||||||
print -u2 -r -- 'Usage: omz theme preview <theme>'
|
print -u2 -r -- 'Usage: omz theme preview <theme>'
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
source "$ZSH/tools/theme-preview.zsh"
|
source "$ZSH/tools/theme-browser/preview.zsh"
|
||||||
_omz_theme_preview "$1"
|
_omz_theme_preview "$1"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -787,8 +787,8 @@ function _omz::theme::browse {
|
|||||||
print -u2 -r -- 'omz theme browse requires an interactive terminal; use omz theme preview <theme> instead.'
|
print -u2 -r -- 'omz theme browse requires an interactive terminal; use omz theme preview <theme> instead.'
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
source "$ZSH/tools/theme-preview.zsh"
|
source "$ZSH/tools/theme-browser/preview.zsh"
|
||||||
source "$ZSH/tools/theme-browser.zsh"
|
source "$ZSH/tools/theme-browser/browser.zsh"
|
||||||
local selection action name
|
local selection action name
|
||||||
selection=$(_omz_theme_browser "$1") || return $?
|
selection=$(_omz_theme_browser "$1") || return $?
|
||||||
[[ -n $selection ]] || return 0
|
[[ -n $selection ]] || return 0
|
||||||
|
|||||||
@@ -1,118 +0,0 @@
|
|||||||
# Theme Browser
|
|
||||||
|
|
||||||
```zsh
|
|
||||||
omz theme browse
|
|
||||||
omz theme browse agn
|
|
||||||
omz theme preview agnoster
|
|
||||||
```
|
|
||||||
|
|
||||||
The browser renders only the highlighted theme, in your invoking directory with real,
|
|
||||||
synchronous Git information. Filtering is a case-insensitive literal substring match.
|
|
||||||
Names are deduplicated; resolution follows normal Oh My Zsh precedence:
|
|
||||||
`$ZSH_CUSTOM/<name>.zsh-theme`, `$ZSH_CUSTOM/themes/<name>.zsh-theme`, then
|
|
||||||
`$ZSH/themes/<name>.zsh-theme`. Nested custom themes are supported. The `random`
|
|
||||||
selector and names containing traversal components or control characters are excluded.
|
|
||||||
|
|
||||||
## 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 calls `omz theme use`: it does **not** remove hooks, widgets, or other
|
|
||||||
state installed by your previous theme. Saving calls `omz theme set`, edits `.zshrc`,
|
|
||||||
and reloads the shell. These actions happen only after leaving the browser and
|
|
||||||
restoring terminal state. Names containing shell punctuation cannot be saved through
|
|
||||||
the browser; configure those manually. No global aliases or keybindings are installed.
|
|
||||||
|
|
||||||
## Preview Limits
|
|
||||||
|
|
||||||
- Each preview runs in a fresh `zsh -df` worker, not your active shell. The worker
|
|
||||||
inherits the directory and exported environment, but not non-exported theme
|
|
||||||
settings, plugins, user `.zshrc`, or custom library overrides. System `zshenv`
|
|
||||||
still runs as required by zsh.
|
|
||||||
- Previews initialize prompt helpers and precmd hooks. They show a successful
|
|
||||||
previous command (status 0). Tests also exercise nonzero status rendering.
|
|
||||||
- Multiline left prompts are supported. Right prompts are deliberately labeled
|
|
||||||
and displayed separately, not positioned as ZLE would position them.
|
|
||||||
- Interactive/async themes, plugin-dependent segments, terminal queries, and
|
|
||||||
widget-driven prompts may be incomplete or unsupported. Themes still require
|
|
||||||
their usual fonts for special glyphs; the browser itself requires no fonts.
|
|
||||||
- Workers have a three-second execution limit and a 32,000-byte output limit.
|
|
||||||
Navigation cancels obsolete work. ANSI colors are retained; other terminal
|
|
||||||
controls are filtered. Long lines and tall previews are clipped in the browser.
|
|
||||||
- The browser requires an interactive terminal with alternate-screen support and
|
|
||||||
at least 40 columns by 16 rows. Smaller windows show a resize message; Esc still
|
|
||||||
exits. `omz theme preview` also works without an interactive terminal.
|
|
||||||
- This is **shell-state isolation, not a security sandbox**. Only preview trusted
|
|
||||||
themes: their code can write files, access the network, or deliberately detach
|
|
||||||
processes. The supervisor cleans up ordinary descendants, not escaped processes.
|
|
||||||
|
|
||||||
No new third-party runtime is required. The implementation uses standard zsh modules
|
|
||||||
(`zpty`, `system`, `datetime`, `zselect`, `terminfo`) and platform utilities including
|
|
||||||
`stty`, `mktemp`, `mkfifo`, and `rm`. Git segments need Git as usual. Private temporary
|
|
||||||
files are removed when previews finish or are cancelled. Full OMZ startup, update
|
|
||||||
checks, and OMZ cache initialization are not invoked by workers. The old
|
|
||||||
`tools/theme_chooser.sh` is unchanged.
|
|
||||||
|
|
||||||
## Local Testing
|
|
||||||
|
|
||||||
From this checkout, launch a disposable interactive shell without reading your `.zshrc`:
|
|
||||||
|
|
||||||
```zsh
|
|
||||||
env ZSH="$PWD" zsh -dfi
|
|
||||||
```
|
|
||||||
|
|
||||||
Inside it, load the CLI and try previews. Change directory to a Git repository to
|
|
||||||
compare clean, dirty, and untracked-file states:
|
|
||||||
|
|
||||||
```zsh
|
|
||||||
source "$ZSH/lib/cli.zsh"
|
|
||||||
omz theme preview robbyrussell
|
|
||||||
omz theme preview agnoster
|
|
||||||
omz theme preview half-life
|
|
||||||
omz theme preview dieter
|
|
||||||
omz theme browse
|
|
||||||
```
|
|
||||||
|
|
||||||
This minimal shell is intended for preview/navigation testing. For a real session-use
|
|
||||||
test, load this checkout's full OMZ configuration in a disposable shell. Do not choose
|
|
||||||
Save unless you intend to edit your actual `.zshrc`; automated browser tests stub both
|
|
||||||
actions and never edit your configuration. Exit the disposable shell when finished.
|
|
||||||
|
|
||||||
Regression suites run without additional test frameworks:
|
|
||||||
|
|
||||||
```zsh
|
|
||||||
zsh -df tools/tests/theme-preview.zsh
|
|
||||||
zsh -df tools/tests/theme-browser.zsh
|
|
||||||
```
|
|
||||||
|
|
||||||
The tests cover resolution, prompt isolation, Git context, hook/status rendering,
|
|
||||||
terminal-control filtering, failures, time/output limits, descendant cleanup,
|
|
||||||
completion candidates, lazy loading, keyboard navigation, resizing, action dispatch,
|
|
||||||
and terminal restoration using real PTYs. Human visual testing in your terminal and
|
|
||||||
font is still required before proposing a PR. Linux/older-zsh testing remains pending.
|
|
||||||
|
|
||||||
## Footprint And Startup
|
|
||||||
|
|
||||||
Runtime code is roughly 18 KiB across the three `tools/theme-*.zsh` files, plus small
|
|
||||||
CLI wrappers and completion/help entries. Tests and this guide are additional text
|
|
||||||
files, not runtime dependencies. Measure the exact current footprint with:
|
|
||||||
|
|
||||||
```zsh
|
|
||||||
wc -c tools/theme-browser.zsh tools/theme-preview.zsh tools/theme-preview-worker.zsh
|
|
||||||
```
|
|
||||||
|
|
||||||
Normal shell startup only defines the CLI wrappers: none of the three tool files is
|
|
||||||
sourced, no browser modules are loaded, and no workers or browser I/O are started.
|
|
||||||
The regression suite verifies lazy loading. Startup timing should be considered
|
|
||||||
noise-sensitive; this prototype does not claim a measurable speed improvement.
|
|
||||||
@@ -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
|
||||||
|
```
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# Invoked only by theme-preview.zsh in a fresh zsh -df process. Themes and hooks
|
# 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).
|
# are intentionally sourced/run at top level (not inside a setup function).
|
||||||
emulate -R zsh
|
emulate -R zsh
|
||||||
if [[ $1 == --supervise ]]; then
|
if [[ $1 == --supervise ]]; then
|
||||||
@@ -5,7 +5,7 @@ typeset -g _OMZ_THEME_PREVIEW_DIR=${${(%):-%x}:A:h}
|
|||||||
function _omz_theme_names() (
|
function _omz_theme_names() (
|
||||||
emulate -L zsh
|
emulate -L zsh
|
||||||
setopt extendedglob
|
setopt extendedglob
|
||||||
local root=${ZSH:-${_OMZ_THEME_PREVIEW_DIR:h}}
|
local root=${ZSH:-${_OMZ_THEME_PREVIEW_DIR:h:h}}
|
||||||
local custom=${ZSH_CUSTOM:-$root/custom} dir file name
|
local custom=${ZSH_CUSTOM:-$root/custom} dir file name
|
||||||
local -a names files
|
local -a names files
|
||||||
for dir in "$custom" "$root/themes"; do
|
for dir in "$custom" "$root/themes"; do
|
||||||
@@ -28,7 +28,7 @@ function _omz_theme_names() (
|
|||||||
function _omz_theme_resolve() (
|
function _omz_theme_resolve() (
|
||||||
emulate -L zsh
|
emulate -L zsh
|
||||||
setopt extendedglob
|
setopt extendedglob
|
||||||
local name=$1 root=${ZSH:-${_OMZ_THEME_PREVIEW_DIR:h}}
|
local name=$1 root=${ZSH:-${_OMZ_THEME_PREVIEW_DIR:h:h}}
|
||||||
local custom=${ZSH_CUSTOM:-$root/custom} dir
|
local custom=${ZSH_CUSTOM:-$root/custom} dir
|
||||||
if (( $# != 1 )) || [[ -z $name || $name == random || /$name/ == */(.|..)/* || $name == /* || $name == *//* || $name == *[[:cntrl:]\\]* ]]; then
|
if (( $# != 1 )) || [[ -z $name || $name == random || /$name/ == */(.|..)/* || $name == /* || $name == *//* || $name == *[[:cntrl:]\\]* ]]; then
|
||||||
print -u2 -r -- 'theme preview: invalid or non-selectable theme name'
|
print -u2 -r -- 'theme preview: invalid or non-selectable theme name'
|
||||||
@@ -60,7 +60,7 @@ function _omz_theme_preview() (
|
|||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
local tmp fifo_fd pty=omz-preview-$sysparams[pid] record pgid raw='' chunk error=''
|
local tmp fifo_fd pty=omz-preview-$sysparams[pid] record pgid raw='' chunk error=''
|
||||||
local selected=$1 custom=${ZSH_CUSTOM:-${ZSH:-$backend:h}/custom}
|
local selected=$1 custom=${ZSH_CUSTOM:-${ZSH:-${backend:h:h}}/custom}
|
||||||
local -i active=0 cancelled=0 bytes=0 count remaining result=1
|
local -i active=0 cancelled=0 bytes=0 count remaining result=1
|
||||||
local -F deadline
|
local -F deadline
|
||||||
trap 'cancelled=130' INT
|
trap 'cancelled=130' INT
|
||||||
@@ -73,8 +73,8 @@ function _omz_theme_preview() (
|
|||||||
|
|
||||||
# Exec immediately: a forked shell function could run inherited EXIT traps
|
# Exec immediately: a forked shell function could run inherited EXIT traps
|
||||||
# after returning. Both the launcher and theme worker must start fresh.
|
# after returning. Both the launcher and theme worker must start fresh.
|
||||||
local -a launch=("$commands[zsh]" -df "$backend/theme-preview-worker.zsh" --supervise
|
local -a launch=("$commands[zsh]" -df "$backend/preview-worker.zsh" --supervise
|
||||||
"$tmp" "$backend:h" "$theme" "$sample" "$selected" "$custom")
|
"$tmp" "${backend:h:h}" "$theme" "$sample" "$selected" "$custom")
|
||||||
deadline=$(( EPOCHREALTIME + 3 ))
|
deadline=$(( EPOCHREALTIME + 3 ))
|
||||||
if ! zpty -b "$pty" exec "${(@q)launch}"; then
|
if ! zpty -b "$pty" exec "${(@q)launch}"; then
|
||||||
print -u2 -r -- 'theme preview: could not allocate a private PTY'
|
print -u2 -r -- 'theme preview: could not allocate a private PTY'
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env zsh
|
#!/usr/bin/env zsh
|
||||||
# Run with: zsh -df tools/tests/theme-browser.zsh
|
# Run with: zsh -df tools/theme-browser/tests/browser_test.zsh
|
||||||
# Optionally pass one group, e.g. `slow-interrupt`, to run it alone.
|
# 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.
|
# Each browser runs under a real controlling PTY, with no user startup files.
|
||||||
emulate -R zsh
|
emulate -R zsh
|
||||||
@@ -85,7 +85,7 @@ setopt err_exit pipe_fail
|
|||||||
zmodload zsh/zpty
|
zmodload zsh/zpty
|
||||||
zmodload zsh/datetime
|
zmodload zsh/datetime
|
||||||
zmodload zsh/zselect
|
zmodload zsh/zselect
|
||||||
typeset -r repo=${0:A:h:h:h} self=${0:A}
|
typeset -r repo=${0:A:h:h:h:h} self=${0:A}
|
||||||
typeset scratch=$(mktemp -d "${TMPDIR:-/tmp}/omz-browser-test.XXXXXXXX")
|
typeset scratch=$(mktemp -d "${TMPDIR:-/tmp}/omz-browser-test.XXXXXXXX")
|
||||||
scratch=${scratch:A}
|
scratch=${scratch:A}
|
||||||
trap 'zpty -d 2>/dev/null; command rm -rf -- "$scratch"' EXIT
|
trap 'zpty -d 2>/dev/null; command rm -rf -- "$scratch"' EXIT
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
#!/usr/bin/env zsh
|
#!/usr/bin/env zsh
|
||||||
# Run with: zsh -df tools/tests/theme-preview.zsh
|
# Run with: zsh -df tools/theme-browser/tests/preview_test.zsh
|
||||||
emulate -R zsh
|
emulate -R zsh
|
||||||
setopt err_exit pipe_fail
|
setopt err_exit pipe_fail
|
||||||
typeset -r repo=${0:A:h:h:h}
|
typeset -r repo=${0:A:h:h:h:h}
|
||||||
source "$repo/tools/theme-preview.zsh"
|
source "$repo/tools/theme-browser/preview.zsh"
|
||||||
typeset scratch=$(mktemp -d "${TMPDIR:-/tmp}/omz-preview-test.XXXXXXXX")
|
typeset scratch=$(mktemp -d "${TMPDIR:-/tmp}/omz-preview-test.XXXXXXXX")
|
||||||
scratch=${scratch:A}
|
scratch=${scratch:A}
|
||||||
trap 'command rm -rf -- "$scratch"' EXIT
|
trap 'command rm -rf -- "$scratch"' EXIT
|
||||||
@@ -220,7 +220,7 @@ check test -z "${child_state//[ ZN+]/}"
|
|||||||
# Match the browser's nested-PTY invocation, preserving other owned PTYs.
|
# Match the browser's nested-PTY invocation, preserving other owned PTYs.
|
||||||
zmodload zsh/zpty
|
zmodload zsh/zpty
|
||||||
zpty -b unrelated 'exec sleep 30'
|
zpty -b unrelated 'exec sleep 30'
|
||||||
typeset -a nested_command=("$commands[zsh]" -dfc 'source "$1"; _omz_theme_preview status' zsh "$repo/tools/theme-preview.zsh")
|
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}"
|
zpty -b browser exec "${(@q)nested_command}"
|
||||||
output=''
|
output=''
|
||||||
for attempt in {1..300}; do
|
for attempt in {1..300}; do
|
||||||
@@ -239,7 +239,7 @@ zpty -d browser unrelated
|
|||||||
# Cancellation by the browser's outer process group must also clean the
|
# Cancellation by the browser's outer process group must also clean the
|
||||||
# backend's separate inner PTY group and private FIFO directory.
|
# backend's separate inner PTY group and private FIFO directory.
|
||||||
rm -f "$PREVIEW_PID_FILE"
|
rm -f "$PREVIEW_PID_FILE"
|
||||||
nested_command=("$commands[zsh]" -dfc 'source "$1"; _omz_theme_preview slow' zsh "$repo/tools/theme-preview.zsh")
|
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}"
|
zpty -b browser exec "${(@q)nested_command}"
|
||||||
typeset browser_group record
|
typeset browser_group record
|
||||||
for record in "${(@f)$(zpty)}"; do
|
for record in "${(@f)$(zpty)}"; do
|
||||||
@@ -266,7 +266,7 @@ zpty -d browser
|
|||||||
export ZSH=$repo
|
export ZSH=$repo
|
||||||
git -C "$scratch/work" init -q
|
git -C "$scratch/work" init -q
|
||||||
git -C "$scratch/work" symbolic-ref HEAD refs/heads/preview-branch
|
git -C "$scratch/work" symbolic-ref HEAD refs/heads/preview-branch
|
||||||
output=$(zsh -dfc 'builtin cd "$1"; source tools/theme-preview.zsh; builtin cd "$2"; _omz_theme_preview robbyrussell' zsh "$repo" "$scratch/work")
|
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"
|
check test "${output#*preview-branch}" != "$output"
|
||||||
for name in robbyrussell dieter agnoster bira nicoulaj half-life; do
|
for name in robbyrussell dieter agnoster bira nicoulaj half-life; do
|
||||||
output=$(builtin cd "$scratch/work"; _omz_theme_preview "$name" 7)
|
output=$(builtin cd "$scratch/work"; _omz_theme_preview "$name" 7)
|
||||||
Reference in New Issue
Block a user