From 87bb5da2e9fd38589da541f48910360cf9ad57ee Mon Sep 17 00:00:00 2001 From: Kieran Klukas Date: Sun, 7 Jun 2026 01:01:43 -0400 Subject: [PATCH] feat: rename and remove npm --- .github/issue_template.md | 4 +- .npmrc | 1 - license => LICENSE.md | 6 +- README.md | 162 +++++++++++++++ arch/PKGBUILD | 26 +-- impure.zsh | 94 +++++---- package.json | 49 ----- readme.md | 355 --------------------------------- tests/async-worker-failure.zsh | 224 ++++++++++----------- tests/benchmark.zsh | 46 ++--- tests/git-dirty.zsh | 186 ++++++----------- tests/node-version.zsh | 112 ----------- tests/path-separator.zsh | 144 ------------- tests/preview.zsh | 81 -------- tests/prompt-fingerprint.zsh | 60 ------ tests/rprompt.zsh | 15 -- tests/test-helper.zsh | 4 +- tests/test-prompt.zsh | 27 +-- tests/test.zsh | 38 +++- tests/virtualenv.zsh | 107 ---------- 20 files changed, 460 insertions(+), 1281 deletions(-) delete mode 100644 .npmrc rename license => LICENSE.md (88%) create mode 100644 README.md delete mode 100644 package.json delete mode 100644 readme.md delete mode 100644 tests/node-version.zsh delete mode 100644 tests/path-separator.zsh delete mode 100644 tests/preview.zsh delete mode 100644 tests/prompt-fingerprint.zsh delete mode 100644 tests/rprompt.zsh delete mode 100644 tests/virtualenv.zsh diff --git a/.github/issue_template.md b/.github/issue_template.md index 001af80..d8aa580 100644 --- a/.github/issue_template.md +++ b/.github/issue_template.md @@ -2,9 +2,9 @@ ### General information -#### System report (output of `prompt_pure_system_report`): +#### System report (output of `prompt_impure_system_report`): - + #### Other information diff --git a/.npmrc b/.npmrc deleted file mode 100644 index 43c97e7..0000000 --- a/.npmrc +++ /dev/null @@ -1 +0,0 @@ -package-lock=false diff --git a/license b/LICENSE.md similarity index 88% rename from license rename to LICENSE.md index fa7ceba..3636fec 100644 --- a/license +++ b/LICENSE.md @@ -1,6 +1,8 @@ -MIT License +# MIT License -Copyright (c) Sindre Sorhus (https://sindresorhus.com) +Copyright © `2026` `Kieran Klukas` + +Copyright © `Sindre Sorhus` (https://sindresorhus.com) — original Pure prompt Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/README.md b/README.md new file mode 100644 index 0000000..98f079e --- /dev/null +++ b/README.md @@ -0,0 +1,162 @@ +# impure + +> A pretty, minimal and fast ZSH prompt — a fork of [Pure](https://github.com/sindresorhus/pure) with Jujutsu, nix-shell, and zmx awareness + +The canonical repo for this is hosted on tangled over at [`dunkirk.sh/impure`](https://tangled.org/dunkirk.sh/impure) + + + +## Features + +- **Async git status.** Branch, dirty marker, and unpushed/unpulled arrows are + computed in a background worker so the prompt never blocks. +- **Staging summary.** Dirty repos show an oh-my-posh-style breakdown of the + index, e.g. `main (+2 ~1 -1)*` — added, modified, deleted, plus a `*` when the + working tree is dirty. +- **Jujutsu support.** Inside a `jj` repo the prompt shows bookmarks, the short + change ID (in cyan), and a working-copy change count. `jj` takes precedence + over git when both are present. +- **nix-shell and virtualenv.** The active environment name is shown on the + right prompt. +- **zmx sessions.** When `$ZMX_SESSION` is set the tag is shown in the preprompt. +- **Transient prompt.** After you press Enter the two-line prompt collapses to a + single `❯` in scrollback, keeping history clean. +- **Custom segments.** Define `prompt_impure_precustom` to inject your own prefix + and suffix segments. +- **Exec time, SSH/container detection, VI-mode indicator, suspended-jobs + marker** — all carried over from Pure. + +Requires Git 2.15.2+ and ZSH 5.2+ (5.3+ recommended for VI-mode and transient +prompt). `jujutsu` is optional and only needed for jj status. + +## Install + +### Arch Linux + +A `PKGBUILD` is provided under [`arch/`](arch/PKGBUILD). Build and install it +with `makepkg -si`. + +### Manually + +1. Clone this repo somewhere, e.g. `$HOME/.zsh/impure`: + + ```sh + mkdir -p "$HOME/.zsh" + git clone https://tangled.org/dunkirk.sh/impure "$HOME/.zsh/impure" + ``` + +2. Add the path to `fpath` in `$HOME/.zshrc`: + + ```sh + fpath+=("$HOME/.zsh/impure") + ``` + +## Getting started + +Initialize the prompt system (if not already) and choose `impure`: + +```sh +# .zshrc +autoload -U promptinit; promptinit +prompt impure +``` + +## Configuration + +### Environment variables + +| Variable | Default | Description | +| ------------------------------ | ------- | ------------------------------------------------------------- | +| `IMPURE_CMD_MAX_EXEC_TIME` | `3` | Seconds before a command's exec time is shown. | +| `IMPURE_GIT_PULL` | `1` | Set to `0` to disable the background `git fetch`. | +| `IMPURE_GIT_UNTRACKED_DIRTY` | `1` | Set to `0` to ignore untracked files when checking dirtiness. | +| `IMPURE_GIT_DELAY_DIRTY_CHECK` | `1800` | Seconds to cache a slow dirty check before re-running. | +| `IMPURE_PROMPT_SYMBOL` | `❯` | The prompt symbol. | +| `IMPURE_PROMPT_VICMD_SYMBOL` | `❮` | The prompt symbol in VI command mode. | +| `IMPURE_GIT_UP_ARROW` | `⇡` | Symbol for unpushed commits. | +| `IMPURE_GIT_DOWN_ARROW` | `⇣` | Symbol for unpulled commits. | +| `IMPURE_GIT_STASH_SYMBOL` | `≡` | Symbol shown when stashes exist. | +| `IMPURE_SUSPENDED_JOBS_SYMBOL` | `✦` | Symbol shown when jobs are suspended. | + +The prompt also reacts to these external variables when set: + +- `ZMX_SESSION` — renders a `[session]` tag in the preprompt. + +### zstyles + +All toggles default to **on** unless noted. Disable a feature by setting its +style to `no` before `prompt impure`: + +```sh +zstyle ':prompt:impure:git' show no # disable git integration +zstyle ':prompt:impure:jj' show no # disable jujutsu integration +zstyle ':prompt:impure:host' show no # hide hostname +zstyle ':prompt:impure:title' show no # stop managing the terminal title +zstyle ':prompt:impure:git:stash' show yes # show stash indicator +zstyle ':prompt:impure:git:fetch' only_upstream yes # only fetch the current branch's upstream +zstyle ':prompt:impure:environment:nix-shell' show no +zstyle ':prompt:impure:environment:virtualenv' show no +zstyle ':prompt:impure:path:separator' dim yes # dim the path separators +``` + +### Colors + +Override any segment color with the `color` style. Values are zsh color names or +256-color numbers: + +```sh +zstyle ':prompt:impure:path' color blue +zstyle ':prompt:impure:git:branch' color 242 +zstyle ':prompt:impure:prompt:success' color magenta +``` + +Available color keys: `path`, `git:branch`, +`git:branch:cached`, `git:dirty`, `git:action`, `git:arrow`, `git:stash`, `jj`, +`host`, `user`, `user:root`, `zmx`, `nix-shell`, `virtualenv`, +`execution_time`, `suspended_jobs`, `custom:prefix`, `custom:suffix`, +`prompt:success`, `prompt:error`, `prompt:ssh`, `prompt:continuation`. + +### Custom segments + +Define `prompt_impure_precustom` to add your own prefix/suffix segments. It runs +on every render and can set `psvar[22]` (prefix) and `psvar[23]` (suffix): + +```sh +prompt_impure_precustom() { + psvar[22]="$(date +%H:%M)" # rendered before the path + psvar[23]="" # rendered after the git/jj segments +} +``` + +## Preview + +Run `prompt_impure_preview` after loading the prompt to see every segment and +color rendered at once. + +## Tests + +```sh +zsh tests/test.zsh +``` + +Each test file runs in its own subshell; the harness exits non-zero if any fail. + +## Credits + +Impure is a fork of [Pure](https://github.com/sindresorhus/pure) by Sindre +Sorhus and Mathias Fredriksson, and bundles +[`zsh-async`](https://github.com/mafredri/zsh-async). All of the hard work on the +async core and the original prompt design is theirs; Impure adds the jj, nix, +zmx and staging-summary layers on top. + +

+ +

+ +

+ © 2026-present Kieran Klukas +

+ +

+ +

diff --git a/arch/PKGBUILD b/arch/PKGBUILD index 18cdc92..30dc588 100644 --- a/arch/PKGBUILD +++ b/arch/PKGBUILD @@ -1,26 +1,28 @@ -# Author: Sindre Sorhus -# Maintainer: Pat Brisbin -# Contributor: Emil Falk -pkgname=zsh-pure-prompt-git -pkgver=r61.7d3b317 +# Maintainer: Kieran Klukas +# Upstream: Sindre Sorhus (Pure) +pkgname=zsh-impure-prompt-git +pkgver=r1.0000000 pkgrel=1 -pkgdesc='A minimal and pure prompt for zsh.' +pkgdesc='A pretty, minimal and fast zsh prompt with Jujutsu, nix-shell, and zmx awareness.' arch=('any') -url='https://github.com/sindresorhus/pure' +url='https://github.com/taciturnaxolotl/impure' license=('MIT') depends=('zsh' 'git') -source=("$pkgname::git://github.com/sindresorhus/pure.git") +optdepends=('jujutsu: jj repository status in the prompt') +source=("$pkgname::git+https://github.com/taciturnaxolotl/impure.git") sha256sums=('SKIP') pkgver() { - cd $srcdir/$pkgname + cd "$srcdir/$pkgname" printf "r%s.%s" "$(git rev-list --count HEAD)" "$(git rev-parse --short HEAD)" } package() { - cd $srcdir/$pkgname - install -Dm644 pure.zsh \ - "$pkgdir/usr/share/zsh/functions/Prompts/prompt_pure_setup" + cd "$srcdir/$pkgname" + install -Dm644 impure.zsh \ + "$pkgdir/usr/share/zsh/functions/Prompts/prompt_impure_setup" install -Dm644 async.zsh \ "$pkgdir/usr/share/zsh/functions/async" + install -Dm644 LICENSE.md \ + "$pkgdir/usr/share/licenses/$pkgname/LICENSE.md" } diff --git a/impure.zsh b/impure.zsh index 0c212bd..9ba5723 100644 --- a/impure.zsh +++ b/impure.zsh @@ -29,14 +29,17 @@ # https://github.com/sindresorhus/pretty-time-zsh prompt_impure_human_time_to_var() { local total_seconds=$1 var=$2 - local formatted - if (( total_seconds < 60 )); then - formatted=$(printf '%.3fs' "$total_seconds") - else - local minutes=$(( total_seconds / 60 )) - local seconds=$(( total_seconds % 60 )) - formatted="${minutes}m ${seconds}s" - fi + local days=$(( total_seconds / 60 / 60 / 24 )) + local hours=$(( total_seconds / 60 / 60 % 24 )) + local minutes=$(( total_seconds / 60 % 60 )) + local seconds=$(( total_seconds % 60 )) + + local formatted= + (( days > 0 )) && formatted+="${days}d " + (( hours > 0 )) && formatted+="${hours}h " + (( minutes > 0 )) && formatted+="${minutes}m " + formatted+="${seconds}s" + typeset -g "${var}"="${formatted}" } @@ -126,11 +129,6 @@ prompt_impure_set_colors() { prompt_impure_set_path_separator() { local path_color=$prompt_impure_colors[path] - # Ephemeral shell: use red path to signal non-persistent environment. - if [[ -n ${ATELIER_EPHEMERAL:-} ]]; then - path_color=$prompt_impure_colors[path:ephemeral] - fi - typeset -g prompt_impure_path_segment="%F{${path_color}}%~%f" if zstyle -t ':prompt:impure:path:separator' dim; then @@ -156,9 +154,6 @@ prompt_impure_render_dimmed_path() { fi local path_color=$prompt_impure_colors[path] - if [[ -n ${ATELIER_EPHEMERAL:-} ]]; then - path_color=$prompt_impure_colors[path:ephemeral] - fi print -n -r -- "%F{${path_color}}${prefix}${current_path//\//$separator}%f" } @@ -236,7 +231,7 @@ prompt_impure_preprompt_render() { "${psvar[17]}" "${psvar[18]}" "${psvar[19]}" - # psvar[21] (exec time) excluded: changes every command, handled by precmd. + # psvar[20] (exec time) excluded: changes every command, handled by precmd. "${psvar[21]}" "${psvar[22]}" "${psvar[23]}" @@ -503,18 +498,25 @@ prompt_impure_async_git_stash() { command git rev-list --walk-reflogs --count refs/stash } +# Walk up from $PWD looking for a .jj directory. Returns 0 if inside a +# Jujutsu repo, 1 otherwise. Shared by the sync dispatch path and the +# async worker so the detection logic lives in exactly one place. +prompt_impure_in_jj_repo() { + local dir=$PWD + while [[ $dir != "/" ]]; do + [[ -d "$dir/.jj" ]] && return 0 + dir=${dir:h} + done + [[ -d "/.jj" ]] +} + # Async jujutsu status: get change ID and working set modifications. # Mirrors the git async pattern — runs in the background worker, never blocks. prompt_impure_async_jj_status() { setopt localoptions noshwordsplit - # Check if we're in a jj repo by looking for .jj directory. - local jj_dir=$PWD - while [[ $jj_dir != "/" ]]; do - [[ -d "$jj_dir/.jj" ]] && break - jj_dir=${jj_dir:h} - done - [[ -d "$jj_dir/.jj" ]] || return 1 + # Skip unless inside a jj repo. + prompt_impure_in_jj_repo || return 1 # Get bookmark and change ID in one call. local jj_info @@ -621,15 +623,21 @@ prompt_impure_async_init() { prompt_impure_async_tasks() { setopt localoptions noshwordsplit - # If inside a jj repo, skip git entirely (jj takes precedence). - local check_dir=$PWD - while [[ $check_dir != "/" ]]; do - if [[ -d "$check_dir/.jj" ]]; then - prompt_impure_clear_git_state - return + # If inside a jj repo, skip git entirely (jj takes precedence). Cache the + # result per directory so we only walk the path on an actual `cd`, keeping + # the synchronous precmd path off the filesystem in the common case. + if [[ $PWD != ${prompt_impure_jj_repo_check_pwd:-} ]]; then + typeset -g prompt_impure_jj_repo_check_pwd=$PWD + if prompt_impure_in_jj_repo; then + typeset -g prompt_impure_jj_repo_check=1 + else + typeset -g prompt_impure_jj_repo_check=0 fi - check_dir=${check_dir:h} - done + fi + if (( ${prompt_impure_jj_repo_check:-0} )); then + prompt_impure_clear_git_state + return + fi # Check if git integration is enabled (default: yes). if ! zstyle -T ":prompt:impure:git" show; then @@ -1264,8 +1272,7 @@ prompt_impure_setup() { host 242 jj 242 nix-shell red - path blue - path:ephemeral red + path blue prompt:error red prompt:success magenta prompt:ssh cyan @@ -1307,9 +1314,11 @@ prompt_impure_setup() { typeset -g prompt_impure_jj_changeid= typeset -g prompt_impure_jj_working= typeset -g prompt_impure_jj_pwd= + typeset -g prompt_impure_jj_repo_check_pwd= + typeset -g prompt_impure_jj_repo_check=0 # Construct PROMPT once, both preprompt and prompt line. Kept - # dynamic via variables and psvar[12-25], updated each render + # dynamic via variables and psvar[12-27], updated each render # in prompt_impure_preprompt_render. Numbering starts at 12 for # legacy reasons (Pure originally used psvar[12] for virtualenv) # and to avoid collisions with low psvar indices which users @@ -1318,11 +1327,12 @@ prompt_impure_setup() { # psvar[12] = suspended jobs symbol (e.g. ✦) # psvar[13] = username flag, renders user/host (e.g. user@host) # psvar[14] = git branch - # psvar[15] = git dirty marker, nested inside [14] conditional - # psvar[16] = git action (e.g. rebase, merge) - # psvar[17] = git arrows (e.g. ⇣⇡) - # psvar[18] = git stash symbol (e.g. ≡) - # psvar[19] = exec time (e.g. 1d 3h 2m 5s) — shown in RPROMPT + # psvar[15] = git staging summary (e.g. "+2 ~1"), shown in (parens) + # psvar[16] = git working tree dirty marker ("*") + # psvar[17] = git action (e.g. rebase, merge) + # psvar[18] = git arrows (e.g. ⇣⇡), with leading space + # psvar[19] = git stash symbol (e.g. ≡) + # psvar[20] = exec time (e.g. 1d 3h 2m 5s) — shown in RPROMPT # psvar[21] = virtualenv/nix-shell name — shown in RPROMPT # psvar[22] = custom prefix (set by prompt_impure_precustom) # psvar[23] = custom suffix (set by prompt_impure_precustom) @@ -1332,8 +1342,8 @@ prompt_impure_setup() { # psvar[27] = Jujutsu working set changes # # Example output: - # hostname [zmx] ~/Code/impure main* rebase ⇣⇡ ≡ ⬢22 3s suffix - # @abc1234 modified ❯ + # hostname [zmx] ~/Code/impure main (+2 ~1)* rebase ⇣⇡ ≡ suffix + # 42s pure ❯ # # Preprompt line: each %(NV..) section only renders when its psvar is non-empty. PROMPT='${prompt_newline}' diff --git a/package.json b/package.json deleted file mode 100644 index b3967c0..0000000 --- a/package.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "name": "pure-prompt", - "version": "1.28.1", - "description": "Pretty, minimal and fast ZSH prompt", - "license": "MIT", - "repository": "sindresorhus/pure", - "funding": "https://github.com/sponsors/sindresorhus", - "author": { - "name": "Sindre Sorhus", - "email": "sindresorhus@gmail.com", - "url": "https://sindresorhus.com" - }, - "maintainers": [ - { - "name": "Mathias Fredriksson", - "url": "https://github.com/mafredri" - } - ], - "scripts": { - "prepack": "rm -f prompt_pure_setup async && cp pure.zsh prompt_pure_setup && cp async.zsh async", - "postpack": "git checkout -- prompt_pure_setup async", - "postinstall": "if [ -e /opt/homebrew/bin/zsh ]; then PURE_DEST=/opt/homebrew/share/zsh/site-functions npm run --silent postinstall-link && exit 0; elif [ -e /usr/local/bin/zsh ]; then PURE_DEST=/usr/local/share/zsh/site-functions npm run --silent postinstall-link && exit 0; elif [ -e /bin/zsh ] || [ -e /usr/bin/zsh ]; then for dest in /usr/share/zsh/site-functions /usr/local/share/zsh/site-functions; do if [ -d $dest ]; then PURE_DEST=$dest npm run --silent postinstall-link && exit 0; fi; done; fi; npm run --silent postinstall-fail-instructions", - "postinstall-link": "mkdir -p \"$PURE_DEST\" && ln -sf \"$PWD/pure.zsh\" \"$PURE_DEST/prompt_pure_setup\" && ln -sf \"$PWD/async.zsh\" \"$PURE_DEST/async\"", - "postinstall-fail-instructions": "echo \"\\nERROR: Could not automagically symlink the prompt. Add the following to your \\`.zshrc\\`:\\n\\n fpath+=(\\\"\\$(npm root -g)/pure-prompt\\\")\\n\\nOr check out the readme: https://github.com/sindresorhus/pure#npm\"", - "test": "zsh tests/test.zsh", - "version": "sed -i '' -e 's/prompt_pure_state\\[version\\]=.*/prompt_pure_state[version]=\"'\"$npm_package_version\"'\"/' pure.zsh && git add pure.zsh" - }, - "files": [ - "pure.zsh", - "async.zsh", - "prompt_pure_setup", - "async" - ], - "keywords": [ - "zsh", - "zshell", - "sh", - "shell", - "bash", - "pure", - "prompt", - "theme", - "git", - "async", - "fast", - "minimal", - "pretty" - ] -} diff --git a/readme.md b/readme.md deleted file mode 100644 index 9e29d53..0000000 --- a/readme.md +++ /dev/null @@ -1,355 +0,0 @@ -# Pure - -> Pretty, minimal and fast ZSH prompt - - - -## Overview - -Most prompts are cluttered, ugly and slow. We wanted something visually pleasing that stayed out of our way. - -### Why? - -- Comes with the perfect prompt character. - Author went through the whole Unicode range to find it. -- Shows `git` branch and whether it's dirty (with a `*`). -- Indicates when you have unpushed/unpulled `git` commits with up/down arrows. *(Check is done asynchronously!)* -- Prompt character turns red if the last command didn't exit with `0`. -- Command execution time will be displayed if it exceeds the set threshold. -- Username and host only displayed when in an SSH session or a container. -- Shows the current path in the title and the [current folder & command](screenshot-title-cmd.png) when a process is running. -- Support VI-mode indication by reverse prompt symbol (Zsh 5.3+). -- Makes an excellent starting point for your own custom prompt. - -## Install - -Can be installed with `npm` (not `yarn`) or manually. Requires Git 2.15.2+ and ZSH 5.2+. Older versions of ZSH are known to work, but they are **not** recommended. - -### npm - -```sh -npm install --global pure-prompt -``` - -That's it. Skip to [Getting started](#getting-started). - -If `prompt -l` does not list `pure`, add this to your `.zshrc` before `promptinit`: - -```sh -fpath+=("$(npm root -g)/pure-prompt") -``` - -### [Homebrew](https://brew.sh) - -```sh -brew install pure -``` - -If you're not using ZSH from Homebrew (`brew install zsh` and `$(brew --prefix)/bin/zsh`), you must also add the site-functions to your `fpath` in `$HOME/.zshrc`: - -```sh -fpath+=("$(brew --prefix)/share/zsh/site-functions") -``` - -### Manually - -1. Clone this repo somewhere. Here we'll use `$HOME/.zsh/pure`. - -```sh -mkdir -p "$HOME/.zsh" -git clone https://github.com/sindresorhus/pure.git "$HOME/.zsh/pure" -``` - -2. Add the path of the cloned repo to `$fpath` in `$HOME/.zshrc`. -```sh -# .zshrc -fpath+=($HOME/.zsh/pure) -``` - -## Getting started - -Initialize the prompt system (if not so already) and choose `pure`: - -```sh -# .zshrc -autoload -U promptinit; promptinit -prompt pure -``` - -## Options - -| Option | Description | Default value | -| :------------------------------- | :--------------------------------------------------------------------------------------------- | :------------- | -| **`PURE_CMD_MAX_EXEC_TIME`** | The max execution time of a process before its run time is shown when it exits. | `5` seconds | -| **`PURE_GIT_PULL`** | Prevents Pure from checking whether the current Git remote has been updated. | `1` | -| **`PURE_GIT_UNTRACKED_DIRTY`** | Do not include untracked files in dirtiness check. Mostly useful on large repos (like WebKit). | `1` | -| **`PURE_GIT_DELAY_DIRTY_CHECK`** | Time in seconds to delay git dirty checking when `git status` takes > 5 seconds. | `1800` seconds | -| **`PURE_PROMPT_SYMBOL`** | Defines the prompt symbol. | `❯` | -| **`PURE_PROMPT_VICMD_SYMBOL`** | Defines the prompt symbol used when the `vicmd` keymap is active (VI-mode). | `❮` | -| **`PURE_SUSPENDED_JOBS_SYMBOL`** | Defines the symbol that indicates that jobs are running in the background. Set to empty to disable. | `✦` | -| **`PURE_GIT_DOWN_ARROW`** | Defines the git down arrow symbol. | `⇣` | -| **`PURE_GIT_UP_ARROW`** | Defines the git up arrow symbol. | `⇡` | -| **`PURE_GIT_STASH_SYMBOL`** | Defines the git stash symbol. Set to empty to disable. | `≡` | - -## Zstyle options - -Showing git stash status as part of the prompt is not activated by default. To activate this you'll need to opt in via `zstyle`: - -`zstyle :prompt:pure:git:stash show yes` - -You can set Pure to only `git fetch` the upstream branch of the current local branch. In some cases, this can result in faster updates for Git arrows, but for most users, it's better to leave this setting disabled. You can enable it with: - -`zstyle :prompt:pure:git:fetch only_upstream yes` - -Node.js version display shows the current major `node` version when inside a directory tree containing a `package.json`. It is not enabled by default. You can enable it with: - -`zstyle :prompt:pure:environment:node_version show yes` - -You can change the symbol shown before the Node.js version (default `⬢`) with: - -`zstyle :prompt:pure:environment:node_version symbol ⬡` - -`nix-shell` integration adds the shell name to the prompt when used from within a nix shell. It is enabled by default, you can disable it with: - -`zstyle :prompt:pure:environment:nix-shell show no` - -Virtualenv integration shows the current Python virtualenv or Conda environment name. It is enabled by default, you can disable it with: - -`zstyle :prompt:pure:environment:virtualenv show no` - -Git integration is enabled by default, you can disable it with: - -`zstyle :prompt:pure:git show no` - -Detailed dirty indicators differentiate between unstaged (`*`), staged (`+`), and untracked (`?`) changes instead of showing a single `*`. It is not enabled by default. You can enable it with: - -`zstyle :prompt:pure:git:dirty detailed yes` - -Path separator dimming makes `/` characters in the path visually dimmer to help distinguish path components. It is not enabled by default. You can enable it with: - -`zstyle :prompt:pure:path:separator dim yes` - -Hostname display is enabled by default when in an SSH session or container. You can disable it while still showing the username with: - -`zstyle :prompt:pure:host show no` - -Automatic terminal title management can be disabled if you want to set your own tab or window titles: - -`zstyle :prompt:pure:title show no` - -## Customization - -Pure supports a hook function for adding custom content to the prompt. Define a function called `prompt_pure_precustom` in your `.zshrc` (after `prompt pure`) to set custom prefix and suffix segments on the preprompt line: - -- `psvar[22]` — Custom prefix, rendered before all built-in segments. -- `psvar[23]` — Custom suffix, rendered after all built-in segments. - -The function is called each time the prompt renders (including async redraws), so avoid slow commands. For expensive operations, cache the result in a `precmd` hook and read the cached value in `prompt_pure_precustom`. - -```sh -# .zshrc - -# Cache expensive calls in precmd (runs once per command, not on async redraws). -my_kube_context= -my_precmd() { - my_kube_context=$(kubectl config current-context 2>/dev/null) -} -add-zsh-hook precmd my_precmd - -prompt_pure_precustom() { - psvar[22]=$my_kube_context - psvar[23]=$(date +%H:%M) -} -``` - -The colors can be customized with `zstyle`: - -```sh -zstyle :prompt:pure:custom:prefix color cyan -zstyle :prompt:pure:custom:suffix color yellow -``` - -## Colors - -As explained in ZSH's [manual](http://zsh.sourceforge.net/Doc/Release/Zsh-Line-Editor.html#Character-Highlighting), color values can be: -- A decimal integer corresponding to the color index of your terminal. If your `$TERM` is `xterm-256color`, see this [chart](https://upload.wikimedia.org/wikipedia/commons/1/15/Xterm_256color_chart.svg). -- The name of one of the following nine colors: `black`, `red`, `green`, `yellow`, `blue`, `magenta`, `cyan`, `white`, and `default` (the terminal’s default foreground) -- `#` followed by an RGB triplet in hexadecimal format, for example `#424242`. Only if your terminal supports 24-bit colors (true color) or when the [`zsh/nearcolor` module](http://zsh.sourceforge.net/Doc/Release/Zsh-Modules.html#The-zsh_002fnearcolor-Module) is loaded. - -Colors can be changed by using [`zstyle`](http://zsh.sourceforge.net/Doc/Release/Zsh-Modules.html#The-zsh_002fzutil-Module) with a pattern of the form `:prompt:pure:$color_name` and style `color`. The color names, their default, and what part they affect are: -- `custom:prefix` (242) - Custom prefix set via `prompt_pure_precustom`. -- `custom:suffix` (242) - Custom suffix set via `prompt_pure_precustom`. -- `execution_time` (yellow) - The execution time of the last command when exceeding `PURE_CMD_MAX_EXEC_TIME`. -- `git:arrow` (cyan) - For `PURE_GIT_UP_ARROW` and `PURE_GIT_DOWN_ARROW`. -- `git:stash` (cyan) - For `PURE_GIT_STASH_SYMBOL`. -- `git:branch` (242) - The name of the current branch when in a Git repository. -- `git:branch:cached` (red) - The name of the current branch when the data isn't fresh. -- `git:action` (yellow) - The current action in progress (cherry-pick, rebase, etc.) when in a Git repository. -- `git:dirty` (218) - The asterisk showing the branch is dirty. -- `host` (242) - The hostname when on a remote machine. -- `node_version` (green) - The current major Node.js version in directories with `package.json`. -- `path` (blue) - The current path, for example, `PWD`. -- `prompt:error` (red) - The `PURE_PROMPT_SYMBOL` when the previous command has *failed*. -- `prompt:success` (magenta) - The `PURE_PROMPT_SYMBOL` when the previous command has *succeeded*. -- `prompt:continuation` (242) - The color for showing the state of the parser in the continuation prompt (PS2). It's the pink part in [this screenshot](https://user-images.githubusercontent.com/147409/70068574-ebc74800-15f8-11ea-84c0-8b94a4b57ff4.png), it appears in the same spot as `virtualenv`. You could for example matching both colors so that Pure has a uniform look. -- `suspended_jobs` (red) - The `PURE_SUSPENDED_JOBS_SYMBOL`. -- `user` (242) - The username when on remote machine. -- `user:root` (default) - The username when the user is root. -- `virtualenv` (242) - The name of the Python `virtualenv` when in use. - -The following diagram shows where each color is applied on the prompt: - -``` -┌──────────────────────────────────────────────────────────────────────── custom:prefix -│ ┌─────────────────────────────────────────────────────────────────── suspended_jobs -│ │ ┌───────────────────────────────────────────────────────────────── user -│ │ │ ┌────────────────────────────────────────────────────────── host -│ │ │ │ ┌────────────────────────────────────────────── path -│ │ │ │ │ ┌─────────────────────────────────── git:branch -│ │ │ │ │ │ ┌────────────────────────────── git:dirty -│ │ │ │ │ │ │ ┌──────────────────────────── git:action -│ │ │ │ │ │ │ │ ┌─────────────────── git:arrow -│ │ │ │ │ │ │ │ │ ┌───────────────── git:stash -│ │ │ │ │ │ │ │ │ │ ┌─────────────── node_version -│ │ │ │ │ │ │ │ │ │ │ ┌─────────── execution_time -│ │ │ │ │ │ │ │ │ │ │ │ ┌────── custom:suffix -│ │ │ │ │ │ │ │ │ │ │ │ │ -prefix ✦ zaphod@heartofgold ~/dev/pure main* rebase-i ⇡ ≡ ⬢22 42s suffix -venv ❯ -│ │ -│ └───────────────────────────────────────────────────────────────────── prompt -└────────────────────────────────────────────────────────────────────────── virtualenv (or prompt:continuation) -``` - -### Preview - -Run `prompt_pure_preview` to display a sample prompt with all components rendered in their current colors. This is useful for testing color customizations. - -### RGB colors - -There are two ways to use RGB colors with the hexadecimal format. The correct way is to use a [terminal that support 24-bit colors](https://gist.github.com/XVilka/8346728) and enable this feature as explained in the terminal's documentation. - -If you can't use such terminal, the module [`zsh/nearcolor`](http://zsh.sourceforge.net/Doc/Release/Zsh-Modules.html#The-zsh_002fnearcolor-Module) can be useful. It will map any hexadecimal color to the nearest color in the 88 or 256 color palettes of your terminal, but without using the first 16 colors, since their values can be modified by the user. Keep in mind that when using this module you won't be able to display true RGB colors. It only allows you to specify colors in a more convenient way. The following is an example on how to use this module: - -```sh -# .zshrc -zmodload zsh/nearcolor -zstyle :prompt:pure:path color '#FF0000' -``` - -## Example - -```sh -# .zshrc - -autoload -U promptinit; promptinit - -# optionally define some options -PURE_CMD_MAX_EXEC_TIME=10 - -# change the path color -zstyle :prompt:pure:path color white - -# change the color for both `prompt:success` and `prompt:error` -zstyle ':prompt:pure:prompt:*' color cyan - -# turn on git stash status -zstyle :prompt:pure:git:stash show yes - -prompt pure -``` - -## Tips - -In the screenshot you see Pure running in [Hyper](https://hyper.is) with the [hyper-snazzy](https://github.com/sindresorhus/hyper-snazzy) theme and Menlo font. - -The [Tomorrow Night Eighties](https://github.com/chriskempson/tomorrow-theme) theme with the [Droid Sans Mono](https://www.fontsquirrel.com/fonts/droid-sans-mono) font (15pt) is also a [nice combination](https://github.com/sindresorhus/pure/blob/95ee3e7618c6e2162a1e3cdac2a88a20ac3beb27/screenshot.png).
-*Just make sure you have anti-aliasing enabled in your terminal.* - -To have commands colorized as seen in the screenshot, install [zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting). - -## Integration - -### [oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh) - -1. Set `ZSH_THEME=""` in your `.zshrc` to disable oh-my-zsh themes. -2. Follow the Pure [Install](#install) instructions. -3. Do not enable the following (incompatible) plugins: `vi-mode`, `virtualenv`. - -**NOTE:** `oh-my-zsh` overrides the prompt so Pure must be activated *after* `source $ZSH/oh-my-zsh.sh`. - -### [prezto](https://github.com/sorin-ionescu/prezto) - -Pure is bundled with Prezto. No need to install it. - -Add `prompt pure` to your `~/.zpreztorc`. - -### [zim](https://github.com/Eriner/zim) - -Add `zmodule sindresorhus/pure --source async.zsh --source pure.zsh` to your `.zimrc` and run `zimfw install`. - -### [zplug](https://github.com/zplug/zplug) - -Update your `.zshrc` file with the following two lines: - -```sh -zplug mafredri/zsh-async, from:github -zplug sindresorhus/pure, use:pure.zsh, from:github, as:theme -``` - -### [zinit](https://github.com/zdharma-continuum/zinit) - -Update your `.zshrc` file with the following two lines (order matters): - -```sh -zinit ice compile'(pure|async).zsh' pick'async.zsh' src'pure.zsh' -zinit light sindresorhus/pure -``` - -### [zi](https://wiki.zshell.dev) - -Update your `.zshrc` file with the following line: - -```sh -zi light-mode for @sindresorhus/pure -``` - -See the [ZI wiki](https://wiki.zshell.dev/community/gallery/collection/themes#thp-sindresorhuspure) for more. - -## FAQ - -### Prompt is corrupted when using tab completion - -If the prompt displays incorrectly during tab completion (ghost characters, misaligned cursor), your shell is likely missing a UTF-8 locale. Pure uses Unicode characters (like `❯`) that require UTF-8 to calculate display width correctly. Add this to your `.zshrc`: - -```sh -export LANG=en_US.UTF-8 -``` - -See [FAQ Archive](https://github.com/sindresorhus/pure/wiki/FAQ-Archive) for previous FAQs. - -## Ports - -- **ZSH** - - [therealklanni/purity](https://github.com/therealklanni/purity) - More compact current working directory, important details on the main prompt line, and extra Git indicators. - - [intelfx/pure](https://github.com/intelfx/pure) - Solarized-friendly colors, highly verbose, and fully async Git integration. - - [forivall/pure](https://github.com/forivall/pure) - A minimal fork which highlights the Git repo's root directory in the path. - - [dfurnes/purer](https://github.com/dfurnes/purer) - Compact single-line prompt with built-in Vim-mode indicator. - - [chabou/pure-now](https://github.com/chabou/pure-now) - Fork with [Now](https://zeit.co/now) support. - - [pure10k](https://gist.github.com/romkatv/7cbab80dcbc639003066bb68b9ae0bbf) - Configuration file for [Powerlevel10k](https://github.com/romkatv/powerlevel10k/) that makes it look like Pure. -- **Bash** - - [sapegin/dotfiles](https://github.com/sapegin/dotfiles) - [Prompt](https://github.com/sapegin/dotfiles/blob/dd063f9c30de7d2234e8accdb5272a5cc0a3388b/includes/bash_prompt.bash) and [color theme](https://github.com/sapegin/dotfiles/tree/master/color) for Terminal.app. -- **Fish** - - [pure-fish/pure](https://github.com/pure-fish/pure) - Fully tested Fish port aiming for feature parity. -- **Rust** - - [xcambar/purs](https://github.com/xcambar/purs) - Pure-inspired prompt in Rust. -- **Go** - - [talal/mimir](https://github.com/talal/mimir) - Pure-inspired prompt in Go with Kubernetes and OpenStack cloud support. Not intended to have feature parity. -- **PowerShell** - - [nickcox/pure-pwsh](https://github.com/nickcox/pure-pwsh/) - PowerShell/PS Core implementation of the Pure prompt. - -## Team - -[![Sindre Sorhus](https://github.com/sindresorhus.png?size=100)](https://sindresorhus.com) | [![Mathias Fredriksson](https://github.com/mafredri.png?size=100)](https://github.com/mafredri) ----|--- -[Sindre Sorhus](https://github.com/sindresorhus) | [Mathias Fredriksson](https://github.com/mafredri) diff --git a/tests/async-worker-failure.zsh b/tests/async-worker-failure.zsh index 7f4777a..17e9462 100644 --- a/tests/async-worker-failure.zsh +++ b/tests/async-worker-failure.zsh @@ -26,29 +26,29 @@ main() { assert_git_state_empty() { local message=$1 - assert_empty "${prompt_pure_vcs_info[branch]-}" "branch should be cleared $message" || return - assert_empty "${prompt_pure_vcs_info[top]-}" "top-level should be cleared $message" || return - assert_empty "${prompt_pure_vcs_info[action]-}" "action should be cleared $message" || return - assert_empty "${prompt_pure_vcs_info[pwd]-}" "pwd should be cleared $message" || return - assert_empty "${prompt_pure_git_dirty-}" "dirty marker should be cleared $message" || return - assert_empty "${prompt_pure_git_last_dirty_check_timestamp-}" "cached dirty timestamp should be cleared $message" || return - assert_empty "${prompt_pure_git_arrows-}" "arrows should be cleared $message" || return - assert_empty "${prompt_pure_git_stash-}" "stash should be cleared $message" || return - assert_empty "${prompt_pure_git_fetch_pattern-}" "fetch pattern should be cleared $message" || return + assert_empty "${prompt_impure_vcs_info[branch]-}" "branch should be cleared $message" || return + assert_empty "${prompt_impure_vcs_info[top]-}" "top-level should be cleared $message" || return + assert_empty "${prompt_impure_vcs_info[action]-}" "action should be cleared $message" || return + assert_empty "${prompt_impure_vcs_info[pwd]-}" "pwd should be cleared $message" || return + assert_empty "${prompt_impure_git_dirty-}" "dirty marker should be cleared $message" || return + assert_empty "${prompt_impure_git_last_dirty_check_timestamp-}" "cached dirty timestamp should be cleared $message" || return + assert_empty "${prompt_impure_git_arrows-}" "arrows should be cleared $message" || return + assert_empty "${prompt_impure_git_stash-}" "stash should be cleared $message" || return + assert_empty "${prompt_impure_git_fetch_pattern-}" "fetch pattern should be cleared $message" || return } test_no_infinite_recursion_on_worker_failure() { # Simulate async_start_worker always failing (e.g. zpty permission denied). async_start_worker() { return 1 } - typeset -g prompt_pure_async_inited=0 + typeset -g prompt_impure_async_inited=0 # This should return 1 (failure), not infinitely recurse. local ret=0 - prompt_pure_async_init || ret=$? + prompt_impure_async_init || ret=$? - assert_equal 1 $ret "prompt_pure_async_init should return 1 when worker fails to start" || return - assert_equal 0 $prompt_pure_async_inited "prompt_pure_async_inited should be reset to 0 on failure" || return + assert_equal 1 $ret "prompt_impure_async_init should return 1 when worker fails to start" || return + assert_equal 0 $prompt_impure_async_inited "prompt_impure_async_inited should be reset to 0 on failure" || return unfunction async_start_worker } @@ -59,15 +59,15 @@ test_worker_startup_failure_clears_git_state() { return 1 } - typeset -gA prompt_pure_vcs_info=(branch main top /tmp/repo action rebase pwd /tmp/repo) - typeset -g prompt_pure_git_dirty="*" - typeset -g prompt_pure_git_last_dirty_check_timestamp=1 - typeset -g prompt_pure_git_arrows="⇡" - typeset -g prompt_pure_git_stash=1 - typeset -g prompt_pure_git_fetch_pattern="pull|fetch" - typeset -g prompt_pure_async_inited=0 + typeset -gA prompt_impure_vcs_info=(branch main top /tmp/repo action rebase pwd /tmp/repo) + typeset -g prompt_impure_git_dirty="*" + typeset -g prompt_impure_git_last_dirty_check_timestamp=1 + typeset -g prompt_impure_git_arrows="⇡" + typeset -g prompt_impure_git_stash=1 + typeset -g prompt_impure_git_fetch_pattern="pull|fetch" + typeset -g prompt_impure_async_inited=0 - prompt_pure_async_tasks || : + prompt_impure_async_tasks || : assert_git_state_empty "when worker fails to start" || return @@ -89,19 +89,19 @@ test_worker_sync_clears_stale_git_state_before_returning() { return 1 } - typeset -g prompt_pure_async_inited=1 - typeset -gA prompt_pure_worker_env=(pwd /tmp/repo git_dir __unset__ git_work_tree __unset__) - typeset -gA prompt_pure_worker_env_pending=() - typeset -gA prompt_pure_vcs_info=(branch main top /tmp/repo action rebase pwd /tmp/repo) - typeset -g prompt_pure_git_dirty="*" - typeset -g prompt_pure_git_last_dirty_check_timestamp=1 - typeset -g prompt_pure_git_arrows="⇡" - typeset -g prompt_pure_git_stash=1 - typeset -g prompt_pure_git_fetch_pattern="pull|fetch" + typeset -g prompt_impure_async_inited=1 + typeset -gA prompt_impure_worker_env=(pwd /tmp/repo git_dir __unset__ git_work_tree __unset__) + typeset -gA prompt_impure_worker_env_pending=() + typeset -gA prompt_impure_vcs_info=(branch main top /tmp/repo action rebase pwd /tmp/repo) + typeset -g prompt_impure_git_dirty="*" + typeset -g prompt_impure_git_last_dirty_check_timestamp=1 + typeset -g prompt_impure_git_arrows="⇡" + typeset -g prompt_impure_git_stash=1 + typeset -g prompt_impure_git_fetch_pattern="pull|fetch" builtin cd -q "$tmpdir" - prompt_pure_async_tasks || : + prompt_impure_async_tasks || : builtin cd -q "$saved_pwd" zf_rm -rf -- "$saved_pwd/.ai-temporary" @@ -133,41 +133,41 @@ test_worker_sync_cd_failure_is_not_cached() { git_job_called=1 return 1 } - prompt_pure_preprompt_render() { + prompt_impure_preprompt_render() { render_called=1 } - typeset -g prompt_pure_async_inited=1 - typeset -gA prompt_pure_worker_env=() - typeset -gA prompt_pure_worker_env_pending=() + typeset -g prompt_impure_async_inited=1 + typeset -gA prompt_impure_worker_env=() + typeset -gA prompt_impure_worker_env_pending=() builtin cd -q "$tmpdir" zf_rm -rf -- "$tmpdir" - prompt_pure_async_tasks || : + prompt_impure_async_tasks || : builtin cd -q "$saved_pwd" zf_rm -rf -- "$saved_pwd/.ai-temporary" - assert_empty "${prompt_pure_worker_env[pwd]-}" "worker sync should not be cached before eval callback" || return + assert_empty "${prompt_impure_worker_env[pwd]-}" "worker sync should not be cached before eval callback" || return assert_equal 1 $async_flush_jobs_called "old git jobs should be flushed before worker sync" || return assert_equal 0 $git_job_called "git jobs should wait for worker sync callback" || return local sync_output sync_output=$("${(@)worker_eval_command}" 2>&1) builtin cd -q "$saved_pwd" - prompt_pure_async_callback '[async/eval]' 0 "$sync_output" 0 "" 1 + prompt_impure_async_callback '[async/eval]' 0 "$sync_output" 0 "" 1 - assert_empty "${prompt_pure_worker_env[pwd]-}" "failed worker cd should not be cached as synced" || return + assert_empty "${prompt_impure_worker_env[pwd]-}" "failed worker cd should not be cached as synced" || return assert_equal 0 $git_job_called "git jobs should not run when worker cd fails" || return assert_equal 1 $render_called "failed worker sync should render even when stale results are pending" || return - prompt_pure_async_callback prompt_pure_async_vcs_info 0 "pwd ${(q)PWD} branch stale top /tmp/repo action rebase" 0 "" 0 + prompt_impure_async_callback prompt_impure_async_vcs_info 0 "pwd ${(q)PWD} branch stale top /tmp/repo action rebase" 0 "" 0 - assert_empty "${prompt_pure_vcs_info[branch]-}" "stale vcs info should not repopulate branch after failed worker sync" || return - assert_empty "${prompt_pure_vcs_info[top]-}" "stale vcs info should not repopulate top-level after failed worker sync" || return + assert_empty "${prompt_impure_vcs_info[branch]-}" "stale vcs info should not repopulate branch after failed worker sync" || return + assert_empty "${prompt_impure_vcs_info[top]-}" "stale vcs info should not repopulate top-level after failed worker sync" || return - prompt_pure_preprompt_render() { + prompt_impure_preprompt_render() { : } unfunction async_worker_eval async_flush_jobs async_job @@ -192,27 +192,27 @@ test_worker_sync_success_queues_git_job() { return 0 } - typeset -g prompt_pure_async_inited=1 - typeset -gA prompt_pure_worker_env=() - typeset -gA prompt_pure_worker_env_pending=() - typeset -gA prompt_pure_vcs_info=(branch "" top "" action "" pwd "") + typeset -g prompt_impure_async_inited=1 + typeset -gA prompt_impure_worker_env=() + typeset -gA prompt_impure_worker_env_pending=() + typeset -gA prompt_impure_vcs_info=(branch "" top "" action "" pwd "") - prompt_pure_async_tasks || : + prompt_impure_async_tasks || : - assert_empty "${prompt_pure_worker_env[pwd]-}" "worker sync should not be cached before eval callback" || return + assert_empty "${prompt_impure_worker_env[pwd]-}" "worker sync should not be cached before eval callback" || return assert_equal 1 $async_flush_jobs_called "old git jobs should be flushed before worker sync" || return assert_equal 0 $git_job_called "git jobs should wait for worker sync callback" || return - prompt_pure_async_callback '[async/eval]' 0 "renice output" 0 "" 0 + prompt_impure_async_callback '[async/eval]' 0 "renice output" 0 "" 0 - assert_empty "${prompt_pure_worker_env[pwd]-}" "unrelated eval callback should not cache worker sync" || return + assert_empty "${prompt_impure_worker_env[pwd]-}" "unrelated eval callback should not cache worker sync" || return assert_equal 0 $git_job_called "unrelated eval callback should not queue git jobs" || return local sync_output sync_output=$("${(@)worker_eval_command}" 2>&1) - prompt_pure_async_callback '[async/eval]' 0 "$sync_output" 0 "" 0 + prompt_impure_async_callback '[async/eval]' 0 "$sync_output" 0 "" 0 - assert_equal "$PWD" "${prompt_pure_worker_env[pwd]-}" "successful worker sync should be cached" || return + assert_equal "$PWD" "${prompt_impure_worker_env[pwd]-}" "successful worker sync should be cached" || return assert_equal 1 $git_job_called "git jobs should run after worker sync succeeds" || return unfunction async_worker_eval async_flush_jobs async_job @@ -231,25 +231,25 @@ test_worker_sync_eval_failure_clears_pending_state() { return 0 } - typeset -g prompt_pure_async_inited=1 - typeset -gA prompt_pure_worker_env=() - typeset -gA prompt_pure_worker_env_pending=() + typeset -g prompt_impure_async_inited=1 + typeset -gA prompt_impure_worker_env=() + typeset -gA prompt_impure_worker_env_pending=() - prompt_pure_async_tasks || : + prompt_impure_async_tasks || : - assert_equal "$PWD" "${prompt_pure_worker_env_pending[pwd]-}" "worker sync should be pending before eval failure" || return + assert_equal "$PWD" "${prompt_impure_worker_env_pending[pwd]-}" "worker sync should be pending before eval failure" || return - prompt_pure_async_callback '[async/eval]' 1 "" 0 "" 0 + prompt_impure_async_callback '[async/eval]' 1 "" 0 "" 0 assert_equal 1 $async_worker_eval_called "non-marker eval failure should not immediately retry worker sync" || return - assert_empty "${prompt_pure_worker_env_pending[pwd]-}" "non-marker eval failure should clear pending sync state" || return + assert_empty "${prompt_impure_worker_env_pending[pwd]-}" "non-marker eval failure should clear pending sync state" || return unfunction async_worker_eval async_flush_jobs async_job } test_worker_sync_records_pending_before_eval() { async_worker_eval() { - prompt_pure_async_callback '[async/eval]' 0 "prompt_pure_worker_sync:${prompt_pure_worker_env_pending[token]}:0" 0 "" 0 + prompt_impure_async_callback '[async/eval]' 0 "prompt_impure_worker_sync:${prompt_impure_worker_env_pending[token]}:0" 0 "" 0 return 0 } async_flush_jobs() { @@ -261,15 +261,15 @@ test_worker_sync_records_pending_before_eval() { return 0 } - typeset -g prompt_pure_async_inited=1 - typeset -gA prompt_pure_worker_env=() - typeset -gA prompt_pure_worker_env_pending=() - typeset -gA prompt_pure_vcs_info=(branch "" top "" action "" pwd "") + typeset -g prompt_impure_async_inited=1 + typeset -gA prompt_impure_worker_env=() + typeset -gA prompt_impure_worker_env_pending=() + typeset -gA prompt_impure_vcs_info=(branch "" top "" action "" pwd "") - prompt_pure_async_tasks || : + prompt_impure_async_tasks || : - assert_empty "${prompt_pure_worker_env_pending[pwd]-}" "sync callback should not leave pending state behind" || return - assert_equal "$PWD" "${prompt_pure_worker_env[pwd]-}" "sync callback should cache worker sync even if it arrives during async_worker_eval" || return + assert_empty "${prompt_impure_worker_env_pending[pwd]-}" "sync callback should not leave pending state behind" || return + assert_equal "$PWD" "${prompt_impure_worker_env[pwd]-}" "sync callback should cache worker sync even if it arrives during async_worker_eval" || return assert_equal 1 $git_job_called "git jobs should run after immediate worker sync callback" || return unfunction async_worker_eval async_flush_jobs async_job @@ -277,7 +277,7 @@ test_worker_sync_records_pending_before_eval() { test_worker_sync_enqueue_failure_preserves_new_pending_state() { async_worker_eval() { - typeset -gA prompt_pure_worker_env_pending=(pwd "$PWD" git_dir __unset__ git_work_tree __unset__ token 999) + typeset -gA prompt_impure_worker_env_pending=(pwd "$PWD" git_dir __unset__ git_work_tree __unset__ token 999) return 1 } async_flush_jobs() { @@ -287,13 +287,13 @@ test_worker_sync_enqueue_failure_preserves_new_pending_state() { return 0 } - typeset -g prompt_pure_async_inited=1 - typeset -gA prompt_pure_worker_env=() - typeset -gA prompt_pure_worker_env_pending=() + typeset -g prompt_impure_async_inited=1 + typeset -gA prompt_impure_worker_env=() + typeset -gA prompt_impure_worker_env_pending=() - prompt_pure_async_tasks || : + prompt_impure_async_tasks || : - assert_equal 999 "${prompt_pure_worker_env_pending[token]-}" "enqueue failure should not clear newer pending sync state" || return + assert_equal 999 "${prompt_impure_worker_env_pending[token]-}" "enqueue failure should not clear newer pending sync state" || return unfunction async_worker_eval async_flush_jobs async_job } @@ -336,7 +336,7 @@ test_real_worker_sync_reports_encoded_status() { typeset -gA ASYNC_CALLBACKS=() typeset -gA ASYNC_PROCESS_BUFFER=() - local worker=prompt_pure_worker_sync_test + local worker=prompt_impure_worker_sync_test async_stop_worker $worker >/dev/null 2>&1 || : async_start_worker $worker -u || return @@ -352,7 +352,7 @@ test_real_worker_sync_reports_encoded_status() { callback_output=$3 } - async_worker_eval $worker prompt_pure_async_worker_sync 1 "$tmpdir" 0 "" 0 "" || return + async_worker_eval $worker prompt_impure_async_worker_sync 1 "$tmpdir" 0 "" 0 "" || return local index for index in {1..50}; do @@ -367,7 +367,7 @@ test_real_worker_sync_reports_encoded_status() { assert_equal "[async/eval]" "$callback_job" "real worker sync should report through async/eval" || return assert_equal 1 "$callback_code" "real async/eval wrapper should preserve eval status" || return - if [[ $callback_output != *"prompt_pure_worker_sync:1:1"* ]]; then + if [[ $callback_output != *"prompt_impure_worker_sync:1:1"* ]]; then print -u2 -- "Assertion failed: real worker sync should encode failed cd status in output" print -u2 -- "Actual: $callback_output" return 1 @@ -381,20 +381,20 @@ test_callback_no_recursion_on_worker_failure() { async_start_worker() { return 1 } async_stop_worker() { : } - # Stub prompt_pure_async_tasks to detect if it gets called. + # Stub prompt_impure_async_tasks to detect if it gets called. local tasks_called=0 - prompt_pure_async_tasks() { tasks_called=1 } + prompt_impure_async_tasks() { tasks_called=1 } - typeset -g prompt_pure_async_inited=0 + typeset -g prompt_impure_async_inited=0 # Simulate the callback receiving an async worker crash (code 3). # Code 3 is representative; codes 2 and 130 share the same branch. - prompt_pure_async_callback '[async]' 3 '' 0 'worker crashed' 0 + prompt_impure_async_callback '[async]' 3 '' 0 'worker crashed' 0 - assert_equal 0 $prompt_pure_async_inited "prompt_pure_async_inited should remain 0 after failed recovery" || return - assert_equal 0 $tasks_called "prompt_pure_async_tasks should not be called when recovery fails" || return + assert_equal 0 $prompt_impure_async_inited "prompt_impure_async_inited should remain 0 after failed recovery" || return + assert_equal 0 $tasks_called "prompt_impure_async_tasks should not be called when recovery fails" || return - unfunction async_start_worker async_stop_worker prompt_pure_async_tasks + unfunction async_start_worker async_stop_worker prompt_impure_async_tasks } test_callback_failed_recovery_clears_git_state() { @@ -406,20 +406,20 @@ test_callback_failed_recovery_clears_git_state() { : } - typeset -gA prompt_pure_vcs_info=(branch main top /tmp/repo action rebase pwd /tmp/repo) - typeset -g prompt_pure_git_dirty="*" - typeset -g prompt_pure_git_last_dirty_check_timestamp=1 - typeset -g prompt_pure_git_arrows="⇡" - typeset -g prompt_pure_git_stash=1 - typeset -g prompt_pure_git_fetch_pattern="pull|fetch" - typeset -g prompt_pure_async_inited=1 + typeset -gA prompt_impure_vcs_info=(branch main top /tmp/repo action rebase pwd /tmp/repo) + typeset -g prompt_impure_git_dirty="*" + typeset -g prompt_impure_git_last_dirty_check_timestamp=1 + typeset -g prompt_impure_git_arrows="⇡" + typeset -g prompt_impure_git_stash=1 + typeset -g prompt_impure_git_fetch_pattern="pull|fetch" + typeset -g prompt_impure_async_inited=1 local render_called=0 - prompt_pure_preprompt_render() { + prompt_impure_preprompt_render() { render_called=1 } # Use next_pending=1 because a worker crash may be reported while buffered output remains. - prompt_pure_async_callback '[async]' 3 '' 0 'worker crashed' 1 + prompt_impure_async_callback '[async]' 3 '' 0 'worker crashed' 1 assert_git_state_empty "when worker recovery fails" || return assert_equal 1 $render_called "prompt should be rendered after worker recovery clears git state" || return @@ -428,13 +428,13 @@ test_callback_failed_recovery_clears_git_state() { async_job() { async_job_called=1 } - prompt_pure_async_callback prompt_pure_async_vcs_info 0 "pwd ${(q)PWD} branch stale top /tmp/repo action rebase" 0 '' 0 + prompt_impure_async_callback prompt_impure_async_vcs_info 0 "pwd ${(q)PWD} branch stale top /tmp/repo action rebase" 0 '' 0 - assert_empty "${prompt_pure_vcs_info[branch]-}" "stale callback should not restore branch after worker recovery fails" || return - assert_empty "${prompt_pure_vcs_info[top]-}" "stale callback should not restore top-level after worker recovery fails" || return + assert_empty "${prompt_impure_vcs_info[branch]-}" "stale callback should not restore branch after worker recovery fails" || return + assert_empty "${prompt_impure_vcs_info[top]-}" "stale callback should not restore top-level after worker recovery fails" || return assert_equal 0 $async_job_called "stale callback should not queue async jobs after worker recovery fails" || return - unfunction async_start_worker async_stop_worker prompt_pure_preprompt_render async_job + unfunction async_start_worker async_stop_worker prompt_impure_preprompt_render async_job } test_callback_recovery_calls_tasks_on_success() { @@ -444,29 +444,29 @@ test_callback_recovery_calls_tasks_on_success() { async_register_callback() { : } async_worker_eval() { : } - # Stub prompt_pure_async_tasks to detect if it gets called. + # Stub prompt_impure_async_tasks to detect if it gets called. local tasks_called=0 - prompt_pure_async_tasks() { tasks_called=1 } + prompt_impure_async_tasks() { tasks_called=1 } - typeset -g prompt_pure_async_inited=0 + typeset -g prompt_impure_async_inited=0 # Simulate the callback receiving an async worker crash (code 2). - prompt_pure_async_callback '[async]' 2 '' 0 'worker crashed' 0 + prompt_impure_async_callback '[async]' 2 '' 0 'worker crashed' 0 - assert_equal 1 $tasks_called "prompt_pure_async_tasks should be called when recovery succeeds" || return + assert_equal 1 $tasks_called "prompt_impure_async_tasks should be called when recovery succeeds" || return - unfunction async_start_worker async_stop_worker async_register_callback async_worker_eval prompt_pure_async_tasks + unfunction async_start_worker async_stop_worker async_register_callback async_worker_eval prompt_impure_async_tasks } test_dead_worker_no_stderr_leakage() { - # Restore prompt_pure_async_tasks (earlier tests unfunction it). - source ./pure.zsh >/dev/null 2>&1 - prompt_pure_preprompt_render() { : } + # Restore prompt_impure_async_tasks (earlier tests unfunction it). + source ./impure.zsh >/dev/null 2>&1 + prompt_impure_preprompt_render() { : } # Simulate: worker was previously started but is now dead. - # When recovery fails, the remaining async calls in prompt_pure_async_tasks + # When recovery fails, the remaining async calls in prompt_impure_async_tasks # must not leak error messages to stderr. (GitHub issue #639) - typeset -g prompt_pure_async_inited=1 + typeset -g prompt_impure_async_inited=1 # Recovery will fail because the worker cannot be restarted. async_start_worker() { return 1 } @@ -477,9 +477,9 @@ test_dead_worker_no_stderr_leakage() { async_register_callback() { : } async_flush_jobs() { : } - # Register the callback (as prompt_pure_async_init would have done). + # Register the callback (as prompt_impure_async_init would have done). typeset -gA ASYNC_CALLBACKS - ASYNC_CALLBACKS[prompt_pure]="prompt_pure_async_callback" + ASYNC_CALLBACKS[prompt_impure]="prompt_impure_async_callback" # Simulate dead worker behavior: the first call with a registered callback # invokes recovery (which fails and unregisters the callback). Subsequent @@ -510,10 +510,10 @@ test_dead_worker_no_stderr_leakage() { } local stderr_file=$TMPDIR/pure-test-stderr-$$ - prompt_pure_async_tasks 2>$stderr_file + prompt_impure_async_tasks 2>$stderr_file local stderr_output=$(<$stderr_file 2>/dev/null) - assert_empty "$stderr_output" "prompt_pure_async_tasks must not leak error messages to stderr when worker is dead" || return + assert_empty "$stderr_output" "prompt_impure_async_tasks must not leak error messages to stderr when worker is dead" || return unfunction async_start_worker async_stop_worker async_register_callback async_flush_jobs async_worker_eval async_job } diff --git a/tests/benchmark.zsh b/tests/benchmark.zsh index fd3d9d1..6e52928 100644 --- a/tests/benchmark.zsh +++ b/tests/benchmark.zsh @@ -1,5 +1,5 @@ #!/usr/bin/env zsh -# Benchmark Pure's synchronous hot path. +# Benchmark Impure's synchronous hot path. # Usage: zsh tests/benchmark.zsh [--dimmed] setopt noshwordsplit @@ -9,7 +9,7 @@ cd -- "${0:A:h}/.." # Stub async machinery so we only measure synchronous code. # Note: with async_worker_eval stubbed, the worker-sync caching in -# prompt_pure_async_tasks always measures the cache-hit path after +# prompt_impure_async_tasks always measures the cache-hit path after # the first iteration. async() { : } async_init() { : } @@ -24,27 +24,27 @@ async_job() { : } local dimmed=0 if [[ $1 == '--dimmed' ]]; then dimmed=1 - zstyle ':prompt:pure:path:separator' dim yes + zstyle ':prompt:impure:path:separator' dim yes fi # Disable title setting to avoid escape sequences in output. -zstyle ':prompt:pure:title' show no +zstyle ':prompt:impure:title' show no -# Source Pure (suppresses output from setup). -source ./pure.zsh >/dev/null 2>&1 +# Source Impure (suppresses output from setup). +source ./impure.zsh >/dev/null 2>&1 # Simulate a populated prompt state (as if inside a git repo with all features). -typeset -gA prompt_pure_vcs_info=( +typeset -gA prompt_impure_vcs_info=( branch 'main' top "$PWD" action '' pwd "$PWD" ) -typeset -g prompt_pure_git_dirty='*' -typeset -g prompt_pure_git_arrows='⇣⇡' -typeset -g prompt_pure_git_stash='1' -typeset -g prompt_pure_cmd_exec_time='42s' -typeset -g prompt_pure_cmd_timestamp=$EPOCHSECONDS +typeset -g prompt_impure_git_dirty='*' +typeset -g prompt_impure_git_arrows='⇣⇡' +typeset -g prompt_impure_git_stash='1' +typeset -g prompt_impure_cmd_exec_time='42s' +typeset -g prompt_impure_cmd_timestamp=$EPOCHSECONDS _bench() { local name=$1 iters=$2 @@ -84,7 +84,7 @@ _bench() { local iters=200 -print "Pure prompt benchmark (${iters} iterations)" +print "Impure prompt benchmark (${iters} iterations)" if (( dimmed )); then print " Mode: dimmed path separator ENABLED" else @@ -92,23 +92,23 @@ else fi print -_bench "prompt_pure_set_colors" $iters \ - 'prompt_pure_set_colors' +_bench "prompt_impure_set_colors" $iters \ + 'prompt_impure_set_colors' -_bench "prompt_pure_set_path_separator" $iters \ - 'prompt_pure_set_path_separator' +_bench "prompt_impure_set_path_separator" $iters \ + 'prompt_impure_set_path_separator' -_bench "prompt_pure_preprompt_render (precmd)" $iters \ - 'prompt_pure_preprompt_render precmd' +_bench "prompt_impure_preprompt_render (precmd)" $iters \ + 'prompt_impure_preprompt_render precmd' _bench "\${(S%%)PROMPT} expansion" $iters \ 'local x="${(S%%)PROMPT}"' -_bench "prompt_pure_async_tasks (sync part)" $iters \ - 'prompt_pure_async_tasks' +_bench "prompt_impure_async_tasks (sync part)" $iters \ + 'prompt_impure_async_tasks' -_bench "prompt_pure_precmd (full)" $iters \ - 'prompt_pure_precmd' +_bench "prompt_impure_precmd (full)" $iters \ + 'prompt_impure_precmd' print print "Done." diff --git a/tests/git-dirty.zsh b/tests/git-dirty.zsh index 158828b..d7bda07 100644 --- a/tests/git-dirty.zsh +++ b/tests/git-dirty.zsh @@ -1,5 +1,12 @@ #!/usr/bin/env zsh +# Tests for prompt_impure_async_git_dirty. +# +# The function outputs an oh-my-posh style summary "STAGING|WORKING" where +# STAGING is like "+2 ~1 -1" (added/modified/deleted in the index) and +# WORKING is "*" when the working tree is dirty or empty when it is clean. +# Return code is 0 for a clean repo, 1 for a dirty one. + setopt clobber set -euo pipefail @@ -25,156 +32,77 @@ setup_repo() { command git commit -q -m "initial" } +# Run the dirty check. $1 is the untracked_dirty flag (1 = count untracked). check_dirty() { local dirty_output dirty_code - dirty_output=$(prompt_pure_async_git_dirty "$@") && dirty_code=0 || dirty_code=$? + dirty_output=$(prompt_impure_async_git_dirty "$@") && dirty_code=0 || dirty_code=$? typeset -g check_output=$dirty_output check_code=$dirty_code } setup_repo -# ── Default mode (no detailed flag) ── - -# Clean repo. +# Clean repo: return 0, no output. check_dirty 1 assert_equal 0 $check_code "clean repo should return 0" assert_empty "$check_output" "clean repo should have no output" -# Unstaged changes. +# Unstaged modification only: working dirty, no staging. echo "modified" > file.txt check_dirty 1 -assert_equal 1 $check_code "dirty repo should return 1" -assert_empty "$check_output" "default mode should have no output" +assert_equal 1 $check_code "unstaged change should return 1" +assert_equal "|*" "$check_output" "unstaged change should show empty staging + working marker" -# Staged changes. +# Staged modification only: staging "~1", clean working tree. command git add file.txt check_dirty 1 -assert_equal 1 $check_code "staged dirty should return 1" -assert_empty "$check_output" "default mode staged should have no output" +assert_equal 1 $check_code "staged change should return 1" +assert_equal "~1|" "$check_output" "staged modification should show ~1 with no working marker" -# Untracked files. -command git commit -q -m "second" -echo "untracked" > newfile.txt +# Staged + unstaged on the same file: staging "~1", working dirty. +echo "more" > file.txt check_dirty 1 -assert_equal 1 $check_code "untracked dirty should return 1" -assert_empty "$check_output" "default mode untracked should have no output" - -# PURE_GIT_UNTRACKED_DIRTY=0 ignores untracked. -check_dirty 0 -assert_equal 0 $check_code "untracked with PURE_GIT_UNTRACKED_DIRTY=0 should be clean" -assert_empty "$check_output" "untracked with PURE_GIT_UNTRACKED_DIRTY=0 should have no output" -zf_rm -f newfile.txt +assert_equal 1 $check_code "staged+unstaged should return 1" +assert_equal "~1|*" "$check_output" "staged+unstaged should show ~1 and working marker" +command git checkout -q -- file.txt +command git reset -q HEAD -- file.txt 2>/dev/null || true +command git checkout -q -- file.txt + +# Staged addition: staging "+1". +echo "added" > added.txt +command git add added.txt +check_dirty 1 +assert_equal 1 $check_code "staged addition should return 1" +assert_equal "+1|" "$check_output" "staged addition should show +1" +command git commit -q -m "add added.txt" -# PURE_GIT_UNTRACKED_DIRTY=0 still detects staged tracked changes. -echo "staged with ignored untracked" > file.txt +# Staged deletion: staging "-1". +command git rm -q added.txt +check_dirty 1 +assert_equal 1 $check_code "staged deletion should return 1" +assert_equal "-1|" "$check_output" "staged deletion should show -1" +command git commit -q -m "remove added.txt" + +# Multiple staged kinds: +N ~N together, space separated. +echo "newone" > newone.txt +echo "newtwo" > newtwo.txt +command git add newone.txt newtwo.txt +echo "touch" > file.txt command git add file.txt -check_dirty 0 -assert_equal 1 $check_code "staged with PURE_GIT_UNTRACKED_DIRTY=0 should return 1" -assert_empty "$check_output" "staged with PURE_GIT_UNTRACKED_DIRTY=0 should have no output" -command git commit -q -m "staged with ignored untracked" - -# ── Detailed mode ── - -# Clean repo. -check_dirty 1 1 -assert_equal 0 $check_code "detailed: clean repo should return 0" -assert_empty "$check_output" "detailed: clean repo should have no output" - -# Unstaged changes only. -echo "detailed-modified" > file.txt -check_dirty 1 1 -assert_equal 1 $check_code "detailed: unstaged should return 1" -assert_equal "*" "$check_output" "detailed: unstaged only should show *" +check_dirty 1 +assert_equal 1 $check_code "mixed staging should return 1" +assert_equal "+2 ~1|" "$check_output" "mixed staging should show +2 ~1" +command git commit -q -m "mixed" -# Staged changes only. -command git add file.txt -check_dirty 1 1 -assert_equal 1 $check_code "detailed: staged should return 1" -assert_equal "+" "$check_output" "detailed: staged only should show +" - -# Staged + intent-to-add. -echo "intent" > intent.txt -command git add -N intent.txt -check_dirty 1 1 -assert_equal 1 $check_code "detailed: staged+intent-to-add should return 1" -assert_equal "*+" "$check_output" "detailed: staged+intent-to-add should show *+" -command git reset -q HEAD -- intent.txt -zf_rm -f intent.txt - -# Staged + unstaged (same file). -echo "more changes" > file.txt -check_dirty 1 1 -assert_equal 1 $check_code "detailed: staged+unstaged should return 1" -assert_equal "*+" "$check_output" "detailed: staged+unstaged should show *+" - -# Untracked only (commit current changes first). -command git add file.txt -command git commit -q -m "third" -echo "new" > newfile.txt -check_dirty 1 1 -assert_equal 1 $check_code "detailed: untracked should return 1" -assert_equal "?" "$check_output" "detailed: untracked only should show ?" - -# All three: unstaged + staged + untracked. -echo "modified again" > file.txt -command git add file.txt -echo "even more" > file.txt -check_dirty 1 1 -assert_equal 1 $check_code "detailed: all three should return 1" -assert_equal "*+?" "$check_output" "detailed: all three should show *+?" +# Untracked only, counted: working dirty. +echo "untracked" > untracked.txt +check_dirty 1 +assert_equal 1 $check_code "untracked should return 1 when counted" +assert_equal "|*" "$check_output" "untracked should show working marker when counted" -# PURE_GIT_UNTRACKED_DIRTY=0 with only untracked files. -command git add file.txt -command git commit -q -m "fourth" -zf_rm -f newfile.txt -echo "untracked" > anotherfile.txt -check_dirty 0 1 -assert_equal 0 $check_code "detailed: untracked with PURE_GIT_UNTRACKED_DIRTY=0 should be clean" -assert_empty "$check_output" "detailed: untracked with PURE_GIT_UNTRACKED_DIRTY=0 should have no output" - -# PURE_GIT_UNTRACKED_DIRTY=0 with unstaged changes only. -echo "changed" > file.txt -check_dirty 0 1 -assert_equal 1 $check_code "detailed: unstaged with PURE_GIT_UNTRACKED_DIRTY=0 should return 1" -assert_equal "*" "$check_output" "detailed: unstaged with PURE_GIT_UNTRACKED_DIRTY=0 should show *" - -# PURE_GIT_UNTRACKED_DIRTY=0 with staged changes. -command git checkout -- file.txt -echo "staged" > staged.txt -command git add staged.txt -check_dirty 0 1 -assert_equal 1 $check_code "detailed: staged with PURE_GIT_UNTRACKED_DIRTY=0 should return 1" -assert_equal "+" "$check_output" "detailed: staged with PURE_GIT_UNTRACKED_DIRTY=0 should show +" - -# Deleted file (unstaged). -command git commit -q --allow-empty -m "prep" -command git checkout -- file.txt 2>/dev/null || true -zf_rm -f anotherfile.txt staged.txt -command git add -A -command git commit -q -m "clean slate" -echo "to delete" > deleteme.txt -command git add deleteme.txt -command git commit -q -m "add deleteme" -zf_rm -f deleteme.txt -check_dirty 1 1 -assert_equal 1 $check_code "detailed: deleted unstaged should return 1" -assert_equal "*" "$check_output" "detailed: deleted unstaged should show *" - -# Deleted file (staged). -command git rm -q deleteme.txt -check_dirty 1 1 -assert_equal 1 $check_code "detailed: deleted staged should return 1" -assert_equal "+" "$check_output" "detailed: deleted staged should show +" - -# Renamed file (staged). -command git reset -q HEAD -- deleteme.txt -command git checkout -- deleteme.txt 2>/dev/null || true -echo "to rename" > renameme.txt -command git add renameme.txt -command git commit -q -m "add renameme" -command git mv renameme.txt renamed.txt -check_dirty 1 1 -assert_equal 1 $check_code "detailed: renamed staged should return 1" -assert_equal "+" "$check_output" "detailed: renamed staged should show +" +# Untracked only, ignored (untracked_dirty=0): clean. +check_dirty 0 +assert_equal 0 $check_code "untracked should be clean when ignored" +assert_empty "$check_output" "untracked should produce no output when ignored" +zf_rm -f untracked.txt print "git-dirty tests passed" diff --git a/tests/node-version.zsh b/tests/node-version.zsh deleted file mode 100644 index d0d1113..0000000 --- a/tests/node-version.zsh +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:A:h}/test-helper.zsh" - -main() { - zmodload -F zsh/files b:zf_rm - - local base_directory=$PWD/.ai-temporary/pure-node-version-$$ - local bin_directory=$base_directory/bin - local second_bin_directory=$base_directory/bin-second - local project_directory=$base_directory/project - local nested_directory=$project_directory/deep/nested - local outside_directory=/ - local counter_path=$base_directory/node-count - - trap "zf_rm -rf -- ${(q)base_directory}" EXIT - - mkdir -p -- "$bin_directory" "$second_bin_directory" "$nested_directory" - : > "$project_directory/package.json" - : > "$counter_path" - - local node_path=$bin_directory/node - cat > "$node_path" < "$counter_path" -printf '%s\n' 'v25.8.0' -EOF - chmod +x "$node_path" - - local second_node_path=$second_bin_directory/node - cat > "$second_node_path" < "$counter_path" -printf '%s\n' 'v26.1.0' -EOF - chmod +x "$second_node_path" - - prompt_pure_async_init() { - : - } - - async_worker_eval() { - : - } - - async_flush_jobs() { - : - } - - async_job() { - : - } - - typeset -gA prompt_pure_vcs_info=(pwd '' top '') - zstyle ':prompt:pure:environment:node_version' show yes - local prompt_layout=$(command zsh -fc 'source ./pure.zsh >/dev/null 2>&1; print -r -- $PROMPT') - local before_stash=${prompt_layout%%"%(18V."*} - local before_node_version=${prompt_layout%%"%(21V."*} - local before_execution_time=${prompt_layout%%"%(19V."*} - local before_newline=${prompt_layout%%'${prompt_newline}'*} - local before_virtualenv=${prompt_layout%%"%(20V."*} - if (( ${#before_stash} >= ${#before_node_version} || ${#before_node_version} >= ${#before_execution_time} || ${#before_execution_time} >= ${#before_newline} || ${#before_newline} >= ${#before_virtualenv} )); then - print -u2 -- "Assertion failed: node version should render after git stash and before execution time on the preprompt line" - return 1 - fi - - PATH="$bin_directory:/usr/bin:/bin" - builtin cd -q "$nested_directory" - prompt_pure_async_tasks || : - assert_equal "25" "${prompt_pure_node_version-}" "node version should be set synchronously inside a package.json tree" - assert_equal "1" "$(cat "$counter_path")" "node should be resolved on the first prompt in a package.json tree" - - prompt_pure_async_tasks || : - assert_equal "25" "${prompt_pure_node_version-}" "node version should be reused when directory and PATH do not change" - assert_equal "1" "$(cat "$counter_path")" "node should not be resolved again when directory and PATH are unchanged" - - PATH="$second_bin_directory:/usr/bin:/bin" - prompt_pure_async_tasks || : - assert_equal "26" "${prompt_pure_node_version-}" "node version should be refreshed when PATH changes" - assert_equal "2" "$(cat "$counter_path")" "node should be resolved again when PATH changes" - - prompt_pure_vcs_info[pwd]=$outside_directory - prompt_pure_async_tasks || : - assert_equal "26" "${prompt_pure_node_version-}" "node version should survive git working tree state reset" - assert_equal "2" "$(cat "$counter_path")" "node should not be resolved again when git working tree state resets" - - zstyle ':prompt:pure:git' show no - unset prompt_pure_node_version - unset prompt_pure_node_cache_key - prompt_pure_async_tasks || : - assert_equal "26" "${prompt_pure_node_version-}" "node version should still be set when git integration is disabled" - assert_equal "3" "$(cat "$counter_path")" "node should be resolved when git integration is disabled" - zstyle -d ':prompt:pure:git' show - - builtin cd -q "$outside_directory" - prompt_pure_async_tasks || : - assert_empty "${prompt_pure_node_version-}" "node version should be cleared outside a package.json tree" - - zstyle ':prompt:pure:environment:node_version' show no - builtin cd -q "$nested_directory" - typeset -g prompt_pure_node_version=stale - prompt_pure_async_tasks || : - assert_empty "${prompt_pure_node_version-}" "node version should stay disabled when the style is off" - - print -- "node-version tests passed" -} - -main "$@" diff --git a/tests/path-separator.zsh b/tests/path-separator.zsh deleted file mode 100644 index c0f7d7f..0000000 --- a/tests/path-separator.zsh +++ /dev/null @@ -1,144 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:A:h}/test-helper.zsh" - -main() { - zmodload -F zsh/files b:zf_rm - - local base_directory=$PWD/.ai-temporary/pure-path-test-$$ - trap "zf_rm -rf -- ${(q)base_directory}" EXIT - - typeset -gA prompt_pure_colors=( - path blue - ) - - # Disabled by default: path should render without dimmed separators. - local prompt_layout - prompt_layout=$(command zsh -fc 'source ./pure.zsh >/dev/null 2>&1; print -r -- $PROMPT') - if [[ $prompt_layout != *'${prompt_pure_path_segment}'* || $prompt_layout != *'$(prompt_pure_render_dimmed_path)'* ]]; then - print -u2 -- "Assertion failed: default PROMPT should support plain and dimmed path rendering" - print -u2 -- "Actual: $prompt_layout" - return 1 - fi - local expanded_prompt - expanded_prompt=$(command zsh -fc 'source ./pure.zsh >/dev/null 2>&1; print -r -- ${(S%%)PROMPT}') - local dim=$'\e[2m' - local nodim=$'\e[22m' - local blue=$'\e[34m' - local red=$'\e[31m' - local expected_path=${(%):-%~} - if [[ $expanded_prompt != *"${blue}${expected_path}"* || $expanded_prompt == *"${dim}/"* || $expanded_prompt == *"${nodim}"* || $expanded_prompt == *'}'* ]]; then - print -u2 -- "Assertion failed: default PROMPT should not dim path separators" - print -u2 -- "Actual: $expanded_prompt" - return 1 - fi - - expanded_prompt=$(command zsh -fc 'source ./pure.zsh >/dev/null 2>&1; zstyle :prompt:pure:path color red; prompt_pure_set_colors; print -r -- ${(S%%)PROMPT}') - if [[ $expanded_prompt != *"${red}${expected_path}"* ]]; then - print -u2 -- "Assertion failed: default PROMPT should refresh path color" - print -u2 -- "Actual: $expanded_prompt" - return 1 - fi - - # Enabled via zstyle: separators should use ANSI dim attribute. - zstyle ':prompt:pure:path:separator' dim yes - local rendered_path - rendered_path=$(prompt_pure_render_dimmed_path) - if [[ $rendered_path != *"%{${dim}%}/%{${nodim}%}"* ]]; then - print -u2 -- "Assertion failed: separators should use ANSI dim when enabled" - print -u2 -- "Actual: $rendered_path" - return 1 - fi - if [[ $rendered_path != "%F{blue}"* ]]; then - print -u2 -- "Assertion failed: path should start with path color" - print -u2 -- "Actual: $rendered_path" - return 1 - fi - - # Path without slashes (home directory itself). - local saved_pwd=$PWD - builtin cd -q ~ - rendered_path=$(prompt_pure_render_dimmed_path) - assert_equal "%F{blue}~%f" "$rendered_path" "home directory with no slashes should produce no dim separators" - builtin cd -q "$saved_pwd" - - # Absolute path outside home: leading / should not be dimmed. - builtin cd -q / - rendered_path=$(prompt_pure_render_dimmed_path) - if [[ $rendered_path != "%F{blue}/"* ]]; then - print -u2 -- "Assertion failed: leading slash on absolute path should not be dimmed" - print -u2 -- "Actual: $rendered_path" - builtin cd -q "$saved_pwd" - return 1 - fi - builtin cd -q "$saved_pwd" - - # Paths with percent characters are escaped. - mkdir -p -- "$base_directory/a%b" - builtin cd -q "$base_directory/a%b" - rendered_path=$(prompt_pure_render_dimmed_path) - if [[ $rendered_path != *"a%%b"* ]]; then - print -u2 -- "Assertion failed: percent in path should be escaped as %%" - print -u2 -- "Actual: $rendered_path" - builtin cd -q "$saved_pwd" - return 1 - fi - builtin cd -q "$saved_pwd" - - # Prompt redraws without precmd should still show the current directory. - mkdir -p -- "$base_directory/first/path" "$base_directory/second/path" - local first_prompt second_prompt - { - read -r first_prompt - read -r second_prompt - } < <( - command zsh -fc ' - typeset -g prompt_newline=" " - zstyle ":prompt:pure:path:separator" dim yes - source ./pure.zsh >/dev/null 2>&1 - typeset -gA prompt_pure_colors=( - path blue - prompt:success magenta - prompt:error red - ) - builtin cd -q "$1" - local first_prompt=${(S%%)PROMPT} - builtin cd -q "$2" - local second_prompt=${(S%%)PROMPT} - print -r -- "$first_prompt" - print -r -- "$second_prompt" - ' zsh "$base_directory/first/path" "$base_directory/second/path" - ) - if [[ $first_prompt != *"first"* || $second_prompt != *"second"* || $second_prompt == *"first"* ]]; then - print -u2 -- "Assertion failed: dimmed path should update on prompt redraw without precmd" - print -u2 -- "First prompt: $first_prompt" - print -u2 -- "Second prompt: $second_prompt" - return 1 - fi - - # PROMPT template renders the path dynamically. - prompt_layout=$(command zsh -fc 'zstyle :prompt:pure:path:separator dim yes; source ./pure.zsh >/dev/null 2>&1; print -r -- $PROMPT') - if [[ $prompt_layout != *'$(prompt_pure_render_dimmed_path)'* ]]; then - print -u2 -- "Assertion failed: PROMPT should render path dynamically" - print -u2 -- "Actual: $prompt_layout" - return 1 - fi - - expanded_prompt=$(command zsh -fc 'zstyle :prompt:pure:path:separator dim yes; source ./pure.zsh >/dev/null 2>&1; print -r -- ${(S%%)PROMPT}') - if [[ $expanded_prompt != *"${dim}/"* || $expanded_prompt != *"${nodim}"* || $expanded_prompt == *"$expected_path"* || $expanded_prompt == *'}'* ]]; then - print -u2 -- "Assertion failed: expanded PROMPT should dim path separators" - print -u2 -- "Actual: $expanded_prompt" - return 1 - fi - - expanded_prompt=$(command zsh -fc 'source ./pure.zsh >/dev/null 2>&1; zstyle :prompt:pure:path:separator dim yes; prompt_pure_set_colors; print -r -- ${(S%%)PROMPT}') - if [[ $expanded_prompt != *"${dim}/"* || $expanded_prompt != *"${nodim}"* || $expanded_prompt == *"$expected_path"* || $expanded_prompt == *'}'* ]]; then - print -u2 -- "Assertion failed: path separator dimming should update after setup" - print -u2 -- "Actual: $expanded_prompt" - return 1 - fi - - print -- "path-separator tests passed" -} - -main "$@" diff --git a/tests/preview.zsh b/tests/preview.zsh deleted file mode 100644 index e3a127d..0000000 --- a/tests/preview.zsh +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:A:h}/test-helper.zsh" - -main() { - # Set up colors like prompt_pure_setup would. - typeset -gA prompt_pure_colors=( - custom:prefix 242 - custom:suffix 242 - execution_time yellow - git:arrow cyan - git:stash cyan - git:branch 242 - git:branch:cached red - git:action yellow - git:dirty 218 - host 242 - node_version green - path blue - prompt:error red - prompt:success magenta - prompt:continuation 242 - suspended_jobs red - user 242 - user:root default - virtualenv 242 - ) - typeset -gA prompt_pure_colors_default - prompt_pure_colors_default=("${(@kv)prompt_pure_colors}") - - local output - output=$(prompt_pure_preview 2>&1) - - for component in prefix suffix zaphod heartofgold "~/dev/pure" "main" "rebase-i" "42s" "venv" "prompt after error" "continuation prompt" "root" "branch color when data is cached"; do - if [[ $output != *"$component"* ]]; then - print -u2 "Missing component in preview output: $component" - return 1 - fi - done - - zstyle ':prompt:pure:path' color red - output=$(prompt_pure_preview 2>&1) - - if [[ $output != *$'\e[31m~/dev/pure'* ]]; then - print -u2 "Preview did not apply zstyle path color." - return 1 - fi - - zstyle ':prompt:pure:environment:node_version' symbol '⬡' - output=$(prompt_pure_preview 2>&1) - - if [[ $output != *'⬡22'* ]]; then - print -u2 "Preview did not apply zstyle Node.js symbol." - return 1 - fi - - zstyle ':prompt:pure:path:separator' dim yes - output=$(prompt_pure_preview 2>&1) - - if [[ $output != *$'\e[2m/\e[22m'* ]]; then - print -u2 "Preview did not apply dimmed path separators." - return 1 - fi - - zstyle ':prompt:pure:host' show no - output=$(prompt_pure_preview 2>&1) - - if [[ $output == *'heartofgold'* ]]; then - print -u2 "Preview should not show hostname when host display is disabled." - return 1 - fi - - if [[ $output != *'zaphod'* ]]; then - print -u2 "Preview should still show username when host display is disabled." - return 1 - fi - - print "preview tests passed." -} - -main "$@" diff --git a/tests/prompt-fingerprint.zsh b/tests/prompt-fingerprint.zsh deleted file mode 100644 index 8fcac84..0000000 --- a/tests/prompt-fingerprint.zsh +++ /dev/null @@ -1,60 +0,0 @@ -#!/usr/bin/env zsh - -set -euo pipefail - -cd -- "${0:A:h}/.." - -set +e -set +u -zstyle ':prompt:pure:title' show no -source ./pure.zsh >/dev/null 2>&1 - -assert_equal() { - local expected=$1 - local actual=$2 - local message=$3 - - if [[ $expected != $actual ]]; then - print -u2 -- "Assertion failed: $message" - print -u2 -- "Expected: $expected" - print -u2 -- "Actual: $actual" - return 1 - fi -} - -main() { - typeset -gA prompt_pure_state=( - prompt '❯' - ) - typeset -gA prompt_pure_vcs_info=( - branch '' - action '' - ) - typeset -g prompt_pure_git_branch_color=242 - typeset -g prompt_pure_precustom_step=0 - typeset -g prompt_pure_reset_prompt_count=0 - - prompt_pure_precustom() { - if (( prompt_pure_precustom_step == 0 )); then - psvar[22]='a|b' - psvar[23]=c - else - psvar[22]=a - psvar[23]='b|c' - fi - } - - prompt_pure_reset_prompt() { - (( ++prompt_pure_reset_prompt_count )) - } - - prompt_pure_preprompt_render precmd - (( prompt_pure_precustom_step++ )) - prompt_pure_preprompt_render - - assert_equal 1 $prompt_pure_reset_prompt_count "prompt fingerprint should distinguish separators inside custom prompt parts" || return - - print -- "prompt-fingerprint tests passed" -} - -main "$@" diff --git a/tests/rprompt.zsh b/tests/rprompt.zsh deleted file mode 100644 index 5f9ca2b..0000000 --- a/tests/rprompt.zsh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:A:h}/test-helper.zsh" - -main() { - # Simulate a framework that sets RPROMPT before Pure loads. - # Pure should clear it during setup since it does not use a right prompt. - local rprompt_after - rprompt_after=$(command zsh -fc 'RPROMPT="%~"; source ./pure.zsh >/dev/null 2>&1; print -r -- "$RPROMPT"') - assert_empty "$rprompt_after" "RPROMPT should be cleared by pure setup" - - print -- "rprompt tests passed" -} - -main "$@" diff --git a/tests/test-helper.zsh b/tests/test-helper.zsh index 58f0ebe..988ddb5 100644 --- a/tests/test-helper.zsh +++ b/tests/test-helper.zsh @@ -4,9 +4,9 @@ set -euo pipefail cd -- "${0:A:h}/.." -source ./pure.zsh >/dev/null 2>&1 +source ./impure.zsh >/dev/null 2>&1 -prompt_pure_preprompt_render() { +prompt_impure_preprompt_render() { : } diff --git a/tests/test-prompt.zsh b/tests/test-prompt.zsh index 62777a0..d277b99 100644 --- a/tests/test-prompt.zsh +++ b/tests/test-prompt.zsh @@ -57,7 +57,6 @@ test_colors_initialized() { assert_not_empty "prompt:error color set" "$prompt_impure_colors[prompt:error]" assert_not_empty "prompt:ssh color set" "$prompt_impure_colors[prompt:ssh]" assert_not_empty "path color set" "$prompt_impure_colors[path]" - assert_not_empty "path:ephemeral color set" "$prompt_impure_colors[path:ephemeral]" assert_not_empty "jj color set" "$prompt_impure_colors[jj]" assert_not_empty "zmx color set" "$prompt_impure_colors[zmx]" assert_not_empty "nix-shell color set" "$prompt_impure_colors[nix-shell]" @@ -174,28 +173,6 @@ test_zmx_segment() { assert_empty "ZMX psvar cleared" "${psvar[24]}" } -test_ephemeral_path() { - print "\n--- Ephemeral path ---" - ATELIER_EPHEMERAL=1 - prompt_impure_set_path_separator - if [[ "$prompt_impure_path_segment" == *"${prompt_impure_colors[path:ephemeral]}"* ]]; then - print -P "%F{green}✓%f ephemeral uses red path color" - (( PASS++ )) - else - print -P "%F{red}✗%f ephemeral not using red path color" - (( FAIL++ )) - fi - unset ATELIER_EPHEMERAL - prompt_impure_set_path_separator - if [[ "$prompt_impure_path_segment" == *"${prompt_impure_colors[path]}"* ]]; then - print -P "%F{green}✓%f normal uses blue path color" - (( PASS++ )) - else - print -P "%F{red}✗%f normal not using blue path color" - (( FAIL++ )) - fi -} - test_jj_async_function() { print "\n--- Jujutsu async ---" # The async function should exist @@ -235,7 +212,6 @@ run_test() { fingerprint) test_fingerprint_stable ;; transient) test_transient_flag ;; zmx) test_zmx_segment ;; - ephemeral) test_ephemeral_path ;; jj) test_jj_async_function ;; ssh) test_ssh_prompt_color ;; all) @@ -245,13 +221,12 @@ run_test() { test_fingerprint_stable test_transient_flag test_zmx_segment - test_ephemeral_path test_jj_async_function test_ssh_prompt_color ;; *) print "Unknown test: $1" - print "Available: colors structure rprompt fingerprint transient zmx ephemeral jj ssh all" + print "Available: colors structure rprompt fingerprint transient zmx jj ssh all" exit 1 ;; esac diff --git a/tests/test.zsh b/tests/test.zsh index 7bb7533..5eea3b1 100644 --- a/tests/test.zsh +++ b/tests/test.zsh @@ -1,15 +1,39 @@ #!/usr/bin/env zsh - -set -euo pipefail +# +# Run every test file in its own subshell so a failure in one cannot mask +# the others, and report an accurate overall pass/fail. Each test file is +# expected to exit non-zero on failure. cd -- "${0:A:h}" -local test_file -for test_file in *.zsh; do - if [[ $test_file == test.zsh || $test_file == test-helper.zsh || $test_file == benchmark.zsh ]]; then - continue - fi +local -a test_files +test_files=(*.zsh) + +local -a failed +local test_file test_status + +for test_file in $test_files; do + # Skip the harness itself, the shared helper, and the benchmark + # (the benchmark is a manual performance tool, not a pass/fail test). + case $test_file in + test.zsh|test-helper.zsh|benchmark.zsh) continue ;; + esac print -- "Running $test_file" zsh "$test_file" + test_status=$? + if (( test_status != 0 )); then + failed+=("$test_file (exit $test_status)") + fi done + +print -- +if (( ${#failed} )); then + print -u2 -- "FAILED:" + for test_file in $failed; do + print -u2 -- " - $test_file" + done + exit 1 +fi + +print -- "All tests passed." diff --git a/tests/virtualenv.zsh b/tests/virtualenv.zsh deleted file mode 100644 index 7bbf2f3..0000000 --- a/tests/virtualenv.zsh +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env zsh - -source "${0:A:h}/test-helper.zsh" - -main() { - # prompt_pure_precmd references many env vars that may be unset. - set +u - - zmodload zsh/datetime - zmodload zsh/zutil - - prompt_pure_set_title() { - : - } - - prompt_pure_set_colors() { - : - } - - prompt_pure_check_cmd_exec_time() { - : - } - - prompt_pure_async_tasks() { - : - } - - typeset -gA prompt_pure_vcs_info=(pwd '' top '') - typeset -gA prompt_pure_state=(prompt '❯') - - # Conda env should be shown by default. - CONDA_DEFAULT_ENV=myenv - prompt_pure_precmd - assert_equal "myenv" "$psvar[20]" "conda env should be shown by default" - - # Conda env with full path should show only the basename. - CONDA_DEFAULT_ENV=/path/to/envs/myenv - prompt_pure_precmd - assert_equal "myenv" "$psvar[20]" "conda env should show basename when set to full path" - - # Conda 'base' environment should be hidden. - CONDA_DEFAULT_ENV=base - prompt_pure_precmd - assert_empty "$psvar[20]" "conda base environment should be hidden" - - # Conda 'base' environment with full path should also be hidden. - CONDA_DEFAULT_ENV=/path/to/envs/base - prompt_pure_precmd - assert_empty "$psvar[20]" "conda base environment with full path should be hidden" - - # Conda env should be hidden when virtualenv style is off. - zstyle ':prompt:pure:environment:virtualenv' show no - prompt_pure_precmd - assert_empty "$psvar[20]" "conda env should be hidden when virtualenv show is no" - - # Virtualenv should be hidden when virtualenv style is off. - unset CONDA_DEFAULT_ENV - VIRTUAL_ENV=/path/to/venvs/myvenv - VIRTUAL_ENV_DISABLE_PROMPT=20 - prompt_pure_precmd - assert_empty "$psvar[20]" "virtualenv should be hidden when virtualenv show is no" - - # VIRTUAL_ENV_DISABLE_PROMPT should not be set by precmd when virtualenv show is off. - unset VIRTUAL_ENV_DISABLE_PROMPT - prompt_pure_precmd - assert_empty "${VIRTUAL_ENV_DISABLE_PROMPT-}" "VIRTUAL_ENV_DISABLE_PROMPT should not be set when virtualenv show is no" - - # Re-enable and verify virtualenv is shown. - zstyle -d ':prompt:pure:environment:virtualenv' show - prompt_pure_precmd - assert_equal "myvenv" "$psvar[20]" "virtualenv should be shown when style is re-enabled" - - # Virtualenv with VIRTUAL_ENV_DISABLE_PROMPT unset (user handed control back). - unset VIRTUAL_ENV_DISABLE_PROMPT - prompt_pure_precmd - assert_equal "myvenv" "$psvar[20]" "virtualenv should be shown when VIRTUAL_ENV_DISABLE_PROMPT is unset" - - # Conda env should be overridden by virtualenv when both are active. - CONDA_DEFAULT_ENV=condaenv - prompt_pure_precmd - assert_equal "myvenv" "$psvar[20]" "virtualenv should take precedence over conda" - - # VIRTUAL_ENV_PROMPT should be preferred over VIRTUAL_ENV basename. - VIRTUAL_ENV_PROMPT="custom-prompt" - prompt_pure_precmd - assert_equal "custom-prompt" "$psvar[20]" "VIRTUAL_ENV_PROMPT should be preferred" - - # Third-party VIRTUAL_ENV_DISABLE_PROMPT should be respected (Pure should not override). - unset CONDA_DEFAULT_ENV VIRTUAL_ENV_PROMPT - VIRTUAL_ENV_DISABLE_PROMPT=1 - prompt_pure_precmd - assert_empty "$psvar[20]" "third-party VIRTUAL_ENV_DISABLE_PROMPT should be respected" - - # Nix-shell should still work when virtualenv style is off. - unset VIRTUAL_ENV VIRTUAL_ENV_DISABLE_PROMPT - zstyle ':prompt:pure:environment:virtualenv' show no - IN_NIX_SHELL=pure - name=my-nix-shell - prompt_pure_precmd - assert_equal "my-nix-shell" "$psvar[20]" "nix-shell should be independent of virtualenv style" - unset IN_NIX_SHELL name - zstyle -d ':prompt:pure:environment:virtualenv' show - - print -- "virtualenv tests passed" -} - -main "$@" -- 2.51.2