Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"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; PURE_DEST=\"$PWD/functions\" npm run --silent postinstall-link && 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. You can either:\\n\\n1. Add the following to your \\`.zshrc\\`:\\n\\n fpath+=('$PWD/functions')\\n\\n2. Or check out the readme on how to do it manually: https://github.com/sindresorhus/pure#manually\"",
"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": [
Expand Down
54 changes: 50 additions & 4 deletions pure.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,14 @@ prompt_pure_preprompt_render() {
# psvar[19]: Command execution time.
psvar[19]=${prompt_pure_cmd_exec_time}

# psvar[21]: Node.js version.
psvar[21]=
if [[ -n $prompt_pure_node_version ]]; then
local node_symbol
zstyle -s ":prompt:pure:environment:node_version" symbol node_symbol || node_symbol='⬢'
psvar[21]="${node_symbol}${prompt_pure_node_version}"
fi

# Expand the prompt for future comparison.
local expanded_prompt
expanded_prompt="${(S%%)PROMPT}"
Expand Down Expand Up @@ -368,6 +376,25 @@ prompt_pure_async_git_stash() {
git rev-list --walk-reflogs --count refs/stash
}

prompt_pure_check_node_version() {
setopt localoptions noshwordsplit

# Walk up to find package.json (similar to how git detects repos).
local dir=$PWD
while [[ $dir != "/" ]]; do
[[ -f "$dir/package.json" ]] && break
dir=${dir:h}
done

local version=
if [[ -f "$dir/package.json" ]]; then
version=$(command node --version 2>/dev/null) || version=
version=${${${version#v}%%.*}//[$'\t\r\n']}
fi

print -r -- "$version"
}

# Try to lower the priority of the worker so that disk heavy operations
# like `git status` has less impact on the system responsivity.
prompt_pure_async_renice() {
Expand Down Expand Up @@ -409,19 +436,35 @@ prompt_pure_async_tasks() {
# Stop any running async jobs.
async_flush_jobs "prompt_pure"

# Reset Git preprompt variables, switching working tree.
# Reset preprompt variables, switching working tree.
unset prompt_pure_git_dirty
unset prompt_pure_git_last_dirty_check_timestamp
unset prompt_pure_git_arrows
unset prompt_pure_git_stash
unset prompt_pure_git_fetch_pattern
unset prompt_pure_node_version
unset prompt_pure_node_version_path
unset prompt_pure_node_version_pwd
prompt_pure_vcs_info[branch]=
prompt_pure_vcs_info[top]=
fi
unset MATCH MBEGIN MEND

async_job "prompt_pure" prompt_pure_async_vcs_info

# Check if Node.js version display is enabled (independent of Git).
if zstyle -t ":prompt:pure:environment:node_version" show; then
if [[ ${prompt_pure_node_version_pwd-} != $PWD ]] || [[ ${prompt_pure_node_version_path-} != $PATH ]]; then
typeset -g prompt_pure_node_version=$(prompt_pure_check_node_version)
typeset -g prompt_pure_node_version_pwd=$PWD
typeset -g prompt_pure_node_version_path=$PATH
fi
else
unset prompt_pure_node_version
unset prompt_pure_node_version_path
unset prompt_pure_node_version_pwd
fi

# Only perform tasks inside a Git working tree.
[[ -n $prompt_pure_vcs_info[top] ]] || return

Expand Down Expand Up @@ -812,6 +855,7 @@ prompt_pure_setup() {
git:action yellow
git:dirty 218
host 242
node_version green
path blue
prompt:error red
prompt:success magenta
Expand Down Expand Up @@ -841,7 +885,7 @@ prompt_pure_setup() {
typeset -g prompt_pure_git_branch_color=$prompt_pure_colors[git:branch]

# Construct PROMPT once, both preprompt and prompt line. Kept
# dynamic via variables and psvar[12-20], updated each render
# dynamic via variables and psvar[12-21], updated each render
# in prompt_pure_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
Expand All @@ -856,10 +900,11 @@ prompt_pure_setup() {
# psvar[18] = git stash flag, renders stash symbol
# psvar[19] = exec time (e.g. 1d 3h 2m 5s)
# psvar[20] = virtualenv/conda/nix-shell name
# psvar[21] = Node.js version (e.g. ⬢22)
#
# Example output:
# ✦ user@host ~/Code/pure main* rebase ⇣⇡ ≡ 3s
# myenv ❯
# myenv ⬢22
#
# Preprompt line: each %(NV..) section only renders when its psvar is non-empty.
PROMPT='%(12V.%F{$prompt_pure_colors[suspended_jobs]}%12v%f .)'
Expand All @@ -874,8 +919,9 @@ prompt_pure_setup() {
# Newline separating preprompt from prompt.
PROMPT+='${prompt_newline}'

# Prompt line: virtualenv and prompt symbol.
# Prompt line: virtualenv, Node.js version, and prompt symbol.
PROMPT+='%(20V.%F{$prompt_pure_colors[virtualenv]}%20v%f .)'
PROMPT+='%(21V.%F{$prompt_pure_colors[node_version]}%21v%f .)'
# Prompt symbol: turns red if the previous command didn't exit with 0.
local prompt_indicator='%(?.%F{$prompt_pure_colors[prompt:success]}.%F{$prompt_pure_colors[prompt:error]})${prompt_pure_state[prompt]}%f '
PROMPT+=$prompt_indicator
Expand Down
18 changes: 14 additions & 4 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,14 @@ You can set Pure to only `git fetch` the upstream branch of the current local br

`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`
Expand All @@ -115,6 +123,7 @@ Colors can be changed by using [`zstyle`](http://zsh.sourceforge.net/Doc/Release
- `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*.
Expand All @@ -138,10 +147,11 @@ The following diagram shows where each color is applied on the prompt:
│ │ │ │ │ │ │ │ ┌─── execution_time
│ │ │ │ │ │ │ │ │
zaphod@heartofgold ~/dev/pure master* rebase-i ⇡ ≡ 42s
venv ❯
│ │
│ └───────────────────────────────────────────────── prompt
└────────────────────────────────────────────────────── virtualenv (or prompt:continuation)
venv ⬢22 ❯
│ │ │
│ │ └──────────────────────────────────────── prompt
│ └──────────────────────────────────────────────── node_version
└───────────────────────────────────────────────────── virtualenv (or prompt:continuation)
```

### RGB colors
Expand Down
85 changes: 85 additions & 0 deletions tests/node-version.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
#!/usr/bin/env zsh

source "${0:A:h}/test-helper.zsh"

main() {
local base_directory=/tmp/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=$base_directory/outside
local counter_path=$base_directory/node-count

mkdir -p -- "$bin_directory" "$second_bin_directory" "$nested_directory" "$outside_directory"
: > "$project_directory/package.json"
: > "$counter_path"

local node_path=$bin_directory/node
cat > "$node_path" <<EOF
#!/bin/sh
count=\$(cat "$counter_path")
count=\$((count + 1))
printf '%s' "\$count" > "$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" <<EOF
#!/bin/sh
count=\$(cat "$counter_path")
count=\$((count + 1))
printf '%s' "\$count" > "$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

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"

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 "$@"
40 changes: 40 additions & 0 deletions tests/test-helper.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#!/usr/bin/env zsh

set -euo pipefail

cd -- "${0:A:h}/.."

source ./pure.zsh >/dev/null 2>&1

prompt_pure_preprompt_render() {
:
}

node_output() {
print -r -- "${1}"$'\t'"${2-}"
}

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
}

assert_empty() {
local actual=$1
local message=$2

if [[ -n $actual ]]; then
print -u2 -- "Assertion failed: $message"
print -u2 -- "Expected empty value"
print -u2 -- "Actual: $actual"
return 1
fi
}
15 changes: 15 additions & 0 deletions tests/test.zsh
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env zsh

set -euo pipefail

cd -- "${0:A:h}"

local test_file
for test_file in *.zsh; do
if [[ $test_file == test.zsh || $test_file == test-helper.zsh ]]; then
continue
fi

print -- "Running $test_file"
zsh "$test_file"
done