Compare changes

Choose any two refs to compare.

Changed files
+760 -1
zsh
+4 -1
zsh/.zshrc
··· 9 9 alias nn="NVIM_APPNAME=nativevim nvim" 10 10 11 11 ## Custom prompt 12 - export PS1='%F{green}%n@%m %F{blue}%~%f %# ' 12 + export PROMPT='%F{green}%n@%m %F{blue}%~%f %# ' 13 + 14 + ## Custom prompt (with asychronous rich data rendering) 15 + source "$HOME/projects/dotfiles/zsh/prompt.zsh" 13 16 14 17 ## Editor 15 18 export EDITOR="$(command -v nvim || command -v vim)"
+672
zsh/async.zsh
··· 1 + #!/usr/bin/env zsh 2 + 3 + # 4 + # zsh-async 5 + # 6 + # version: v1.8.6 7 + # author: Mathias Fredriksson 8 + # url: https://github.com/mafredri/zsh-async 9 + # 10 + 11 + typeset -g ASYNC_VERSION=1.8.6 12 + # Produce debug output from zsh-async when set to 1. 13 + typeset -g ASYNC_DEBUG=${ASYNC_DEBUG:-0} 14 + 15 + # Execute commands that can manipulate the environment inside the async worker. Return output via callback. 16 + _async_eval() { 17 + local ASYNC_JOB_NAME 18 + # Rename job to _async_eval and redirect all eval output to cat running 19 + # in _async_job. Here, stdout and stderr are not separated for 20 + # simplicity, this could be improved in the future. 21 + { 22 + eval "$@" 23 + } &> >(ASYNC_JOB_NAME=[async/eval] _async_job 'command -p cat') 24 + } 25 + 26 + # Wrapper for jobs executed by the async worker, gives output in parseable format with execution time 27 + _async_job() { 28 + # Disable xtrace as it would mangle the output. 29 + setopt localoptions noxtrace 30 + 31 + # Store start time for job. 32 + float -F duration=$EPOCHREALTIME 33 + 34 + # Run the command and capture both stdout (`eval`) and stderr (`cat`) in 35 + # separate subshells. When the command is complete, we grab write lock 36 + # (mutex token) and output everything except stderr inside the command 37 + # block, after the command block has completed, the stdin for `cat` is 38 + # closed, causing stderr to be appended with a $'\0' at the end to mark the 39 + # end of output from this job. 40 + local jobname=${ASYNC_JOB_NAME:-$1} out 41 + out="$( 42 + local stdout stderr ret tok 43 + { 44 + stdout=$(eval "$@") 45 + ret=$? 46 + duration=$(( EPOCHREALTIME - duration )) # Calculate duration. 47 + 48 + print -r -n - $'\0'${(q)jobname} $ret ${(q)stdout} $duration 49 + } 2> >(stderr=$(command -p cat) && print -r -n - " "${(q)stderr}$'\0') 50 + )" 51 + if [[ $out != $'\0'*$'\0' ]]; then 52 + # Corrupted output (aborted job?), skipping. 53 + return 54 + fi 55 + 56 + # Grab mutex lock, stalls until token is available. 57 + read -r -k 1 -p tok || return 1 58 + 59 + # Return output (<job_name> <return_code> <stdout> <duration> <stderr>). 60 + print -r -n - "$out" 61 + 62 + # Unlock mutex by inserting a token. 63 + print -n -p $tok 64 + } 65 + 66 + # The background worker manages all tasks and runs them without interfering with other processes 67 + _async_worker() { 68 + # Reset all options to defaults inside async worker. 69 + emulate -R zsh 70 + 71 + # Make sure monitor is unset to avoid printing the 72 + # pids of child processes. 73 + unsetopt monitor 74 + 75 + # Redirect stderr to `/dev/null` in case unforseen errors produced by the 76 + # worker. For example: `fork failed: resource temporarily unavailable`. 77 + # Some older versions of zsh might also print malloc errors (know to happen 78 + # on at least zsh 5.0.2 and 5.0.8) likely due to kill signals. 79 + exec 2>/dev/null 80 + 81 + # When a zpty is deleted (using -d) all the zpty instances created before 82 + # the one being deleted receive a SIGHUP, unless we catch it, the async 83 + # worker would simply exit (stop working) even though visible in the list 84 + # of zpty's (zpty -L). This has been fixed around the time of Zsh 5.4 85 + # (not released). 86 + if ! is-at-least 5.4.1; then 87 + TRAPHUP() { 88 + return 0 # Return 0, indicating signal was handled. 89 + } 90 + fi 91 + 92 + local -A storage 93 + local unique=0 94 + local notify_parent=0 95 + local parent_pid=0 96 + local coproc_pid=0 97 + local processing=0 98 + 99 + local -a zsh_hooks zsh_hook_functions 100 + zsh_hooks=(chpwd periodic precmd preexec zshexit zshaddhistory) 101 + zsh_hook_functions=(${^zsh_hooks}_functions) 102 + unfunction $zsh_hooks &>/dev/null # Deactivate all zsh hooks inside the worker. 103 + unset $zsh_hook_functions # And hooks with registered functions. 104 + unset zsh_hooks zsh_hook_functions # Cleanup. 105 + 106 + close_idle_coproc() { 107 + local -a pids 108 + pids=(${${(v)jobstates##*:*:}%\=*}) 109 + 110 + # If coproc (cat) is the only child running, we close it to avoid 111 + # leaving it running indefinitely and cluttering the process tree. 112 + if (( ! processing )) && [[ $#pids = 1 ]] && [[ $coproc_pid = $pids[1] ]]; then 113 + coproc : 114 + coproc_pid=0 115 + fi 116 + } 117 + 118 + child_exit() { 119 + close_idle_coproc 120 + 121 + # On older version of zsh (pre 5.2) we notify the parent through a 122 + # SIGWINCH signal because `zpty` did not return a file descriptor (fd) 123 + # prior to that. 124 + if (( notify_parent )); then 125 + # We use SIGWINCH for compatibility with older versions of zsh 126 + # (pre 5.1.1) where other signals (INFO, ALRM, USR1, etc.) could 127 + # cause a deadlock in the shell under certain circumstances. 128 + kill -WINCH $parent_pid 129 + fi 130 + } 131 + 132 + # Register a SIGCHLD trap to handle the completion of child processes. 133 + trap child_exit CHLD 134 + 135 + # Process option parameters passed to worker. 136 + while getopts "np:uz" opt; do 137 + case $opt in 138 + n) notify_parent=1;; 139 + p) parent_pid=$OPTARG;; 140 + u) unique=1;; 141 + z) notify_parent=0;; # Uses ZLE watcher instead. 142 + esac 143 + done 144 + 145 + # Terminate all running jobs, note that this function does not 146 + # reinstall the child trap. 147 + terminate_jobs() { 148 + trap - CHLD # Ignore child exits during kill. 149 + coproc : # Quit coproc. 150 + coproc_pid=0 # Reset pid. 151 + 152 + if is-at-least 5.4.1; then 153 + trap '' HUP # Catch the HUP sent to this process. 154 + kill -HUP -$$ # Send to entire process group. 155 + trap - HUP # Disable HUP trap. 156 + else 157 + # We already handle HUP for Zsh < 5.4.1. 158 + kill -HUP -$$ # Send to entire process group. 159 + fi 160 + } 161 + 162 + killjobs() { 163 + local tok 164 + local -a pids 165 + pids=(${${(v)jobstates##*:*:}%\=*}) 166 + 167 + # No need to send SIGHUP if no jobs are running. 168 + (( $#pids == 0 )) && continue 169 + (( $#pids == 1 )) && [[ $coproc_pid = $pids[1] ]] && continue 170 + 171 + # Grab lock to prevent half-written output in case a child 172 + # process is in the middle of writing to stdin during kill. 173 + (( coproc_pid )) && read -r -k 1 -p tok 174 + 175 + terminate_jobs 176 + trap child_exit CHLD # Reinstall child trap. 177 + } 178 + 179 + local request do_eval=0 180 + local -a cmd 181 + while :; do 182 + # Wait for jobs sent by async_job. 183 + read -r -d $'\0' request || { 184 + # Unknown error occurred while reading from stdin, the zpty 185 + # worker is likely in a broken state, so we shut down. 186 + terminate_jobs 187 + 188 + # Stdin is broken and in case this was an unintended 189 + # crash, we try to report it as a last hurrah. 190 + print -r -n $'\0'"'[async]'" $(( 127 + 3 )) "''" 0 "'$0:$LINENO: zpty fd died, exiting'"$'\0' 191 + 192 + # We use `return` to abort here because using `exit` may 193 + # result in an infinite loop that never exits and, as a 194 + # result, high CPU utilization. 195 + return $(( 127 + 1 )) 196 + } 197 + 198 + # We need to clean the input here because sometimes when a zpty 199 + # has died and been respawned, messages will be prefixed with a 200 + # carraige return (\r, or \C-M). 201 + request=${request#$'\C-M'} 202 + 203 + # Check for non-job commands sent to worker 204 + case $request in 205 + _killjobs) killjobs; continue;; 206 + _async_eval*) do_eval=1;; 207 + esac 208 + 209 + # Parse the request using shell parsing (z) to allow commands 210 + # to be parsed from single strings and multi-args alike. 211 + cmd=("${(z)request}") 212 + 213 + # Name of the job (first argument). 214 + local job=$cmd[1] 215 + 216 + # Check if a worker should perform unique jobs, unless 217 + # this is an eval since they run synchronously. 218 + if (( !do_eval )) && (( unique )); then 219 + # Check if a previous job is still running, if yes, 220 + # skip this job and let the previous one finish. 221 + for pid in ${${(v)jobstates##*:*:}%\=*}; do 222 + if [[ ${storage[$job]} == $pid ]]; then 223 + continue 2 224 + fi 225 + done 226 + fi 227 + 228 + # Guard against closing coproc from trap before command has started. 229 + processing=1 230 + 231 + # Because we close the coproc after the last job has completed, we must 232 + # recreate it when there are no other jobs running. 233 + if (( ! coproc_pid )); then 234 + # Use coproc as a mutex for synchronized output between children. 235 + coproc command -p cat 236 + coproc_pid="$!" 237 + # Insert token into coproc 238 + print -n -p "t" 239 + fi 240 + 241 + if (( do_eval )); then 242 + shift cmd # Strip _async_eval from cmd. 243 + _async_eval $cmd 244 + else 245 + # Run job in background, completed jobs are printed to stdout. 246 + _async_job $cmd & 247 + # Store pid because zsh job manager is extremely unflexible (show jobname as non-unique '$job')... 248 + storage[$job]="$!" 249 + fi 250 + 251 + processing=0 # Disable guard. 252 + 253 + if (( do_eval )); then 254 + do_eval=0 255 + 256 + # When there are no active jobs we can't rely on the CHLD trap to 257 + # manage the coproc lifetime. 258 + close_idle_coproc 259 + fi 260 + done 261 + } 262 + 263 + # 264 + # Get results from finished jobs and pass it to the to callback function. This is the only way to reliably return the 265 + # job name, return code, output and execution time and with minimal effort. 266 + # 267 + # If the async process buffer becomes corrupt, the callback will be invoked with the first argument being `[async]` (job 268 + # name), non-zero return code and fifth argument describing the error (stderr). 269 + # 270 + # usage: 271 + # async_process_results <worker_name> <callback_function> 272 + # 273 + # callback_function is called with the following parameters: 274 + # $1 = job name, e.g. the function passed to async_job 275 + # $2 = return code 276 + # $3 = resulting stdout from execution 277 + # $4 = execution time, floating point e.g. 2.05 seconds 278 + # $5 = resulting stderr from execution 279 + # $6 = has next result in buffer (0 = buffer empty, 1 = yes) 280 + # 281 + async_process_results() { 282 + setopt localoptions unset noshwordsplit noksharrays noposixidentifiers noposixstrings 283 + 284 + local worker=$1 285 + local callback=$2 286 + local caller=$3 287 + local -a items 288 + local null=$'\0' data 289 + integer -l len pos num_processed has_next 290 + 291 + typeset -gA ASYNC_PROCESS_BUFFER 292 + 293 + # Read output from zpty and parse it if available. 294 + while zpty -r -t $worker data 2>/dev/null; do 295 + ASYNC_PROCESS_BUFFER[$worker]+=$data 296 + len=${#ASYNC_PROCESS_BUFFER[$worker]} 297 + pos=${ASYNC_PROCESS_BUFFER[$worker][(i)$null]} # Get index of NULL-character (delimiter). 298 + 299 + # Keep going until we find a NULL-character. 300 + if (( ! len )) || (( pos > len )); then 301 + continue 302 + fi 303 + 304 + while (( pos <= len )); do 305 + # Take the content from the beginning, until the NULL-character and 306 + # perform shell parsing (z) and unquoting (Q) as an array (@). 307 + items=("${(@Q)${(z)ASYNC_PROCESS_BUFFER[$worker][1,$pos-1]}}") 308 + 309 + # Remove the extracted items from the buffer. 310 + ASYNC_PROCESS_BUFFER[$worker]=${ASYNC_PROCESS_BUFFER[$worker][$pos+1,$len]} 311 + 312 + len=${#ASYNC_PROCESS_BUFFER[$worker]} 313 + if (( len > 1 )); then 314 + pos=${ASYNC_PROCESS_BUFFER[$worker][(i)$null]} # Get index of NULL-character (delimiter). 315 + fi 316 + 317 + has_next=$(( len != 0 )) 318 + if (( $#items == 5 )); then 319 + items+=($has_next) 320 + $callback "${(@)items}" # Send all parsed items to the callback. 321 + (( num_processed++ )) 322 + elif [[ -z $items ]]; then 323 + # Empty items occur between results due to double-null ($'\0\0') 324 + # caused by commands being both pre and suffixed with null. 325 + else 326 + # In case of corrupt data, invoke callback with *async* as job 327 + # name, non-zero exit status and an error message on stderr. 328 + $callback "[async]" 1 "" 0 "$0:$LINENO: error: bad format, got ${#items} items (${(q)items})" $has_next 329 + fi 330 + done 331 + done 332 + 333 + (( num_processed )) && return 0 334 + 335 + # Avoid printing exit value when `setopt printexitvalue` is active.` 336 + [[ $caller = trap || $caller = watcher ]] && return 0 337 + 338 + # No results were processed 339 + return 1 340 + } 341 + 342 + # Watch worker for output 343 + _async_zle_watcher() { 344 + setopt localoptions noshwordsplit 345 + typeset -gA ASYNC_PTYS ASYNC_CALLBACKS 346 + local worker=$ASYNC_PTYS[$1] 347 + local callback=$ASYNC_CALLBACKS[$worker] 348 + 349 + if [[ -n $2 ]]; then 350 + # from man zshzle(1): 351 + # `hup' for a disconnect, `nval' for a closed or otherwise 352 + # invalid descriptor, or `err' for any other condition. 353 + # Systems that support only the `select' system call always use 354 + # `err'. 355 + 356 + # this has the side effect to unregister the broken file descriptor 357 + async_stop_worker $worker 358 + 359 + if [[ -n $callback ]]; then 360 + $callback '[async]' 2 "" 0 "$0:$LINENO: error: fd for $worker failed: zle -F $1 returned error $2" 0 361 + fi 362 + return 363 + fi; 364 + 365 + if [[ -n $callback ]]; then 366 + async_process_results $worker $callback watcher 367 + fi 368 + } 369 + 370 + _async_send_job() { 371 + setopt localoptions noshwordsplit noksharrays noposixidentifiers noposixstrings 372 + 373 + local caller=$1 374 + local worker=$2 375 + shift 2 376 + 377 + zpty -t $worker &>/dev/null || { 378 + typeset -gA ASYNC_CALLBACKS 379 + local callback=$ASYNC_CALLBACKS[$worker] 380 + 381 + if [[ -n $callback ]]; then 382 + $callback '[async]' 3 "" 0 "$0:$LINENO: error: no such worker: $worker" 0 383 + else 384 + print -u2 "$caller: no such async worker: $worker" 385 + fi 386 + return 1 387 + } 388 + 389 + zpty -w $worker "$@"$'\0' 390 + } 391 + 392 + # 393 + # Start a new asynchronous job on specified worker, assumes the worker is running. 394 + # 395 + # Note if you are using a function for the job, it must have been defined before the worker was 396 + # started or you will get a `command not found` error. 397 + # 398 + # usage: 399 + # async_job <worker_name> <my_function> [<function_params>] 400 + # 401 + async_job() { 402 + setopt localoptions noshwordsplit noksharrays noposixidentifiers noposixstrings 403 + 404 + local worker=$1; shift 405 + 406 + local -a cmd 407 + cmd=("$@") 408 + if (( $#cmd > 1 )); then 409 + cmd=(${(q)cmd}) # Quote special characters in multi argument commands. 410 + fi 411 + 412 + _async_send_job $0 $worker "$cmd" 413 + } 414 + 415 + # 416 + # Evaluate a command (like async_job) inside the async worker, then worker environment can be manipulated. For example, 417 + # issuing a cd command will change the PWD of the worker which will then be inherited by all future async jobs. 418 + # 419 + # Output will be returned via callback, job name will be [async/eval]. 420 + # 421 + # usage: 422 + # async_worker_eval <worker_name> <my_function> [<function_params>] 423 + # 424 + async_worker_eval() { 425 + setopt localoptions noshwordsplit noksharrays noposixidentifiers noposixstrings 426 + 427 + local worker=$1; shift 428 + 429 + local -a cmd 430 + cmd=("$@") 431 + if (( $#cmd > 1 )); then 432 + cmd=(${(q)cmd}) # Quote special characters in multi argument commands. 433 + fi 434 + 435 + # Quote the cmd in case RC_EXPAND_PARAM is set. 436 + _async_send_job $0 $worker "_async_eval $cmd" 437 + } 438 + 439 + # This function traps notification signals and calls all registered callbacks 440 + _async_notify_trap() { 441 + setopt localoptions noshwordsplit 442 + 443 + local k 444 + for k in ${(k)ASYNC_CALLBACKS}; do 445 + async_process_results $k ${ASYNC_CALLBACKS[$k]} trap 446 + done 447 + } 448 + 449 + # 450 + # Register a callback for completed jobs. As soon as a job is finnished, async_process_results will be called with the 451 + # specified callback function. This requires that a worker is initialized with the -n (notify) option. 452 + # 453 + # usage: 454 + # async_register_callback <worker_name> <callback_function> 455 + # 456 + async_register_callback() { 457 + setopt localoptions noshwordsplit nolocaltraps 458 + 459 + typeset -gA ASYNC_PTYS ASYNC_CALLBACKS 460 + local worker=$1; shift 461 + 462 + ASYNC_CALLBACKS[$worker]="$*" 463 + 464 + # Enable trap when the ZLE watcher is unavailable, allows 465 + # workers to notify (via -n) when a job is done. 466 + if [[ ! -o interactive ]] || [[ ! -o zle ]]; then 467 + trap '_async_notify_trap' WINCH 468 + elif [[ -o interactive ]] && [[ -o zle ]]; then 469 + local fd w 470 + for fd w in ${(@kv)ASYNC_PTYS}; do 471 + if [[ $w == $worker ]]; then 472 + zle -F $fd _async_zle_watcher # Register the ZLE handler. 473 + break 474 + fi 475 + done 476 + fi 477 + } 478 + 479 + # 480 + # Unregister the callback for a specific worker. 481 + # 482 + # usage: 483 + # async_unregister_callback <worker_name> 484 + # 485 + async_unregister_callback() { 486 + typeset -gA ASYNC_CALLBACKS 487 + 488 + unset "ASYNC_CALLBACKS[$1]" 489 + } 490 + 491 + # 492 + # Flush all current jobs running on a worker. This will terminate any and all running processes under the worker, use 493 + # with caution. 494 + # 495 + # usage: 496 + # async_flush_jobs <worker_name> 497 + # 498 + async_flush_jobs() { 499 + setopt localoptions noshwordsplit 500 + 501 + local worker=$1; shift 502 + 503 + # Check if the worker exists 504 + zpty -t $worker &>/dev/null || return 1 505 + 506 + # Send kill command to worker 507 + async_job $worker "_killjobs" 508 + 509 + # Clear the zpty buffer. 510 + local junk 511 + if zpty -r -t $worker junk '*'; then 512 + (( ASYNC_DEBUG )) && print -n "async_flush_jobs $worker: ${(V)junk}" 513 + while zpty -r -t $worker junk '*'; do 514 + (( ASYNC_DEBUG )) && print -n "${(V)junk}" 515 + done 516 + (( ASYNC_DEBUG )) && print 517 + fi 518 + 519 + # Finally, clear the process buffer in case of partially parsed responses. 520 + typeset -gA ASYNC_PROCESS_BUFFER 521 + unset "ASYNC_PROCESS_BUFFER[$worker]" 522 + } 523 + 524 + # 525 + # Start a new async worker with optional parameters, a worker can be told to only run unique tasks and to notify a 526 + # process when tasks are complete. 527 + # 528 + # usage: 529 + # async_start_worker <worker_name> [-u] [-n] [-p <pid>] 530 + # 531 + # opts: 532 + # -u unique (only unique job names can run) 533 + # -n notify through SIGWINCH signal 534 + # -p pid to notify (defaults to current pid) 535 + # 536 + async_start_worker() { 537 + setopt localoptions noshwordsplit noclobber 538 + 539 + local worker=$1; shift 540 + local -a args 541 + args=("$@") 542 + zpty -t $worker &>/dev/null && return 543 + 544 + typeset -gA ASYNC_PTYS 545 + typeset -h REPLY 546 + typeset has_xtrace=0 547 + 548 + if [[ -o interactive ]] && [[ -o zle ]]; then 549 + # Inform the worker to ignore the notify flag and that we're 550 + # using a ZLE watcher instead. 551 + args+=(-z) 552 + 553 + if (( ! ASYNC_ZPTY_RETURNS_FD )); then 554 + # When zpty doesn't return a file descriptor (on older versions of zsh) 555 + # we try to guess it anyway. 556 + integer -l zptyfd 557 + exec {zptyfd}>&1 # Open a new file descriptor (above 10). 558 + exec {zptyfd}>&- # Close it so it's free to be used by zpty. 559 + fi 560 + fi 561 + 562 + # Workaround for stderr in the main shell sometimes (incorrectly) being 563 + # reassigned to /dev/null by the reassignment done inside the async 564 + # worker. 565 + # See https://github.com/mafredri/zsh-async/issues/35. 566 + integer errfd=-1 567 + 568 + # Redirect of errfd is broken on zsh 5.0.2. 569 + if is-at-least 5.0.8; then 570 + exec {errfd}>&2 571 + fi 572 + 573 + # Make sure async worker is started without xtrace 574 + # (the trace output interferes with the worker). 575 + [[ -o xtrace ]] && { 576 + has_xtrace=1 577 + unsetopt xtrace 578 + } 579 + 580 + if (( errfd != -1 )); then 581 + zpty -b $worker _async_worker -p $$ $args 2>&$errfd 582 + else 583 + zpty -b $worker _async_worker -p $$ $args 584 + fi 585 + local ret=$? 586 + 587 + # Re-enable it if it was enabled, for debugging. 588 + (( has_xtrace )) && setopt xtrace 589 + (( errfd != -1 )) && exec {errfd}>& - 590 + 591 + if (( ret )); then 592 + async_stop_worker $worker 593 + return 1 594 + fi 595 + 596 + if ! is-at-least 5.0.8; then 597 + # For ZSH versions older than 5.0.8 we delay a bit to give 598 + # time for the worker to start before issuing commands, 599 + # otherwise it will not be ready to receive them. 600 + sleep 0.001 601 + fi 602 + 603 + if [[ -o interactive ]] && [[ -o zle ]]; then 604 + if (( ! ASYNC_ZPTY_RETURNS_FD )); then 605 + REPLY=$zptyfd # Use the guessed value for the file desciptor. 606 + fi 607 + 608 + ASYNC_PTYS[$REPLY]=$worker # Map the file desciptor to the worker. 609 + fi 610 + } 611 + 612 + # 613 + # Stop one or multiple workers that are running, all unfetched and incomplete work will be lost. 614 + # 615 + # usage: 616 + # async_stop_worker <worker_name_1> [<worker_name_2>] 617 + # 618 + async_stop_worker() { 619 + setopt localoptions noshwordsplit 620 + 621 + local ret=0 worker k v 622 + for worker in $@; do 623 + # Find and unregister the zle handler for the worker 624 + for k v in ${(@kv)ASYNC_PTYS}; do 625 + if [[ $v == $worker ]]; then 626 + zle -F $k 627 + unset "ASYNC_PTYS[$k]" 628 + fi 629 + done 630 + async_unregister_callback $worker 631 + zpty -d $worker 2>/dev/null || ret=$? 632 + 633 + # Clear any partial buffers. 634 + typeset -gA ASYNC_PROCESS_BUFFER 635 + unset "ASYNC_PROCESS_BUFFER[$worker]" 636 + done 637 + 638 + return $ret 639 + } 640 + 641 + # 642 + # Initialize the required modules for zsh-async. To be called before using the zsh-async library. 643 + # 644 + # usage: 645 + # async_init 646 + # 647 + async_init() { 648 + (( ASYNC_INIT_DONE )) && return 649 + typeset -g ASYNC_INIT_DONE=1 650 + 651 + zmodload zsh/zpty 652 + zmodload zsh/datetime 653 + 654 + # Load is-at-least for reliable version check. 655 + autoload -Uz is-at-least 656 + 657 + # Check if zsh/zpty returns a file descriptor or not, 658 + # shell must also be interactive with zle enabled. 659 + typeset -g ASYNC_ZPTY_RETURNS_FD=0 660 + [[ -o interactive ]] && [[ -o zle ]] && { 661 + typeset -h REPLY 662 + zpty _async_test : 663 + (( REPLY )) && ASYNC_ZPTY_RETURNS_FD=1 664 + zpty -d _async_test 665 + } 666 + } 667 + 668 + async() { 669 + async_init 670 + } 671 + 672 + async "$@"
+84
zsh/prompt.zsh
··· 1 + source "$HOME/projects/dotfiles/zsh/async.zsh" 2 + 3 + # init zsh-async 4 + async_init 5 + 6 + jj_status() { 7 + template="' (' ++ change_id.shortest(8) ++ ')'" 8 + if repo_root=$(jj root --ignore-working-copy 2>/dev/null); then 9 + jj log -r @ -T $template --no-graph --no-pager --color never --ignore-working-copy -R $repo_root 10 + fi 11 + } 12 + 13 + fetch_data() { 14 + # refetch data 15 + date_string=$(date +"%Y-%m-%d %H:%M:%S") 16 + JJ_ST=$(jj_status) 17 + } 18 + reset_prompt() { 19 + # reset prompt 20 + PROMPT="%F{green}%n@%m %F{blue}%~%f$JJ_ST %# " 21 + # re-render prompt 22 + zle reset-prompt 23 + } 24 + 25 + RUNNING= 26 + update_prompt() { 27 + if [[ -n $RUNNING ]]; then 28 + return 0 29 + fi 30 + RUNNING=1 31 + 32 + fetch_data 33 + 34 + reset_prompt 35 + 36 + # NOTE: don't make recursive loop. use TMOUT & TRAPALRM for 1s throttling 37 + # # call fetch job again 38 + # async_job prompt_worker 39 + RUNNING= 40 + } 41 + 42 + # define status_worker to run asynchronously 43 + async_start_worker prompt_worker -n 44 + async_register_callback prompt_worker update_prompt 45 + async_job prompt_worker 46 + 47 + # reset_prompt() { 48 + # # reset prompt 49 + # PROMPT="%F{green}%n@%m %F{blue}%~%f$JJ_ST %# " 50 + # # re-render prompt 51 + # zle reset-prompt 52 + # 53 + # async_job prompt_render_worker 54 + # } 55 + # async_start_worker prompt_render_worker -n 56 + # async_register_callback prompt_render_worker reset_prompt 57 + # async_job prompt_render_worker 58 + 59 + # call do_update on every seconds 60 + TMOUT=1 61 + TRAPALRM() { 62 + async_job prompt_worker 63 + } 64 + 65 + # NOTE: don't do heavy jobs here. It will make prompt slower 66 + precmd() { 67 + PROMPT="%F{green}%n@%m %F{blue}%~%f$JJ_ST %# " 68 + # call fetch job again 69 + async_job prompt_worker 70 + } 71 + 72 + zle_reset_prompt_widget() { 73 + # async_job prompt_worker 74 + } 75 + # register it under the name “zle_reset_prompt_widget” 76 + zle -N zle_reset_prompt_widget 77 + autoload -Uz add-zle-hook-widget 78 + add-zle-hook-widget line-init zle_reset_prompt_widget 79 + 80 + PROMPT="%n@%m %~%f$JJ_ST %# " 81 + 82 + # references: 83 + # - https://medium.com/@henrebotha/how-to-write-an-asynchronous-zsh-prompt-b53e81720d32 84 + # - https://void-shana.moe/posts/customize-your-zsh-prompt