Parent directory

async_prompt.zsh

4711 bytes
  1# The async code is taken from
  2# https://github.com/zsh-users/zsh-autosuggestions/blob/master/src/async.zsh
  3# https://github.com/woefe/git-prompt.zsh/blob/master/git-prompt.zsh
  4
  5zmodload zsh/system
  6autoload -Uz is-at-least
  7
  8# For now, async prompt function handlers are set up like so:
  9# First, define the async function handler and register the handler
 10# with _omz_register_handler:
 11#
 12#  function _git_prompt_status_async {
 13#    # Do some expensive operation that outputs to stdout
 14#  }
 15#  _omz_register_handler _git_prompt_status_async
 16#
 17# Then add a stub prompt function in `$PROMPT` or similar prompt variables,
 18# which will show the output of "$_OMZ_ASYNC_OUTPUT[handler_name]":
 19#
 20#  function git_prompt_status {
 21#    echo -n $_OMZ_ASYNC_OUTPUT[_git_prompt_status_async]
 22#  }
 23#
 24#  RPROMPT='$(git_prompt_status)'
 25#
 26# This API is subject to change and optimization. Rely on it at your own risk.
 27
 28function _omz_register_handler {
 29  setopt localoptions noksharrays unset
 30  typeset -ga _omz_async_functions
 31  # we want to do nothing if there's no $1 function or we already set it up
 32  if [[ -z "$1" ]] || (( ! ${+functions[$1]} )) \
 33    || (( ${_omz_async_functions[(Ie)$1]} )); then
 34    return
 35  fi
 36  _omz_async_functions+=("$1")
 37  # let's add the hook to async_request if it's not there yet
 38  if (( ! ${precmd_functions[(Ie)_omz_async_request]} )) \
 39    && (( ${+functions[_omz_async_request]})); then
 40    autoload -Uz add-zsh-hook
 41    add-zsh-hook precmd _omz_async_request
 42  fi
 43}
 44
 45# Set up async handlers and callbacks
 46function _omz_async_request {
 47  setopt localoptions noksharrays unset
 48  local -i ret=$?
 49  typeset -gA _OMZ_ASYNC_FDS _OMZ_ASYNC_PIDS _OMZ_ASYNC_OUTPUT
 50
 51  # executor runs a subshell for all async requests based on key
 52  local handler
 53  for handler in ${_omz_async_functions}; do
 54    (( ${+functions[$handler]} )) || continue
 55
 56    local fd=${_OMZ_ASYNC_FDS[$handler]:--1}
 57    local pid=${_OMZ_ASYNC_PIDS[$handler]:--1}
 58
 59    # If we've got a pending request, cancel it
 60    if (( fd != -1 && pid != -1 )) && { true <&$fd } 2>/dev/null; then
 61      # Close the file descriptor and remove the handler
 62      exec {fd}<&-
 63      zle -F $fd
 64
 65      # Zsh will make a new process group for the child process only if job
 66      # control is enabled (MONITOR option)
 67      if [[ -o MONITOR ]]; then
 68        # Send the signal to the process group to kill any processes that may
 69        # have been forked by the async function handler
 70        kill -TERM -$pid 2>/dev/null
 71      else
 72        # Kill just the child process since it wasn't placed in a new process
 73        # group. If the async function handler forked any child processes they may
 74        # be orphaned and left behind.
 75        kill -TERM $pid 2>/dev/null
 76      fi
 77    fi
 78
 79    # Define global variables to store the file descriptor, PID and output
 80    _OMZ_ASYNC_FDS[$handler]=-1
 81    _OMZ_ASYNC_PIDS[$handler]=-1
 82
 83    # Fork a process to fetch the git status and open a pipe to read from it
 84    exec {fd}< <(
 85      # Tell parent process our PID
 86      builtin echo ${sysparams[pid]}
 87      # Set exit code for the handler if used
 88      () { return $ret }
 89      # Run the async function handler
 90      $handler
 91    )
 92
 93    # Save FD for handler
 94    _OMZ_ASYNC_FDS[$handler]=$fd
 95
 96    # There's a weird bug here where ^C stops working unless we force a fork
 97    # See https://github.com/zsh-users/zsh-autosuggestions/issues/364
 98    # and https://github.com/zsh-users/zsh-autosuggestions/pull/612
 99    is-at-least 5.8 || command true
100
101    # Save the PID from the handler child process
102    read -u $fd "_OMZ_ASYNC_PIDS[$handler]"
103
104    # When the fd is readable, call the response handler
105    zle -F "$fd" _omz_async_callback
106  done
107}
108
109# Called when new data is ready to be read from the pipe
110function _omz_async_callback() {
111  emulate -L zsh
112
113  local fd=$1   # First arg will be fd ready for reading
114  local err=$2  # Second arg will be passed in case of error
115
116  if [[ -z "$err" || "$err" == "hup" ]]; then
117    # Get handler name from fd
118    local handler="${(k)_OMZ_ASYNC_FDS[(r)$fd]}"
119
120    # Store old output which is supposed to be already printed
121    local old_output="${_OMZ_ASYNC_OUTPUT[$handler]}"
122
123    # Read output from fd
124    IFS= read -r -u $fd -d '' "_OMZ_ASYNC_OUTPUT[$handler]"
125
126    # Repaint prompt if output has changed
127    if [[ "$old_output" != "${_OMZ_ASYNC_OUTPUT[$handler]}" ]]; then
128      zle .reset-prompt
129      zle -R
130    fi
131
132    # Close the fd
133    exec {fd}<&-
134  fi
135
136  # Always remove the handler
137  zle -F "$fd"
138
139  # Unset global FD variable to prevent closing user created FDs in the precmd hook
140  _OMZ_ASYNC_FDS[$handler]=-1
141  _OMZ_ASYNC_PIDS[$handler]=-1
142}
143
144autoload -Uz add-zsh-hook
145add-zsh-hook precmd _omz_async_request