diff --git a/async.zsh b/async.zsh index 0a0f1b9..c891f11 100644 --- a/async.zsh +++ b/async.zsh @@ -3,7 +3,7 @@ # # zsh-async # -# version: 1.0.0 +# version: 1.1.0 # author: Mathias Fredriksson # url: https://github.com/mafredri/zsh-async # @@ -36,8 +36,8 @@ _async_job() { duration=$(( EPOCHREALTIME - duration )) # stip all null-characters from stdout and stderr - stdout="${stdout//$'\0'/}" - stderr="${stderr//$'\0'/}" + stdout=${stdout//$'\0'/} + stderr=${stderr//$'\0'/} # if ret is missing for some unknown reason, set it to -1 to indicate we # have run into a bug @@ -60,11 +60,11 @@ _async_worker() { # 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;; - u) unique=1;; + 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;; + u) unique=1;; esac done @@ -79,26 +79,33 @@ _async_worker() { local job=$cmd[1] # Check for non-job commands sent to worker - case "$job" in - _killjobs) - kill -KILL ${${(v)jobstates##*:*:}%\=*} &>/dev/null - continue - ;; + 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 + ;; esac # If worker should perform unique jobs - if ((unique)); then + if (( unique )); then # Check if a previous job is still running, if yes, let it finnish for pid in ${${(v)jobstates##*:*:}%\=*}; do - if [[ "${storage[$job]}" == "$pid" ]]; then + if [[ ${storage[$job]} == $pid ]]; then continue 2 fi done fi - # run task in background + # Run task in background _async_job $cmd & - # store pid because zsh job manager is extremely unflexible (show jobname as non-unique '$job')... + # Store pid because zsh job manager is extremely unflexible (show jobname as non-unique '$job')... storage[$job]=$! done } @@ -118,6 +125,8 @@ _async_worker() { # $5 = resulting stderr from execution # async_process_results() { + setopt localoptions noshwordsplit + integer count=0 local worker=$1 local callback=$2 @@ -126,7 +135,7 @@ async_process_results() { typeset -gA ASYNC_PROCESS_BUFFER # Read output from zpty and parse it if available - while zpty -rt "$worker" line 2>/dev/null; do + 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 @@ -139,22 +148,34 @@ async_process_results() { # Work through all results while (( ${#items} > 0 )); do - "$callback" "${(@)=items[1,5]}" + $callback "${(@)=items[1,5]}" shift 5 items count+=1 done # Empty the buffer - ASYNC_PROCESS_BUFFER[$worker]="" + unset "ASYNC_PROCESS_BUFFER[$worker]" done # If we processed any results, return success - (( $count )) && return 0 + (( count )) && return 0 # No results were processed return 1 } +# Watch worker for output +_async_zle_watcher() { + setopt localoptions noshwordsplit + typeset -gA ASYNC_PTYS ASYNC_CALLBACKS + local worker=$ASYNC_PTYS[$1] + local callback=$ASYNC_CALLBACKS[$worker] + + if [[ -n $callback ]]; then + async_process_results $worker $callback + fi +} + # # Start a new asynchronous job on specified worker, assumes the worker is running. # @@ -162,14 +183,18 @@ async_process_results() { # async_job [] # async_job() { + setopt localoptions noshwordsplit + local worker=$1; shift - zpty -w "$worker" "$@" + zpty -w $worker $@ } # This function traps notification signals and calls all registered callbacks _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]} done } @@ -181,12 +206,16 @@ _async_notify_trap() { # async_register_callback # async_register_callback() { + setopt localoptions noshwordsplit nolocaltraps + typeset -gA ASYNC_CALLBACKS local worker=$1; shift ASYNC_CALLBACKS[$worker]="$*" - trap '_async_notify_trap' WINCH + if (( ! ASYNC_USE_ZLE_HANDLER )); then + trap '_async_notify_trap' WINCH + fi } # @@ -209,20 +238,22 @@ async_unregister_callback() { # async_flush_jobs # async_flush_jobs() { + setopt localoptions noshwordsplit + local worker=$1; shift # Check if the worker exists - zpty -t "$worker" &>/dev/null || return 1 + zpty -t $worker &>/dev/null || return 1 # Send kill command to worker - zpty -w "$worker" "_killjobs" + zpty -w $worker "_killjobs" # Clear all output buffers - while zpty -r "$worker" line; do true; done + while zpty -r $worker line; do true; done # Clear any partial buffers typeset -gA ASYNC_PROCESS_BUFFER - ASYNC_PROCESS_BUFFER[$worker]="" + unset "ASYNC_PROCESS_BUFFER[$worker]" } # @@ -238,8 +269,25 @@ async_flush_jobs() { # -p pid to notify (defaults to current pid) # async_start_worker() { + setopt localoptions noshwordsplit + local worker=$1; shift - zpty -t "$worker" &>/dev/null || zpty -b "$worker" _async_worker -p $$ "$@" || async_stop_worker "$worker" + zpty -t $worker &>/dev/null && return + + typeset -gA ASYNC_PTYS + typeset -h REPLY + 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 + + # If worker was called with -n, disable trap in favor of zle handler + async_job $worker _unset_trap + fi } # @@ -249,10 +297,19 @@ async_start_worker() { # async_stop_worker [] # async_stop_worker() { + setopt localoptions noshwordsplit + local ret=0 - for worker in "$@"; do - async_unregister_callback "$worker" - zpty -d "$worker" 2>/dev/null || ret=$? + for worker in $@; do + # Find and unregister the zle handler for the worker + for k v in ${(@kv)ASYNC_PTYS}; do + if [[ $v == $worker ]]; then + zle -F $k + unset "ASYNC_PTYS[$k]" + fi + done + async_unregister_callback $worker + zpty -d $worker 2>/dev/null || ret=$? done return $ret @@ -265,8 +322,20 @@ async_stop_worker() { # async_init # async_init() { + (( ASYNC_INIT_DONE )) && return + ASYNC_INIT_DONE=1 + 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 ]] && { + typeset -h REPLY + zpty _async_test cat + (( REPLY )) && ASYNC_USE_ZLE_HANDLER=1 + zpty -d _async_test + } } async() { diff --git a/package.json b/package.json index a96668f..a7096f3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pure-prompt", - "version": "1.2.0", + "version": "1.3.0", "description": "Pretty, minimal and fast ZSH prompt", "license": "MIT", "repository": "sindresorhus/pure", diff --git a/pure.zsh b/pure.zsh index 65a79a0..29bf2ea 100644 --- a/pure.zsh +++ b/pure.zsh @@ -84,17 +84,29 @@ prompt_pure_check_git_arrows() { [[ -n $arrows ]] && prompt_pure_git_arrows=" ${arrows}" } +prompt_pure_set_title() { + # tell the terminal we are setting the title + print -n '\e]0;' + # show hostname if connected through ssh + [[ -n $SSH_CONNECTION ]] && print -Pn '(%m) ' + case $1 in + expand-prompt) + print -Pn $2;; + ignore-escape) + print -rn $2;; + esac + # end set title + print -n '\a' +} + prompt_pure_preexec() { + # attempt to detect and prevent prompt_pure_async_git_fetch from interfering with user initiated git or hub fetch + [[ $2 =~ (git|hub)\ .*(pull|fetch) ]] && async_flush_jobs 'prompt_pure' + prompt_pure_cmd_timestamp=$EPOCHSECONDS - # tell the terminal we are setting the title - print -Pn "\e]0;" - # show hostname if connected through ssh - [[ "$SSH_CONNECTION" != '' ]] && print -Pn "(%m) " - # shows the current dir and executed command in the title when a process is active - # (use print -r to disable potential evaluation of escape characters in cmd) - print -nr "$PWD:t: $2" - print -Pn "\a" + # shows the current dir and executed command in the title while a process is active + prompt_pure_set_title 'ignore-escape' "$PWD:t: $2" } # string length ignoring ansi escapes @@ -190,12 +202,8 @@ prompt_pure_precmd() { # check for git arrows prompt_pure_check_git_arrows - # tell the terminal we are setting the title - print -Pn "\e]0;" - # show hostname if connected through ssh - [[ "$SSH_CONNECTION" != '' ]] && print -Pn "(%m) " # shows the full path in the title - print -Pn "%~\a" + prompt_pure_set_title 'expand-prompt' '%~' # get vcs info vcs_info diff --git a/readme.md b/readme.md index a56205e..326a397 100644 --- a/readme.md +++ b/readme.md @@ -134,9 +134,10 @@ To have commands colorized as seen in the screenshot install [zsh-syntax-highlig ### [oh-my-zsh](https://github.com/robbyrussell/oh-my-zsh) -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. Add `ZSH_THEME="pure"` to your `.zshrc` file. +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. ### [prezto](https://github.com/sorin-ionescu/prezto)