+26
-2
nix/darwin.nix
+26
-2
nix/darwin.nix
···
3
3
# $ nix-env -qaP | grep wget
4
4
environment.systemPackages =
5
5
[
6
-
pkgs.brave
7
6
pkgs.fzf
8
7
pkgs.tmux
9
8
pkgs.tree
10
9
pkgs.vim
10
+
pkgs.git
11
+
pkgs.ollama
11
12
# I don't want to touch this due to current neovim settings.
12
13
# remove it when lux.nvim is out
13
14
(pkgs-unstable.lua5_1.withPackages (ps: with ps; [luarocks]))
14
15
];
16
+
17
+
launchd.user.agents = {
18
+
# reference:
19
+
# - https://www.danielcorin.com/til/nix-darwin/launch-agents/
20
+
# - https://medium.com/@anand34577/setting-up-ollama-as-a-background-service-on-macos-66f7492b5cc8
21
+
ollama-serve = {
22
+
environment = {
23
+
OLLAMA_HOST = "0.0.0.0";
24
+
};
25
+
serviceConfig = {
26
+
Label = "com.ollama.serve";
27
+
ProgramArguments = [ "${pkgs.ollama}/bin/ollama" "serve" ];
28
+
KeepAlive = true;
29
+
RunAtLoad = true;
30
+
# TODO: ensure /var/log/ollama exist
31
+
# StandardOutPath = "/var/log/ollama/stdout.log";
32
+
# StandardErrorPath = "/var/log/ollama/stderr.log";
33
+
StandardOutPath = "/tmp/ollama_boltless.log";
34
+
StandardErrorPath = "/tmp/ollama_boltless.err";
35
+
};
36
+
};
37
+
};
15
38
16
39
# Necessary for using flakes on this system.
17
40
nix.settings.experimental-features = "nix-command flakes";
···
173
196
"sanesidebuttons"
174
197
];
175
198
masApps = {
199
+
Numbers = 409203825;
200
+
Pages = 409201541;
176
201
Things3 = 904280696;
177
-
Pages = 409201541;
178
202
};
179
203
};
180
204
}
+43
-3
nix/flake.nix
+43
-3
nix/flake.nix
···
35
35
inherit self pkgs-unstable;
36
36
};
37
37
};
38
+
darwinConfigurations."boltless-Studio" = nix-darwin.lib.darwinSystem {
39
+
modules = [
40
+
./darwin.nix
41
+
];
42
+
specialArgs = {
43
+
inherit self pkgs-unstable;
44
+
};
45
+
};
38
46
# based on https://zaynetro.com/post/2024-you-dont-need-home-manager-nix
39
47
# $ nix profile install .
40
48
# $ nix profile upgrade nix
41
49
packages.${system}.default = pkgs.buildEnv {
42
50
name = "my-env";
43
51
paths = [
44
-
pkgs.anki-bin
45
52
pkgs.bacon
46
53
pkgs.btop
47
54
pkgs.dust
···
49
56
pkgs.fd
50
57
pkgs.gh
51
58
pkgs.hello
52
-
pkgs.iina
53
59
pkgs.janet
54
60
pkgs.jpm
55
61
pkgs.meld
56
62
pkgs.nodejs_24
63
+
pkgs.qmk
57
64
pkgs.ripgrep
58
65
pkgs.sumneko-lua-language-server
59
66
pkgs.unixtools.watch
60
-
pkgs.qmk
61
67
pkgs-unstable.jujutsu
62
68
pkgs-unstable.neovim
69
+
] ++ (if pkgs.stdenv.isDarwin then [
63
70
# TODO: automate this with nix:
64
71
# $ ln -sfn ~/.nix-profile/Applications ~/Applications/Nix\ User\ Apps
72
+
pkgs.anki-bin
73
+
pkgs.brave
74
+
pkgs.iina
75
+
pkgs.macmon
76
+
] else []);
77
+
};
78
+
packages.aarch64-linux.default = let
79
+
system = "aarch64-linux";
80
+
pkgs = import nixpkgs {
81
+
inherit system;
82
+
overlays = [ (import ./overlays/jpm.nix) ];
83
+
};
84
+
pkgs-unstable = nixpkgs-unstable.legacyPackages.${system};
85
+
in pkgs.buildEnv {
86
+
name = "my-env";
87
+
paths = [
88
+
pkgs.bacon
89
+
pkgs.btop
90
+
pkgs.dust
91
+
pkgs.eza
92
+
pkgs.fd
93
+
pkgs.gh
94
+
pkgs.hello
95
+
pkgs.janet
96
+
pkgs.jpm
97
+
pkgs.meld
98
+
pkgs.nodejs_24
99
+
pkgs.qmk
100
+
pkgs.ripgrep
101
+
pkgs.sumneko-lua-language-server
102
+
pkgs.unixtools.watch
103
+
pkgs-unstable.jujutsu
104
+
pkgs-unstable.neovim
65
105
];
66
106
};
67
107
};
+5
-2
tmux/.tmux.conf
+5
-2
tmux/.tmux.conf
···
30
30
# bind ctrl-ed version of other mappings
31
31
# e.g. `n` and `p` for navigating windows
32
32
33
-
bind-key r source-file ~/.tmux.conf
33
+
bind-key r source-file ~/.tmux.conf \; display-message "sourced"
34
34
35
35
set-window-option -g mode-keys vi
36
36
bind-key -T copy-mode-vi v send-keys -X begin-selection
37
37
bind-key -T copy-mode-vi V send-keys -X select-line
38
-
bind-key -T copy-mode-vi y send-keys -X copy-selection
38
+
# bind-key -T copy-mode-vi y send-keys -X copy-selection
39
+
bind-key -T copy-mode-vi y send-keys -X copy-pipe "pbcopy"
39
40
bind-key -T copy-mode-vi r send-keys -X rectangle-toggle
40
41
bind-key -T copy-mode-vi i send-keys -X cancel
41
42
bind-key -T copy-mode-vi C-c send-keys -X cancel
42
43
43
44
bind-key C-e run-shell "tmux neww -n 'sessionizer' tmux-sessionizer"
44
45
bind-key e run-shell "tmux neww -n 'sessionizer' tmux-sessionizer"
46
+
bind-key C-i switch-client -l
47
+
bind-key i switch-client -l
+5
-1
zsh/.zprofile
+5
-1
zsh/.zprofile
···
4
4
eval "$(/opt/homebrew/bin/brew shellenv)"
5
5
6
6
# Rust
7
-
. "$HOME/.cargo/env"
7
+
if [ -f "$HOME/.cargo/env" ]; then
8
+
. "$HOME/.cargo/env"
9
+
fi
8
10
9
11
# bob-nvim
10
12
export PATH=$HOME/.local/share/bob/nvim-bin:$PATH
···
24
26
25
27
# HACK: python binary path. honestly I don't like this hard-coded method
26
28
export PATH="$HOME/Library/Python/3.9/bin:$PATH"
29
+
30
+
export PATH="$HOME/dot/bin:$PATH"
27
31
28
32
29
33
+11
-4
zsh/.zshrc
+11
-4
zsh/.zshrc
···
7
7
alias gs="git status"
8
8
alias ta="tmux attach"
9
9
alias nn="NVIM_APPNAME=nativevim nvim"
10
+
alias nd="nix develop -c zsh -l"
10
11
11
12
## Custom prompt
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
+
setopt prompt_subst
14
+
export PS1='%F{green}%n@%m %F{blue}%~%f${IN_NIX_SHELL:+ nix:($IN_NIX_SHELL)} %# '
16
15
17
16
## Editor
18
17
export EDITOR="$(command -v nvim || command -v vim)"
···
24
23
zle -N edit-command-line
25
24
bindkey '^Xe' edit-command-line
26
25
bindkey '^X^E' edit-command-line
26
+
27
+
if [[ -n "$IN_NIX_SHELL" ]]; then
28
+
export SHELL=$(which zsh)
29
+
fi
30
+
31
+
# Added by LM Studio CLI (lms)
32
+
export PATH="$PATH:$HOME/.lmstudio/bin"
33
+
# End of LM Studio CLI section
-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