summaryrefslogtreecommitdiff
path: root/lib/async_prompt.zsh
diff options
context:
space:
mode:
Diffstat (limited to 'lib/async_prompt.zsh')
-rw-r--r--lib/async_prompt.zsh145
1 files changed, 145 insertions, 0 deletions
diff --git a/lib/async_prompt.zsh b/lib/async_prompt.zsh
new file mode 100644
index 000000000..151e24b8c
--- /dev/null
+++ b/lib/async_prompt.zsh
@@ -0,0 +1,145 @@
+# The async code is taken from
+# https://github.com/zsh-users/zsh-autosuggestions/blob/master/src/async.zsh
+# https://github.com/woefe/git-prompt.zsh/blob/master/git-prompt.zsh
+
+zmodload zsh/system
+autoload -Uz is-at-least
+
+# For now, async prompt function handlers are set up like so:
+# First, define the async function handler and register the handler
+# with _omz_register_handler:
+#
+# function _git_prompt_status_async {
+# # Do some expensive operation that outputs to stdout
+# }
+# _omz_register_handler _git_prompt_status_async
+#
+# Then add a stub prompt function in `$PROMPT` or similar prompt variables,
+# which will show the output of "$_OMZ_ASYNC_OUTPUT[handler_name]":
+#
+# function git_prompt_status {
+# echo -n $_OMZ_ASYNC_OUTPUT[_git_prompt_status_async]
+# }
+#
+# RPROMPT='$(git_prompt_status)'
+#
+# This API is subject to change and optimization. Rely on it at your own risk.
+
+function _omz_register_handler {
+ setopt localoptions noksharrays unset
+ typeset -ga _omz_async_functions
+ # we want to do nothing if there's no $1 function or we already set it up
+ if [[ -z "$1" ]] || (( ! ${+functions[$1]} )) \
+ || (( ${_omz_async_functions[(Ie)$1]} )); then
+ return
+ fi
+ _omz_async_functions+=("$1")
+ # let's add the hook to async_request if it's not there yet
+ if (( ! ${precmd_functions[(Ie)_omz_async_request]} )) \
+ && (( ${+functions[_omz_async_request]})); then
+ autoload -Uz add-zsh-hook
+ add-zsh-hook precmd _omz_async_request
+ fi
+}
+
+# Set up async handlers and callbacks
+function _omz_async_request {
+ setopt localoptions noksharrays unset
+ local -i ret=$?
+ typeset -gA _OMZ_ASYNC_FDS _OMZ_ASYNC_PIDS _OMZ_ASYNC_OUTPUT
+
+ # executor runs a subshell for all async requests based on key
+ local handler
+ for handler in ${_omz_async_functions}; do
+ (( ${+functions[$handler]} )) || continue
+
+ local fd=${_OMZ_ASYNC_FDS[$handler]:--1}
+ local pid=${_OMZ_ASYNC_PIDS[$handler]:--1}
+
+ # If we've got a pending request, cancel it
+ if (( fd != -1 && pid != -1 )) && { true <&$fd } 2>/dev/null; then
+ # Close the file descriptor and remove the handler
+ exec {fd}<&-
+ zle -F $fd
+
+ # Zsh will make a new process group for the child process only if job
+ # control is enabled (MONITOR option)
+ if [[ -o MONITOR ]]; then
+ # Send the signal to the process group to kill any processes that may
+ # have been forked by the async function handler
+ kill -TERM -$pid 2>/dev/null
+ else
+ # Kill just the child process since it wasn't placed in a new process
+ # group. If the async function handler forked any child processes they may
+ # be orphaned and left behind.
+ kill -TERM $pid 2>/dev/null
+ fi
+ fi
+
+ # Define global variables to store the file descriptor, PID and output
+ _OMZ_ASYNC_FDS[$handler]=-1
+ _OMZ_ASYNC_PIDS[$handler]=-1
+
+ # Fork a process to fetch the git status and open a pipe to read from it
+ exec {fd}< <(
+ # Tell parent process our PID
+ builtin echo ${sysparams[pid]}
+ # Set exit code for the handler if used
+ () { return $ret }
+ # Run the async function handler
+ $handler
+ )
+
+ # Save FD for handler
+ _OMZ_ASYNC_FDS[$handler]=$fd
+
+ # There's a weird bug here where ^C stops working unless we force a fork
+ # See https://github.com/zsh-users/zsh-autosuggestions/issues/364
+ # and https://github.com/zsh-users/zsh-autosuggestions/pull/612
+ is-at-least 5.8 || command true
+
+ # Save the PID from the handler child process
+ read -u $fd "_OMZ_ASYNC_PIDS[$handler]"
+
+ # When the fd is readable, call the response handler
+ zle -F "$fd" _omz_async_callback
+ done
+}
+
+# Called when new data is ready to be read from the pipe
+function _omz_async_callback() {
+ emulate -L zsh
+
+ local fd=$1 # First arg will be fd ready for reading
+ local err=$2 # Second arg will be passed in case of error
+
+ if [[ -z "$err" || "$err" == "hup" ]]; then
+ # Get handler name from fd
+ local handler="${(k)_OMZ_ASYNC_FDS[(r)$fd]}"
+
+ # Store old output which is supposed to be already printed
+ local old_output="${_OMZ_ASYNC_OUTPUT[$handler]}"
+
+ # Read output from fd
+ IFS= read -r -u $fd -d '' "_OMZ_ASYNC_OUTPUT[$handler]"
+
+ # Repaint prompt if output has changed
+ if [[ "$old_output" != "${_OMZ_ASYNC_OUTPUT[$handler]}" ]]; then
+ zle .reset-prompt
+ zle -R
+ fi
+
+ # Close the fd
+ exec {fd}<&-
+ fi
+
+ # Always remove the handler
+ zle -F "$fd"
+
+ # Unset global FD variable to prevent closing user created FDs in the precmd hook
+ _OMZ_ASYNC_FDS[$handler]=-1
+ _OMZ_ASYNC_PIDS[$handler]=-1
+}
+
+autoload -Uz add-zsh-hook
+add-zsh-hook precmd _omz_async_request