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