+4
-1
zsh/.zshrc
+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
+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
+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