@factiii/runner 0.11.2 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,410 @@
1
+ #!/usr/bin/env bash
2
+
3
+ CURRENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
4
+
5
+ source "$CURRENT_DIR/variables.sh"
6
+ source "$CURRENT_DIR/helpers.sh"
7
+ source "$CURRENT_DIR/process_restore_helpers.sh"
8
+ source "$CURRENT_DIR/spinner_helpers.sh"
9
+
10
+ # delimiter
11
+ d=$'\t'
12
+
13
+ # Global variable.
14
+ # Used during the restore: if a pane already exists from before, it is
15
+ # saved in the array in this variable. Later, process running in existing pane
16
+ # is also not restored. That makes the restoration process more idempotent.
17
+ EXISTING_PANES_VAR=""
18
+
19
+ RESTORING_FROM_SCRATCH="false"
20
+
21
+ RESTORE_PANE_CONTENTS="false"
22
+
23
+ is_line_type() {
24
+ local line_type="$1"
25
+ local line="$2"
26
+ echo "$line" |
27
+ \grep -q "^$line_type"
28
+ }
29
+
30
+ check_saved_session_exists() {
31
+ local resurrect_file="$(last_resurrect_file)"
32
+ if [ ! -f $resurrect_file ]; then
33
+ display_message "Tmux resurrect file not found!"
34
+ return 1
35
+ fi
36
+ }
37
+
38
+ pane_exists() {
39
+ local session_name="$1"
40
+ local window_number="$2"
41
+ local pane_index="$3"
42
+ tmux list-panes -t "${session_name}:${window_number}" -F "#{pane_index}" 2>/dev/null |
43
+ \grep -q "^$pane_index$"
44
+ }
45
+
46
+ register_existing_pane() {
47
+ local session_name="$1"
48
+ local window_number="$2"
49
+ local pane_index="$3"
50
+ local pane_custom_id="${session_name}:${window_number}:${pane_index}"
51
+ local delimiter=$'\t'
52
+ EXISTING_PANES_VAR="${EXISTING_PANES_VAR}${delimiter}${pane_custom_id}"
53
+ }
54
+
55
+ is_pane_registered_as_existing() {
56
+ local session_name="$1"
57
+ local window_number="$2"
58
+ local pane_index="$3"
59
+ local pane_custom_id="${session_name}:${window_number}:${pane_index}"
60
+ [[ "$EXISTING_PANES_VAR" =~ "$pane_custom_id" ]]
61
+ }
62
+
63
+ restore_from_scratch_true() {
64
+ RESTORING_FROM_SCRATCH="true"
65
+ }
66
+
67
+ is_restoring_from_scratch() {
68
+ [ "$RESTORING_FROM_SCRATCH" == "true" ]
69
+ }
70
+
71
+ restore_pane_contents_true() {
72
+ RESTORE_PANE_CONTENTS="true"
73
+ }
74
+
75
+ is_restoring_pane_contents() {
76
+ [ "$RESTORE_PANE_CONTENTS" == "true" ]
77
+ }
78
+
79
+ restored_session_0_true() {
80
+ RESTORED_SESSION_0="true"
81
+ }
82
+
83
+ has_restored_session_0() {
84
+ [ "$RESTORED_SESSION_0" == "true" ]
85
+ }
86
+
87
+ window_exists() {
88
+ local session_name="$1"
89
+ local window_number="$2"
90
+ tmux list-windows -t "$session_name" -F "#{window_index}" 2>/dev/null |
91
+ \grep -q "^$window_number$"
92
+ }
93
+
94
+ session_exists() {
95
+ local session_name="$1"
96
+ tmux has-session -t "$session_name" 2>/dev/null
97
+ }
98
+
99
+ first_window_num() {
100
+ tmux show -gv base-index
101
+ }
102
+
103
+ tmux_socket() {
104
+ echo $TMUX | cut -d',' -f1
105
+ }
106
+
107
+ # Tmux option stored in a global variable so that we don't have to "ask"
108
+ # tmux server each time.
109
+ cache_tmux_default_command() {
110
+ local default_shell="$(get_tmux_option "default-shell" "")"
111
+ local opt=""
112
+ if [ "$(basename "$default_shell")" == "bash" ]; then
113
+ opt="-l "
114
+ fi
115
+ export TMUX_DEFAULT_COMMAND="$(get_tmux_option "default-command" "$opt$default_shell")"
116
+ }
117
+
118
+ tmux_default_command() {
119
+ echo "$TMUX_DEFAULT_COMMAND"
120
+ }
121
+
122
+ pane_creation_command() {
123
+ echo "cat '$(pane_contents_file "restore" "${1}:${2}.${3}")'; exec $(tmux_default_command)"
124
+ }
125
+
126
+ new_window() {
127
+ local session_name="$1"
128
+ local window_number="$2"
129
+ local dir="$3"
130
+ local pane_index="$4"
131
+ local pane_id="${session_name}:${window_number}.${pane_index}"
132
+ dir="${dir/#\~/$HOME}"
133
+ if is_restoring_pane_contents && pane_contents_file_exists "$pane_id"; then
134
+ local pane_creation_command="$(pane_creation_command "$session_name" "$window_number" "$pane_index")"
135
+ tmux new-window -d -t "${session_name}:${window_number}" -c "$dir" "$pane_creation_command"
136
+ else
137
+ tmux new-window -d -t "${session_name}:${window_number}" -c "$dir"
138
+ fi
139
+ }
140
+
141
+ new_session() {
142
+ local session_name="$1"
143
+ local window_number="$2"
144
+ local dir="$3"
145
+ local pane_index="$4"
146
+ local pane_id="${session_name}:${window_number}.${pane_index}"
147
+ if is_restoring_pane_contents && pane_contents_file_exists "$pane_id"; then
148
+ local pane_creation_command="$(pane_creation_command "$session_name" "$window_number" "$pane_index")"
149
+ TMUX="" tmux -S "$(tmux_socket)" new-session -d -s "$session_name" -c "$dir" "$pane_creation_command"
150
+ else
151
+ TMUX="" tmux -S "$(tmux_socket)" new-session -d -s "$session_name" -c "$dir"
152
+ fi
153
+ # change first window number if necessary
154
+ local created_window_num="$(first_window_num)"
155
+ if [ $created_window_num -ne $window_number ]; then
156
+ tmux move-window -s "${session_name}:${created_window_num}" -t "${session_name}:${window_number}"
157
+ fi
158
+ }
159
+
160
+ new_pane() {
161
+ local session_name="$1"
162
+ local window_number="$2"
163
+ local dir="$3"
164
+ local pane_index="$4"
165
+ local pane_id="${session_name}:${window_number}.${pane_index}"
166
+ if is_restoring_pane_contents && pane_contents_file_exists "$pane_id"; then
167
+ local pane_creation_command="$(pane_creation_command "$session_name" "$window_number" "$pane_index")"
168
+ tmux split-window -t "${session_name}:${window_number}" -c "$dir" "$pane_creation_command"
169
+ else
170
+ tmux split-window -t "${session_name}:${window_number}" -c "$dir"
171
+ fi
172
+ # minimize window so more panes can fit
173
+ tmux resize-pane -t "${session_name}:${window_number}" -U "999"
174
+ }
175
+
176
+ restore_pane() {
177
+ local pane="$1"
178
+ while IFS=$d read line_type session_name window_number window_active window_flags pane_index pane_title dir pane_active pane_command pane_full_command; do
179
+ dir="$(remove_first_char "$dir")"
180
+ pane_full_command="$(remove_first_char "$pane_full_command")"
181
+ if [ "$session_name" == "0" ]; then
182
+ restored_session_0_true
183
+ fi
184
+ if pane_exists "$session_name" "$window_number" "$pane_index"; then
185
+ if is_restoring_from_scratch; then
186
+ # overwrite the pane
187
+ # happens only for the first pane if it's the only registered pane for the whole tmux server
188
+ local pane_id="$(tmux display-message -p -F "#{pane_id}" -t "$session_name:$window_number")"
189
+ new_pane "$session_name" "$window_number" "$dir" "$pane_index"
190
+ tmux kill-pane -t "$pane_id"
191
+ else
192
+ # Pane exists, no need to create it!
193
+ # Pane existence is registered. Later, its process also won't be restored.
194
+ register_existing_pane "$session_name" "$window_number" "$pane_index"
195
+ fi
196
+ elif window_exists "$session_name" "$window_number"; then
197
+ new_pane "$session_name" "$window_number" "$dir" "$pane_index"
198
+ elif session_exists "$session_name"; then
199
+ new_window "$session_name" "$window_number" "$dir" "$pane_index"
200
+ else
201
+ new_session "$session_name" "$window_number" "$dir" "$pane_index"
202
+ fi
203
+ # set pane title
204
+ tmux select-pane -t "$session_name:$window_number.$pane_index" -T "$pane_title"
205
+ done < <(echo "$pane")
206
+ }
207
+
208
+ restore_state() {
209
+ local state="$1"
210
+ echo "$state" |
211
+ while IFS=$d read line_type client_session client_last_session; do
212
+ tmux switch-client -t "$client_last_session"
213
+ tmux switch-client -t "$client_session"
214
+ done
215
+ }
216
+
217
+ restore_grouped_session() {
218
+ local grouped_session="$1"
219
+ echo "$grouped_session" |
220
+ while IFS=$d read line_type grouped_session original_session alternate_window active_window; do
221
+ TMUX="" tmux -S "$(tmux_socket)" new-session -d -s "$grouped_session" -t "$original_session"
222
+ done
223
+ }
224
+
225
+ restore_active_and_alternate_windows_for_grouped_sessions() {
226
+ local grouped_session="$1"
227
+ echo "$grouped_session" |
228
+ while IFS=$d read line_type grouped_session original_session alternate_window_index active_window_index; do
229
+ alternate_window_index="$(remove_first_char "$alternate_window_index")"
230
+ active_window_index="$(remove_first_char "$active_window_index")"
231
+ if [ -n "$alternate_window_index" ]; then
232
+ tmux switch-client -t "${grouped_session}:${alternate_window_index}"
233
+ fi
234
+ if [ -n "$active_window_index" ]; then
235
+ tmux switch-client -t "${grouped_session}:${active_window_index}"
236
+ fi
237
+ done
238
+ }
239
+
240
+ never_ever_overwrite() {
241
+ local overwrite_option_value="$(get_tmux_option "$overwrite_option" "")"
242
+ [ -n "$overwrite_option_value" ]
243
+ }
244
+
245
+ detect_if_restoring_from_scratch() {
246
+ if never_ever_overwrite; then
247
+ return
248
+ fi
249
+ local total_number_of_panes="$(tmux list-panes -a | wc -l | sed 's/ //g')"
250
+ if [ "$total_number_of_panes" -eq 1 ]; then
251
+ restore_from_scratch_true
252
+ fi
253
+ }
254
+
255
+ detect_if_restoring_pane_contents() {
256
+ if capture_pane_contents_option_on; then
257
+ cache_tmux_default_command
258
+ restore_pane_contents_true
259
+ fi
260
+ }
261
+
262
+ # functions called from main (ordered)
263
+
264
+ restore_all_panes() {
265
+ detect_if_restoring_from_scratch # sets a global variable
266
+ detect_if_restoring_pane_contents # sets a global variable
267
+ if is_restoring_pane_contents; then
268
+ pane_content_files_restore_from_archive
269
+ fi
270
+ while read line; do
271
+ if is_line_type "pane" "$line"; then
272
+ restore_pane "$line"
273
+ fi
274
+ done < $(last_resurrect_file)
275
+ }
276
+
277
+ handle_session_0() {
278
+ if is_restoring_from_scratch && ! has_restored_session_0; then
279
+ local current_session="$(tmux display -p "#{client_session}")"
280
+ if [ "$current_session" == "0" ]; then
281
+ tmux switch-client -n
282
+ fi
283
+ tmux kill-session -t "0"
284
+ fi
285
+ }
286
+
287
+ restore_window_properties() {
288
+ local window_name
289
+ \grep '^window' $(last_resurrect_file) |
290
+ while IFS=$d read line_type session_name window_number window_name window_active window_flags window_layout automatic_rename; do
291
+ tmux select-layout -t "${session_name}:${window_number}" "$window_layout"
292
+
293
+ # Below steps are properly handling window names and automatic-rename
294
+ # option. `rename-window` is an extra command in some scenarios, but we
295
+ # opted for always doing it to keep the code simple.
296
+ window_name="$(remove_first_char "$window_name")"
297
+ tmux rename-window -t "${session_name}:${window_number}" "$window_name"
298
+ if [ "${automatic_rename}" = ":" ]; then
299
+ tmux set-option -u -t "${session_name}:${window_number}" automatic-rename
300
+ else
301
+ tmux set-option -t "${session_name}:${window_number}" automatic-rename "$automatic_rename"
302
+ fi
303
+ done
304
+ }
305
+
306
+ restore_shell_history() {
307
+ awk 'BEGIN { FS="\t"; OFS="\t" } /^pane/ { print $2, $3, $6, $9; }' $(last_resurrect_file) |
308
+ while IFS=$d read session_name window_number pane_index pane_command; do
309
+ if ! is_pane_registered_as_existing "$session_name" "$window_number" "$pane_index"; then
310
+ local pane_id="$session_name:$window_number.$pane_index"
311
+ local history_file="$(resurrect_history_file "$pane_id" "$pane_command")"
312
+
313
+ if [ "$pane_command" = "bash" ]; then
314
+ local read_command="history -r '$history_file'"
315
+ tmux send-keys -t "$pane_id" "$read_command" C-m
316
+ elif [ "$pane_command" = "zsh" ]; then
317
+ local accept_line="$(expr "$(zsh -i -c bindkey | grep -m1 '\saccept-line$')" : '^"\(.*\)".*')"
318
+ local read_command="fc -R '$history_file'; clear"
319
+ tmux send-keys -t "$pane_id" "$read_command" "$accept_line"
320
+ fi
321
+ fi
322
+ done
323
+ }
324
+
325
+ restore_all_pane_processes() {
326
+ if restore_pane_processes_enabled; then
327
+ local pane_full_command
328
+ awk 'BEGIN { FS="\t"; OFS="\t" } /^pane/ && $11 !~ "^:$" { print $2, $3, $6, $8, $11; }' $(last_resurrect_file) |
329
+ while IFS=$d read -r session_name window_number pane_index dir pane_full_command; do
330
+ dir="$(remove_first_char "$dir")"
331
+ pane_full_command="$(remove_first_char "$pane_full_command")"
332
+ restore_pane_process "$pane_full_command" "$session_name" "$window_number" "$pane_index" "$dir"
333
+ done
334
+ fi
335
+ }
336
+
337
+ restore_active_pane_for_each_window() {
338
+ awk 'BEGIN { FS="\t"; OFS="\t" } /^pane/ && $9 == 1 { print $2, $3, $6; }' $(last_resurrect_file) |
339
+ while IFS=$d read session_name window_number active_pane; do
340
+ tmux switch-client -t "${session_name}:${window_number}"
341
+ tmux select-pane -t "$active_pane"
342
+ done
343
+ }
344
+
345
+ restore_zoomed_windows() {
346
+ awk 'BEGIN { FS="\t"; OFS="\t" } /^pane/ && $5 ~ /Z/ && $9 == 1 { print $2, $3; }' $(last_resurrect_file) |
347
+ while IFS=$d read session_name window_number; do
348
+ tmux resize-pane -t "${session_name}:${window_number}" -Z
349
+ done
350
+ }
351
+
352
+ restore_grouped_sessions() {
353
+ while read line; do
354
+ if is_line_type "grouped_session" "$line"; then
355
+ restore_grouped_session "$line"
356
+ restore_active_and_alternate_windows_for_grouped_sessions "$line"
357
+ fi
358
+ done < $(last_resurrect_file)
359
+ }
360
+
361
+ restore_active_and_alternate_windows() {
362
+ awk 'BEGIN { FS="\t"; OFS="\t" } /^window/ && $6 ~ /[*-]/ { print $2, $4, $3; }' $(last_resurrect_file) |
363
+ sort -u |
364
+ while IFS=$d read session_name active_window window_number; do
365
+ tmux switch-client -t "${session_name}:${window_number}"
366
+ done
367
+ }
368
+
369
+ restore_active_and_alternate_sessions() {
370
+ while read line; do
371
+ if is_line_type "state" "$line"; then
372
+ restore_state "$line"
373
+ fi
374
+ done < $(last_resurrect_file)
375
+ }
376
+
377
+ # A cleanup that happens after 'restore_all_panes' seems to fix fish shell
378
+ # users' restore problems.
379
+ cleanup_restored_pane_contents() {
380
+ if is_restoring_pane_contents; then
381
+ rm "$(pane_contents_dir "restore")"/*
382
+ fi
383
+ }
384
+
385
+ main() {
386
+ if supported_tmux_version_ok && check_saved_session_exists; then
387
+ start_spinner "Restoring..." "Tmux restore complete!"
388
+ execute_hook "pre-restore-all"
389
+ restore_all_panes
390
+ handle_session_0
391
+ restore_window_properties >/dev/null 2>&1
392
+ execute_hook "pre-restore-history"
393
+ if save_shell_history_option_on; then
394
+ restore_shell_history
395
+ fi
396
+ execute_hook "pre-restore-pane-processes"
397
+ restore_all_pane_processes
398
+ # below functions restore exact cursor positions
399
+ restore_active_pane_for_each_window
400
+ restore_zoomed_windows
401
+ restore_grouped_sessions # also restores active and alt windows for grouped sessions
402
+ restore_active_and_alternate_windows
403
+ restore_active_and_alternate_sessions
404
+ cleanup_restored_pane_contents
405
+ execute_hook "post-restore-all"
406
+ stop_spinner
407
+ display_message "Tmux restore complete!"
408
+ fi
409
+ }
410
+ main