mirror of
https://github.com/ohmyzsh/ohmyzsh.git
synced 2024-11-11 00:10:08 +00:00
feat(async)!: implement async prompt API and apply to git prompt (#12257)
BREAKING CHANGE: the `git_prompt_info` prompt function has been reworked by default to use the new async prompt feature. If you're experiencing issues see #12257. Co-authored-by: Carlo Sala <carlosalag@protonmail.com>
This commit is contained in:
parent
4fca7ccb55
commit
083cc2c8e8
2 changed files with 160 additions and 2 deletions
140
lib/async_prompt.zsh
Normal file
140
lib/async_prompt.zsh
Normal file
|
@ -0,0 +1,140 @@
|
||||||
|
# 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
|
||||||
|
|
||||||
|
# For now, async prompt function handlers are set up like so:
|
||||||
|
# First, define the async function handler and add the function name
|
||||||
|
# to the _omz_async_functions array:
|
||||||
|
#
|
||||||
|
# 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]
|
||||||
|
# }
|
||||||
|
#
|
||||||
|
# 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
|
||||||
|
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 {
|
||||||
|
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]}
|
||||||
|
# Store handler name for callback
|
||||||
|
builtin echo $handler
|
||||||
|
# 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
|
||||||
|
command true
|
||||||
|
|
||||||
|
# Save the PID from the handler child process
|
||||||
|
read pid <&$fd
|
||||||
|
_OMZ_ASYNC_PIDS[$handler]=$pid
|
||||||
|
|
||||||
|
# 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 first line
|
||||||
|
local handler
|
||||||
|
read handler <&$fd
|
||||||
|
|
||||||
|
# Store old output which is supposed to be already printed
|
||||||
|
local old_output="${_OMZ_ASYNC_OUTPUT[$handler]}"
|
||||||
|
|
||||||
|
# Read output from fd
|
||||||
|
_OMZ_ASYNC_OUTPUT[$handler]="$(cat <&$fd)"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
}
|
20
lib/git.zsh
20
lib/git.zsh
|
@ -9,7 +9,7 @@ function __git_prompt_git() {
|
||||||
GIT_OPTIONAL_LOCKS=0 command git "$@"
|
GIT_OPTIONAL_LOCKS=0 command git "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
function git_prompt_info() {
|
function _omz_git_prompt_status() {
|
||||||
# If we are on a folder not tracked by git, get out.
|
# If we are on a folder not tracked by git, get out.
|
||||||
# Otherwise, check for hide-info at global and local repository level
|
# Otherwise, check for hide-info at global and local repository level
|
||||||
if ! __git_prompt_git rev-parse --git-dir &> /dev/null \
|
if ! __git_prompt_git rev-parse --git-dir &> /dev/null \
|
||||||
|
@ -17,6 +17,10 @@ function git_prompt_info() {
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Get either:
|
||||||
|
# - the current branch name
|
||||||
|
# - the tag name if we are on a tag
|
||||||
|
# - the short SHA of the current commit
|
||||||
local ref
|
local ref
|
||||||
ref=$(__git_prompt_git symbolic-ref --short HEAD 2> /dev/null) \
|
ref=$(__git_prompt_git symbolic-ref --short HEAD 2> /dev/null) \
|
||||||
|| ref=$(__git_prompt_git describe --tags --exact-match HEAD 2> /dev/null) \
|
|| ref=$(__git_prompt_git describe --tags --exact-match HEAD 2> /dev/null) \
|
||||||
|
@ -33,6 +37,20 @@ function git_prompt_info() {
|
||||||
echo "${ZSH_THEME_GIT_PROMPT_PREFIX}${ref:gs/%/%%}${upstream:gs/%/%%}$(parse_git_dirty)${ZSH_THEME_GIT_PROMPT_SUFFIX}"
|
echo "${ZSH_THEME_GIT_PROMPT_PREFIX}${ref:gs/%/%%}${upstream:gs/%/%%}$(parse_git_dirty)${ZSH_THEME_GIT_PROMPT_SUFFIX}"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Enable async prompt by default unless the setting is at false / no
|
||||||
|
if zstyle -T ':omz:alpha:lib:git' async-prompt; then
|
||||||
|
function git_prompt_info() {
|
||||||
|
_omz_register_handler _omz_git_prompt_status
|
||||||
|
if [[ -n "$_OMZ_ASYNC_OUTPUT[_omz_git_prompt_status]" ]]; then
|
||||||
|
echo -n "$_OMZ_ASYNC_OUTPUT[_omz_git_prompt_status]"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
else
|
||||||
|
function git_prompt_info() {
|
||||||
|
_omz_git_prompt_status
|
||||||
|
}
|
||||||
|
fi
|
||||||
|
|
||||||
# Checks if working tree is dirty
|
# Checks if working tree is dirty
|
||||||
function parse_git_dirty() {
|
function parse_git_dirty() {
|
||||||
local STATUS
|
local STATUS
|
||||||
|
|
Loading…
Reference in a new issue