Merge remote-tracking branch 'source/master'

This commit is contained in:
2017-02-07 13:58:54 +01:00
6 changed files with 450 additions and 178 deletions

34
.github/issue_template.md vendored Normal file
View File

@@ -0,0 +1,34 @@
<!-- TIP: Hit 'Preview' for a more readable version of this template -->
### General information
- Pure version: 1.x.x
- ZSH version: 5.x.x
- Terminal program & version: <!-- e.g. Hyper 1.0.0, iTerm 3.0.0, Terminal 2.7.1, xterm 327, other? -->
- Operating system: <!-- e.g. macOS Sierra 10.12.1 -->
- ZSH framework: <!-- e.g. oh-my-zsh, prezto, antigen, antibody, zplug, other? -->
I have:
- [ ] Tested with another terminal program and can reproduce the issue: <!-- e.g. iTerm, etc. -->
- [ ] Followed the [Integration](https://github.com/sindresorhus/pure#integration) instructions for my framework
### Problem description
### Reproduction steps
1.
2.
3.
### My `.zshrc`:
<!--
Please provide a minimal `.zshrc` that reproduces the issue.
Try to remove everything that that does not affect the issue, the fewer lines, the better.
-->
```shell
autoload -U promptinit; promptinit
prompt pure
```

348
async.zsh
View File

@@ -3,96 +3,160 @@
#
# zsh-async
#
# version: 1.1.0
# version: 1.5.0
# author: Mathias Fredriksson
# url: https://github.com/mafredri/zsh-async
#
# Produce debug output from zsh-async when set to 1.
ASYNC_DEBUG=${ASYNC_DEBUG:-0}
# Wrapper for jobs executed by the async worker, gives output in parseable format with execution time
_async_job() {
# Disable xtrace as it would mangle the output.
setopt localoptions noxtrace
# Store start time as double precision (+E disables scientific notation)
float -F duration=$EPOCHREALTIME
# Run the command
#
# What is happening here is that we are assigning stdout, stderr and ret to
# variables, and then we are printing out the variable assignment through
# typeset -p. This way when we run eval we get something along the lines of:
# eval "
# typeset stdout=' M async.test.sh\n M async.zsh'
# typeset ret=0
# typeset stderr=''
# "
unset stdout stderr ret
eval "$(
{
stdout=$(eval "$@")
ret=$?
typeset -p stdout ret
} 2> >(stderr=$(cat); typeset -p stderr)
)"
# Run the command and capture both stdout (`eval`) and stderr (`cat`) in
# separate subshells. When the command is complete, we grab write lock
# (mutex token) and output everything except stderr inside the command
# block, after the command block has completed, the stdin for `cat` is
# closed, causing stderr to be appended with a $'\0' at the end to mark the
# end of output from this job.
local stdout stderr ret tok
{
stdout=$(eval "$@")
ret=$?
duration=$(( EPOCHREALTIME - duration )) # Calculate duration.
# Calculate duration
duration=$(( EPOCHREALTIME - duration ))
# Grab mutex lock, stalls until token is available.
read -r -k 1 -p tok || exit 1
# stip all null-characters from stdout and stderr
stdout=${stdout//$'\0'/}
stderr=${stderr//$'\0'/}
# Return output (<job_name> <return_code> <stdout> <duration> <stderr>).
print -r -n - ${(q)1} $ret ${(q)stdout} $duration
} 2> >(stderr=$(cat) && print -r -n - " "${(q)stderr}$'\0')
# if ret is missing for some unknown reason, set it to -1 to indicate we
# have run into a bug
ret=${ret:--1}
# Grab mutex lock
read -ep >/dev/null
# return output (<job_name> <return_code> <stdout> <duration> <stderr>)
print -r -N -n -- "$1" "$ret" "$stdout" "$duration" "$stderr"$'\0'
# Unlock mutex
print -p "t"
# Unlock mutex by inserting a token.
print -n -p $tok
}
# The background worker manages all tasks and runs them without interfering with other processes
_async_worker() {
# Reset all options to defaults inside async worker.
emulate -R zsh
# Make sure monitor is unset to avoid printing the
# pids of child processes.
unsetopt monitor
# Redirect stderr to `/dev/null` in case unforseen errors produced by the
# worker. For example: `fork failed: resource temporarily unavailable`.
# Some older versions of zsh might also print malloc errors (know to happen
# on at least zsh 5.0.2 and 5.0.8) likely due to kill signals.
exec 2>/dev/null
# When a zpty is deleted (using -d) all the zpty instances created before
# the one being deleted receive a SIGHUP, unless we catch it, the async
# worker would simply exit (stop working) even though visible in the list
# of zpty's (zpty -L).
TRAPHUP() {
return 0 # Return 0, indicating signal was handled.
}
local -A storage
local unique=0
local notify_parent=0
local parent_pid=0
local coproc_pid=0
local processing=0
local -a zsh_hooks zsh_hook_functions
zsh_hooks=(chpwd periodic precmd preexec zshexit zshaddhistory)
zsh_hook_functions=(${^zsh_hooks}_functions)
unfunction $zsh_hooks &>/dev/null # Deactivate all zsh hooks inside the worker.
unset $zsh_hook_functions # And hooks with registered functions.
unset zsh_hooks zsh_hook_functions # Cleanup.
child_exit() {
local -a pids
pids=(${${(v)jobstates##*:*:}%\=*})
# If coproc (cat) is the only child running, we close it to avoid
# leaving it running indefinitely and cluttering the process tree.
if (( ! processing )) && [[ $#pids = 1 ]] && [[ $coproc_pid = $pids[1] ]]; then
coproc :
coproc_pid=0
fi
# On older version of zsh (pre 5.2) we notify the parent through a
# SIGWINCH signal because `zpty` did not return a file descriptor (fd)
# prior to that.
if (( notify_parent )); then
# We use SIGWINCH for compatibility with older versions of zsh
# (pre 5.1.1) where other signals (INFO, ALRM, USR1, etc.) could
# cause a deadlock in the shell under certain circumstances.
kill -WINCH $parent_pid
fi
}
# Register a SIGCHLD trap to handle the completion of child processes.
trap child_exit CHLD
# Process option parameters passed to worker
while getopts "np:u" opt; do
case $opt in
# Use SIGWINCH since many others seem to cause zsh to freeze, e.g. ALRM, INFO, etc.
n) trap 'kill -WINCH $ASYNC_WORKER_PARENT_PID' CHLD;;
p) ASYNC_WORKER_PARENT_PID=$OPTARG;;
n) notify_parent=1;;
p) parent_pid=$OPTARG;;
u) unique=1;;
esac
done
# Create a mutex for writing to the terminal through coproc
coproc cat
# Insert token into coproc
print -p "t"
killjobs() {
local tok
local -a pids
pids=(${${(v)jobstates##*:*:}%\=*})
while read -r cmd; do
# Separate on spaces into an array
cmd=(${=cmd})
local job=$cmd[1]
# No need to send SIGHUP if no jobs are running.
(( $#pids == 0 )) && continue
(( $#pids == 1 )) && [[ $coproc_pid = $pids[1] ]] && continue
# Grab lock to prevent half-written output in case a child
# process is in the middle of writing to stdin during kill.
(( coproc_pid )) && read -r -k 1 -p tok
kill -HUP -$$ # Send to entire process group.
coproc : # Quit coproc.
coproc_pid=0 # Reset pid.
}
local request
local -a cmd
while :; do
# Wait for jobs sent by async_job.
read -r -d $'\0' request || {
# Since we handle SIGHUP above (and thus do not know when `zpty -d`)
# occurs, a failure to read probably indicates that stdin has
# closed. This is why we propagate the signal to all children and
# exit manually.
kill -HUP -$$ # Send SIGHUP to all jobs.
exit 0
}
# Check for non-job commands sent to worker
case $job in
_unset_trap)
trap - CHLD; continue;;
_killjobs)
# Do nothing in the worker when receiving the TERM signal
trap '' TERM
# Send TERM to the entire process group (PID and all children)
kill -TERM -$$ &>/dev/null
# Reset trap
trap - TERM
continue
;;
case $request in
_unset_trap) notify_parent=0; continue;;
_killjobs) killjobs; continue;;
esac
# Parse the request using shell parsing (z) to allow commands
# to be parsed from single strings and multi-args alike.
cmd=("${(z)request}")
# Name of the job (first argument).
local job=$cmd[1]
# If worker should perform unique jobs
if (( unique )); then
# Check if a previous job is still running, if yes, let it finnish
@@ -103,10 +167,25 @@ _async_worker() {
done
fi
# Run task in background
# Guard against closing coproc from trap before command has started.
processing=1
# Because we close the coproc after the last job has completed, we must
# recreate it when there are no other jobs running.
if (( ! coproc_pid )); then
# Use coproc as a mutex for synchronized output between children.
coproc cat
coproc_pid="$!"
# Insert token into coproc
print -n -p "t"
fi
# Run job in background, completed jobs are printed to stdout.
_async_job $cmd &
# Store pid because zsh job manager is extremely unflexible (show jobname as non-unique '$job')...
storage[$job]=$!
storage[$job]="$!"
processing=0 # Disable guard.
done
}
@@ -127,38 +206,55 @@ _async_worker() {
async_process_results() {
setopt localoptions noshwordsplit
integer count=0
local worker=$1
local callback=$2
local caller=$3
local -a items
local IFS=$'\0'
local null=$'\0' data
integer -l len pos num_processed
typeset -gA ASYNC_PROCESS_BUFFER
# Read output from zpty and parse it if available
while zpty -rt $worker line 2>/dev/null; do
# Remove unwanted \r from output
ASYNC_PROCESS_BUFFER[$worker]+=${line//$'\r'$'\n'/$'\n'}
# Split buffer on null characters, preserve empty elements
items=("${(@)=ASYNC_PROCESS_BUFFER[$worker]}")
# Remove last element since it's due to the return string separator structure
items=("${(@)items[1,${#items}-1]}")
# Continue until we receive all information
(( ${#items} % 5 )) && continue
# Read output from zpty and parse it if available.
while zpty -r -t $worker data 2>/dev/null; do
ASYNC_PROCESS_BUFFER[$worker]+=$data
len=${#ASYNC_PROCESS_BUFFER[$worker]}
pos=${ASYNC_PROCESS_BUFFER[$worker][(i)$null]} # Get index of NULL-character (delimiter).
# Work through all results
while (( ${#items} > 0 )); do
$callback "${(@)=items[1,5]}"
shift 5 items
count+=1
# Keep going until we find a NULL-character.
if (( ! len )) || (( pos > len )); then
continue
fi
while (( pos <= len )); do
# Take the content from the beginning, until the NULL-character and
# perform shell parsing (z) and unquoting (Q) as an array (@).
items=("${(@Q)${(z)ASYNC_PROCESS_BUFFER[$worker][1,$pos-1]}}")
# Remove the extracted items from the buffer.
ASYNC_PROCESS_BUFFER[$worker]=${ASYNC_PROCESS_BUFFER[$worker][$pos+1,$len]}
if (( $#items == 5 )); then
$callback "${(@)items}" # Send all parsed items to the callback.
else
# In case of corrupt data, invoke callback with *async* as job
# name, non-zero exit status and an error message on stderr.
$callback "async" 1 "" 0 "$0:$LINENO: error: bad format, got ${#items} items (${(@q)items})"
fi
(( num_processed++ ))
len=${#ASYNC_PROCESS_BUFFER[$worker]}
if (( len > 1 )); then
pos=${ASYNC_PROCESS_BUFFER[$worker][(i)$null]} # Get index of NULL-character (delimiter).
fi
done
# Empty the buffer
unset "ASYNC_PROCESS_BUFFER[$worker]"
done
# If we processed any results, return success
(( count )) && return 0
(( num_processed )) && return 0
# Avoid printing exit value when `setopt printexitvalue` is active.`
[[ $caller = trap || $caller = watcher ]] && return 0
# No results were processed
return 1
@@ -172,7 +268,7 @@ _async_zle_watcher() {
local callback=$ASYNC_CALLBACKS[$worker]
if [[ -n $callback ]]; then
async_process_results $worker $callback
async_process_results $worker $callback watcher
fi
}
@@ -186,7 +282,14 @@ async_job() {
setopt localoptions noshwordsplit
local worker=$1; shift
zpty -w $worker $@
local -a cmd
cmd=("$@")
if (( $#cmd > 1 )); then
cmd=(${(q)cmd}) # Quote special characters in multi argument commands.
fi
zpty -w $worker $cmd$'\0'
}
# This function traps notification signals and calls all registered callbacks
@@ -194,7 +297,7 @@ _async_notify_trap() {
setopt localoptions noshwordsplit
for k in ${(k)ASYNC_CALLBACKS}; do
async_process_results $k ${ASYNC_CALLBACKS[$k]}
async_process_results $k ${ASYNC_CALLBACKS[$k]} trap
done
}
@@ -213,7 +316,9 @@ async_register_callback() {
ASYNC_CALLBACKS[$worker]="$*"
if (( ! ASYNC_USE_ZLE_HANDLER )); then
# Enable trap when the ZLE watcher is unavailable, allows
# workers to notify (via -n) when a job is done.
if [[ ! -o interactive ]] || [[ ! -o zle ]]; then
trap '_async_notify_trap' WINCH
fi
}
@@ -246,12 +351,19 @@ async_flush_jobs() {
zpty -t $worker &>/dev/null || return 1
# Send kill command to worker
zpty -w $worker "_killjobs"
async_job $worker "_killjobs"
# Clear all output buffers
while zpty -r $worker line; do true; done
# Clear the zpty buffer.
local junk
if zpty -r -t $worker junk '*'; then
(( ASYNC_DEBUG )) && print -n "async_flush_jobs $worker: ${(V)junk}"
while zpty -r -t $worker junk '*'; do
(( ASYNC_DEBUG )) && print -n "${(V)junk}"
done
(( ASYNC_DEBUG )) && print
fi
# Clear any partial buffers
# Finally, clear the process buffer in case of partially parsed responses.
typeset -gA ASYNC_PROCESS_BUFFER
unset "ASYNC_PROCESS_BUFFER[$worker]"
}
@@ -276,16 +388,47 @@ async_start_worker() {
typeset -gA ASYNC_PTYS
typeset -h REPLY
typeset has_xtrace=0
# Make sure async worker is started without xtrace
# (the trace output interferes with the worker).
[[ -o xtrace ]] && {
has_xtrace=1
unsetopt xtrace
}
if (( ! ASYNC_ZPTY_RETURNS_FD )) && [[ -o interactive ]] && [[ -o zle ]]; then
# When zpty doesn't return a file descriptor (on older versions of zsh)
# we try to guess it anyway.
integer -l zptyfd
exec {zptyfd}>&1 # Open a new file descriptor (above 10).
exec {zptyfd}>&- # Close it so it's free to be used by zpty.
fi
zpty -b $worker _async_worker -p $$ $@ || {
async_stop_worker $worker
return 1
}
if (( ASYNC_USE_ZLE_HANDLER )); then
ASYNC_PTYS[$REPLY]=$worker
zle -F $REPLY _async_zle_watcher
# Re-enable it if it was enabled, for debugging.
(( has_xtrace )) && setopt xtrace
# If worker was called with -n, disable trap in favor of zle handler
if [[ $ZSH_VERSION < 5.0.8 ]]; then
# For ZSH versions older than 5.0.8 we delay a bit to give
# time for the worker to start before issuing commands,
# otherwise it will not be ready to receive them.
sleep 0.001
fi
if [[ -o interactive ]] && [[ -o zle ]]; then
if (( ! ASYNC_ZPTY_RETURNS_FD )); then
REPLY=$zptyfd # Use the guessed value for the file desciptor.
fi
ASYNC_PTYS[$REPLY]=$worker # Map the file desciptor to the worker.
zle -F $REPLY _async_zle_watcher # Register the ZLE handler.
# Disable trap in favor of ZLE handler when notify is enabled (-n).
async_job $worker _unset_trap
fi
}
@@ -310,6 +453,10 @@ async_stop_worker() {
done
async_unregister_callback $worker
zpty -d $worker 2>/dev/null || ret=$?
# Clear any partial buffers.
typeset -gA ASYNC_PROCESS_BUFFER
unset "ASYNC_PROCESS_BUFFER[$worker]"
done
return $ret
@@ -328,12 +475,13 @@ async_init() {
zmodload zsh/zpty
zmodload zsh/datetime
# Check if zsh/zpty returns a file descriptor or not, shell must also be interactive
ASYNC_USE_ZLE_HANDLER=0
[[ -o interactive ]] && {
# Check if zsh/zpty returns a file descriptor or not,
# shell must also be interactive with zle enabled.
ASYNC_ZPTY_RETURNS_FD=0
[[ -o interactive ]] && [[ -o zle ]] && {
typeset -h REPLY
zpty _async_test cat
(( REPLY )) && ASYNC_USE_ZLE_HANDLER=1
zpty _async_test :
(( REPLY )) && ASYNC_ZPTY_RETURNS_FD=1
zpty -d _async_test
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "pure-prompt",
"version": "1.3.0",
"version": "1.5.0",
"description": "Pretty, minimal and fast ZSH prompt",
"license": "MIT",
"repository": "sindresorhus/pure",
@@ -19,7 +19,7 @@
"node": ">=0.10.0"
},
"scripts": {
"postinstall": "dest=/usr/local/share/zsh/site-functions/; mkdir -p $dest && ln -sf \"$PWD/pure.zsh\" $dest/prompt_pure_setup && ln -sf \"$PWD/async.zsh\" $dest/async || echo 'Could not automagically symlink the prompt. Check out the readme on how to do it manually: https://github.com/sindresorhus/pure#manually'"
"postinstall": "dest=/usr/local/share/zsh/site-functions; mkdir -p $dest && ln -sf \"$PWD/pure.zsh\" $dest/prompt_pure_setup && ln -sf \"$PWD/async.zsh\" $dest/async || echo 'Could not automagically symlink the prompt. Check out the readme on how to do it manually: https://github.com/sindresorhus/pure#manually'"
},
"files": [
"pure.zsh",

156
pure.zsh
View File

@@ -61,30 +61,10 @@ prompt_pure_clear_screen() {
prompt_pure_preprompt_render precmd
}
prompt_pure_check_git_arrows() {
# reset git arrows
prompt_pure_git_arrows=
# check if there is an upstream configured for this branch
command git rev-parse --abbrev-ref @'{u}' &>/dev/null || return
local arrow_status
# check git left and right arrow_status
arrow_status="$(command git rev-list --left-right --count HEAD...@'{u}' 2>/dev/null)"
# exit if the command failed
(( !$? )) || return
# left and right are tab-separated, split on tab and store as array
arrow_status=(${(ps:\t:)arrow_status})
local arrows left=${arrow_status[1]} right=${arrow_status[2]}
(( ${right:-0} > 0 )) && arrows+="${PURE_GIT_DOWN_ARROW:-}"
(( ${left:-0} > 0 )) && arrows+="${PURE_GIT_UP_ARROW:-}"
[[ -n $arrows ]] && prompt_pure_git_arrows=" ${arrows}"
}
prompt_pure_set_title() {
# emacs terminal does not support settings the title
(( ${+EMACS} )) && return
# tell the terminal we are setting the title
print -n '\e]0;'
# show hostname if connected through ssh
@@ -120,6 +100,12 @@ prompt_pure_string_length_to_var() {
}
prompt_pure_preprompt_render() {
# store the current prompt_subst setting so that it can be restored later
local prompt_subst_status=$options[prompt_subst]
# make sure prompt_subst is unset to prevent parameter expansion in preprompt
setopt local_options no_prompt_subst
# check that no command is currently running, the preprompt will otherwise be rendered in the wrong place
[[ -n ${prompt_pure_cmd_timestamp+x} && "$1" != "precmd" ]] && return
@@ -138,12 +124,15 @@ prompt_pure_preprompt_render() {
# execution time
preprompt+="%F{yellow}${prompt_pure_cmd_exec_time}%f"
# make sure prompt_pure_last_preprompt is a global array
typeset -g -a prompt_pure_last_preprompt
# if executing through precmd, do not perform fancy terminal editing
if [[ "$1" == "precmd" ]]; then
print -P "${preprompt}"
else
# only redraw if preprompt has changed
[[ "${prompt_pure_last_preprompt}" != "${preprompt}" ]] || return
# only redraw if the expanded preprompt has changed
[[ "${prompt_pure_last_preprompt[2]}" != "${(S%%)preprompt}" ]] || return
# calculate length of preprompt and store it locally in preprompt_length
integer preprompt_length lines
@@ -154,7 +143,7 @@ prompt_pure_preprompt_render() {
# calculate previous preprompt lines to figure out how the new preprompt should behave
integer last_preprompt_length last_lines
prompt_pure_string_length_to_var "${prompt_pure_last_preprompt}" "last_preprompt_length"
prompt_pure_string_length_to_var "${prompt_pure_last_preprompt[1]}" "last_preprompt_length"
(( last_lines = ( last_preprompt_length - 1 ) / COLUMNS + 1 ))
# clr_prev_preprompt erases visual artifacts from previous preprompt
@@ -174,9 +163,6 @@ prompt_pure_preprompt_render() {
elif (( last_lines < lines )); then
# move cursor using newlines because ansi cursor movement can't push the cursor beyond the last line
printf $'\n'%.0s {1..$(( lines - last_lines ))}
# redraw the prompt since it has been moved by print
zle && zle .reset-prompt
fi
# disable clearing of line if last char of preprompt is last column of terminal
@@ -184,11 +170,19 @@ prompt_pure_preprompt_render() {
(( COLUMNS * lines == preprompt_length )) && clr=
# modify previous preprompt
print -Pn "\e7${clr_prev_preprompt}\e[${lines}A\e[1G${preprompt}${clr}\e8"
print -Pn "${clr_prev_preprompt}\e[${lines}A\e[${COLUMNS}D${preprompt}${clr}\n"
if [[ $prompt_subst_status = 'on' ]]; then
# re-eanble prompt_subst for expansion on PS1
setopt prompt_subst
fi
# redraw prompt (also resets cursor position)
zle && zle .reset-prompt
fi
# store previous preprompt for comparison
prompt_pure_last_preprompt=$preprompt
# store both unexpanded and expanded preprompt for comparison
prompt_pure_last_preprompt=("$preprompt" "${(S%%)preprompt}")
}
prompt_pure_precmd() {
@@ -199,9 +193,6 @@ prompt_pure_precmd() {
# with the initial preprompt rendering
prompt_pure_cmd_timestamp=
# check for git arrows
prompt_pure_check_git_arrows
# shows the full path in the title
prompt_pure_set_title 'expand-prompt' '%~'
@@ -220,29 +211,46 @@ prompt_pure_precmd() {
# fastest possible way to check if repo is dirty
prompt_pure_async_git_dirty() {
local untracked_dirty=$1; shift
setopt localoptions noshwordsplit
local untracked_dirty=$1 dir=$2
# use cd -q to avoid side effects of changing directory, e.g. chpwd hooks
cd -q "$*"
builtin cd -q $dir
if [[ "$untracked_dirty" == "0" ]]; then
if [[ $untracked_dirty = 0 ]]; then
command git diff --no-ext-diff --quiet --exit-code
else
test -z "$(command git status --porcelain --ignore-submodules -unormal)"
fi
(( $? )) && echo "%F{red}*%f"
return $?
}
prompt_pure_async_git_fetch() {
setopt localoptions noshwordsplit
# use cd -q to avoid side effects of changing directory, e.g. chpwd hooks
cd -q "$*"
builtin cd -q $1
# set GIT_TERMINAL_PROMPT=0 to disable auth prompting for git fetch (git 2.3+)
GIT_TERMINAL_PROMPT=0 command git -c gc.auto=0 fetch
export GIT_TERMINAL_PROMPT=0
# set ssh BachMode to disable all interactive ssh password prompting
export GIT_SSH_COMMAND=${GIT_SSH_COMMAND:-"ssh -o BatchMode=yes"}
command git -c gc.auto=0 fetch &>/dev/null || return 1
# check arrow status after a successful git fetch
prompt_pure_async_git_arrows $1
}
prompt_pure_async_git_arrows() {
setopt localoptions noshwordsplit
builtin cd -q $1
command git rev-list --left-right --count HEAD...@'{u}'
}
prompt_pure_async_tasks() {
setopt localoptions noshwordsplit
# initialize async worker
((!${prompt_pure_async_init:-0})) && {
async_start_worker "prompt_pure" -u -n
@@ -261,6 +269,7 @@ prompt_pure_async_tasks() {
# reset git preprompt variables, switching working tree
unset prompt_pure_git_dirty
unset prompt_pure_git_last_dirty_check_timestamp
prompt_pure_git_arrows=
# set the new working tree and prefix with "x" to prevent the creation of a named path by AUTO_NAME_DIRS
prompt_pure_current_working_tree="x${working_tree}"
@@ -269,10 +278,12 @@ prompt_pure_async_tasks() {
# only perform tasks inside git working tree
[[ -n $working_tree ]] || return
async_job "prompt_pure" prompt_pure_async_git_arrows $working_tree
# do not preform git fetch if it is disabled or working_tree == HOME
if (( ${PURE_GIT_PULL:-1} )) && [[ $working_tree != $HOME ]]; then
# tell worker to do a git fetch
async_job "prompt_pure" prompt_pure_async_git_fetch "${working_tree}"
async_job "prompt_pure" prompt_pure_async_git_fetch $working_tree
fi
# if dirty checking is sufficiently fast, tell worker to check it again, or wait for timeout
@@ -280,41 +291,76 @@ prompt_pure_async_tasks() {
if (( time_since_last_dirty_check > ${PURE_GIT_DELAY_DIRTY_CHECK:-1800} )); then
unset prompt_pure_git_last_dirty_check_timestamp
# check check if there is anything to pull
async_job "prompt_pure" prompt_pure_async_git_dirty "${PURE_GIT_UNTRACKED_DIRTY:-1}" "${working_tree}"
async_job "prompt_pure" prompt_pure_async_git_dirty ${PURE_GIT_UNTRACKED_DIRTY:-1} $working_tree
fi
}
prompt_pure_async_callback() {
local job=$1
local output=$3
local exec_time=$4
prompt_pure_check_git_arrows() {
setopt localoptions noshwordsplit
local arrows left=${1:-0} right=${2:-0}
case "${job}" in
(( right > 0 )) && arrows+=${PURE_GIT_DOWN_ARROW:-}
(( left > 0 )) && arrows+=${PURE_GIT_UP_ARROW:-}
[[ -n $arrows ]] || return
typeset -g REPLY=" $arrows"
}
prompt_pure_async_callback() {
setopt localoptions noshwordsplit
local job=$1 code=$2 output=$3 exec_time=$4
case $job in
prompt_pure_async_git_dirty)
prompt_pure_git_dirty=$output
prompt_pure_preprompt_render
local prev_dirty=$prompt_pure_git_dirty
if (( code == 0 )); then
prompt_pure_git_dirty=
else
prompt_pure_git_dirty="%F{red}*%f"
fi
[[ $prev_dirty != $prompt_pure_git_dirty ]] && prompt_pure_preprompt_render
# When prompt_pure_git_last_dirty_check_timestamp is set, the git info is displayed in a different color.
# To distinguish between a "fresh" and a "cached" result, the preprompt is rendered before setting this
# variable. Thus, only upon next rendering of the preprompt will the result appear in a different color.
(( $exec_time > 2 )) && prompt_pure_git_last_dirty_check_timestamp=$EPOCHSECONDS
;;
prompt_pure_async_git_fetch)
prompt_pure_check_git_arrows
prompt_pure_preprompt_render
prompt_pure_async_git_fetch|prompt_pure_async_git_arrows)
# prompt_pure_async_git_fetch executes prompt_pure_async_git_arrows
# after a successful fetch.
if (( code == 0 )); then
local REPLY
prompt_pure_check_git_arrows ${(ps:\t:)output}
if [[ $prompt_pure_git_arrows != $REPLY ]]; then
prompt_pure_git_arrows=$REPLY
prompt_pure_preprompt_render
fi
fi
;;
esac
}
prompt_pure_setup() {
local autoload_name=$1; shift
# prevent percentage showing up
# if output doesn't end with a newline
export PROMPT_EOL_MARK=''
prompt_opts=(subst percent)
# if autoload_name or eval context differ, pure wasn't autoloaded via
# promptinit and we need to take care of setting the options ourselves
if [[ $autoload_name != prompt_pure_setup ]] || [[ $zsh_eval_context[-2] != loadautofunc ]]; then
# borrowed from `promptinit`, set the pure prompt options
setopt noprompt{bang,cr,percent,subst} "prompt${^prompt_opts[@]}"
fi
zmodload zsh/datetime
zmodload zsh/zle
zmodload zsh/parameter
autoload -Uz add-zsh-hook
autoload -Uz vcs_info
autoload -Uz async && async
@@ -345,7 +391,7 @@ prompt_pure_setup() {
[[ $UID -eq 0 ]] && prompt_pure_username=' %F{white}%n%f%F{242}@%m%f'
# prompt turns red if the previous command didn't exit with 0
PROMPT="%(?.%F{magenta}.%F{red})${PURE_PROMPT_SYMBOL:-}%f "
PROMPT='%(?.%F{magenta}.%F{red})${PURE_PROMPT_SYMBOL:-}%f '
}
prompt_pure_setup "$@"
prompt_pure_setup "$0" "$@"

View File

@@ -2,7 +2,7 @@
> Pretty, minimal and fast ZSH prompt
![](screenshot.png)
<img src="screenshot.png" width="864">
## Overview
@@ -11,10 +11,10 @@ Most prompts are cluttered, ugly and slow. I wanted something visually pleasing
### Why?
- Comes with the perfect prompt character.
- 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.
- 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.
@@ -24,11 +24,11 @@ Most prompts are cluttered, ugly and slow. I wanted something visually pleasing
## Install
Can be installed with `npm` or manually. Requires git 2.0.0+ and ZSH 5.0.0+.
Can be installed with `npm` or manually. Requires Git 2.0.0+ and ZSH 5.2+. Older versions of ZSH are known to work, but they are **not** recommended.
### npm
```
```console
$ npm install --global pure-prompt
```
@@ -39,7 +39,7 @@ That's it. Skip to [Getting started](#getting-started).
1. Either…
- Clone this repo
- add it as a submodule, or
- just download `pure.zsh`
- just download `pure.zsh` and `async.zsh`
2. Symlink `pure.zsh` to somewhere in [`$fpath`](http://www.refining-linux.org/archives/46/ZSH-Gem-12-Autoloading-functions/) with the name `prompt_pure_setup`.
@@ -47,7 +47,7 @@ That's it. Skip to [Getting started](#getting-started).
#### Example
```
```console
$ ln -s "$PWD/pure.zsh" /usr/local/share/zsh/site-functions/prompt_pure_setup
$ ln -s "$PWD/async.zsh" /usr/local/share/zsh/site-functions/async
```
@@ -62,7 +62,7 @@ fpath=( "$HOME/.zfunctions" $fpath )
Then install the theme there:
```sh
```console
$ ln -s "$PWD/pure.zsh" "$HOME/.zfunctions/prompt_pure_setup"
$ ln -s "$PWD/async.zsh" "$HOME/.zfunctions/async"
```
@@ -74,7 +74,7 @@ Initialize the prompt system (if not so already) and choose `pure`:
```sh
# .zshrc
autoload -U promptinit && promptinit
autoload -U promptinit; promptinit
prompt pure
```
@@ -114,7 +114,7 @@ Defines the git up arrow symbol. The default value is `⇡`.
```sh
# .zshrc
autoload -U promptinit && promptinit
autoload -U promptinit; promptinit
# optionally define some options
PURE_CMD_MAX_EXEC_TIME=10
@@ -125,33 +125,66 @@ prompt pure
## Tips
[Tomorrow Night Eighties](https://github.com/chriskempson/tomorrow-theme) theme with the [Droid Sans Mono](http://www.google.com/webfonts/specimen/Droid+Sans+Mono) font (15pt) is a beautiful combination, as seen in the screenshot above. Just make sure you have anti-aliasing enabled in your Terminal.
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.
To have commands colorized as seen in the screenshot install [zsh-syntax-highlighting](https://github.com/zsh-users/zsh-syntax-highlighting).
The [Tomorrow Night Eighties](https://github.com/chriskempson/tomorrow-theme) theme with the [Droid Sans Mono](https://fonts.google.com/specimen/Droid+Sans+Mono) font (15pt) is also a [nice combination](https://github.com/sindresorhus/pure/blob/95ee3e7618c6e2162a1e3cdac2a88a20ac3beb27/screenshot.png).<br>
*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. Remove competing theme included in oh-my-zsh `~/.oh-my-zsh/themes/pure.zsh-theme`
2. Symlink (or copy) `pure.zsh` to `~/.oh-my-zsh/custom/pure.zsh-theme`
3. Symlink (or copy) `async.zsh` to `~/.oh-my-zsh/custom/async.zsh`
4. Add `ZSH_THEME="pure"` to your `.zshrc` file.
1. Symlink (or copy) `pure.zsh` to `~/.oh-my-zsh/custom/pure.zsh-theme`.
2. Symlink (or copy) `async.zsh` to `~/.oh-my-zsh/custom/async.zsh`.
3. Set `ZSH_THEME="pure"` in your `.zshrc` file.
### [prezto](https://github.com/sorin-ionescu/prezto)
Or skip the `oh-my-zsh` integration above and simply:
1. Set `ZSH_THEME=""` in your `.zshrc` to disable oh-my-zsh themes.
2. Follow the Pure [Install](#install) instructions.
### [prezto](https://github.com/zsh-users/prezto)
Pure is bundled with Prezto. No need to install it.
Set `zstyle ':prezto:module:prompt' theme 'pure'` in `~/.zpreztorc`.
### [zim](https://github.com/Eriner/zim)
Pure is bundled with Zim. No need to install it.
Set `zprompt_theme='pure'` in `~/.zimrc`.
### [antigen](https://github.com/zsh-users/antigen)
Update your `.zshrc` file with the following two lines (order matters). Do not use the `antigen theme` function.
```
```sh
antigen bundle mafredri/zsh-async
antigen bundle sindresorhus/pure
```
### [antibody](https://github.com/getantibody/antibody)
Update your `.zshrc` file with the following two lines (order matters):
```sh
antibody bundle mafredri/zsh-async
antibody bundle sindresorhus/pure
```
### [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
```
## FAQ
### My preprompt is missing when I clear the screen with Ctrl+L
@@ -171,15 +204,26 @@ Using `git pull` when you get the username prompt should help you to break the l
#### Gentoo
```
sudo sh -c "echo 'SANDBOX_WRITE=\"/dev/ptmx\"' > /etc/sandbox.d/10zsh"
sudo emerge -1 zsh
```console
$ sudo sh -c "echo 'SANDBOX_WRITE=\"/dev/ptmx\"' > /etc/sandbox.d/10zsh"
$ sudo emerge -1 zsh
```
#### FreeBSD 10.1
On a default setup, running the command `kldload pty` should do the trick. If you have a custom kernel, you might need to add `device pty` to the configuration file ([example](https://github.com/nbari/freebsd/blob/58646a9c3c4aaabf6f6467ff505f27f09e29dc75/kernels/xen.kernel#L188)).
## Ports
* **Bash**
* [sapegin/dotfiles](https://github.com/sapegin/dotfiles)s [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**
* [brandonweiss/pure.fish](https://github.com/brandonweiss/pure.fish): a Pure-inspired prompt for Fish, not intended to have feature parity.
* [rafaelrinaldi/pure](https://github.com/rafaelrinaldi/pure), support for bare Fish and various framework ([Oh-My-Fish](https://github.com//oh-my-fish/oh-my-fish), [Fisherman](https://github.com//fisherman/fisherman) and [Wahoo](https://github.com//bucaran/wahoo)).
* **Zsh**
* [therealklanni/purity](https://github.com/therealklanni/purity): a 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
## Team
[![Sindre Sorhus](https://avatars.githubusercontent.com/u/170270?v=3&s=100)](http://sindresorhus.com) | [![Mathias Fredriksson](https://avatars.githubusercontent.com/u/147409?v=3&s=100)](https://github.com/mafredri)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 70 KiB