async_prompt.zsh 4.6 KB

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