Skip to content

Commit 3e05193

Browse files
committed
fix(script): add shell mode support for bash builtins in run_with_exit_prompt
1 parent e482821 commit 3e05193

4 files changed

Lines changed: 124 additions & 59 deletions

File tree

scripts/find_and_clone_repo

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#!/bin/bash
22
# find_and_clone_repo: Clone a GitHub repository using fzf and gum
3+
# TODO: Why does this script exist?
34
set -euo pipefail
45

56
# Pull reusable spinners/exit helpers

scripts/find_and_clone_repo_and_connect_with_sesh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ function find_and_clone_repo_and_connect_with_sesh() {
4444
# If user selected "Search fresh repos", wait for API and show full results
4545
if [[ "$selection" == "🔄 Search fresh repos..." ]]; then
4646
# Wait for background API call to complete
47-
run_with_exit_prompt "Searching for GitHub Repos..." bash -lc "wait $api_pid"
47+
run_with_exit_prompt "Searching for GitHub Repos..." \
48+
wait $api_pid;
4849

4950
selection="$( \
5051
(

scripts/find_and_open_repo_in_browser

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@ function find_and_open_repo_in_browser() {
3232
# If user selected "Search fresh repos", wait for API and show full results
3333
if [[ "$selection" == "🔄 Search fresh repos..." ]]; then
3434
# Wait for background API call to complete; allow Esc to cancel the wait
35-
run_with_exit_prompt "Searching for GitHub Repos..." bash -lc "wait $api_pid"
35+
run_with_exit_prompt "Searching for GitHub Repos..." \
36+
wait $api_pid;
3637

3738
selection="$(
3839
(

scripts/helpers/run_with_exit_prompt.sh

Lines changed: 119 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# Display help for run_with_exit_prompt
44
run_with_exit_prompt_help() {
5-
cat <<'EOF'
5+
cat <<'EOF'
66
run_with_exit_prompt: show a spinner while running a command, optionally allowing cancel with Esc/ctrl+c.
77
88
Usage:
@@ -15,71 +15,133 @@ Flags:
1515
Run the command in the foreground so it can read from the TTY (passphrases, prompts, etc.).
1616
Note: Esc-to-cancel is disabled in interactive mode; use Ctrl+C.
1717
18+
--shell
19+
Run the command in the current shell (no background). Needed for bash builtins / job control:
20+
wait, jobs, fg, bg, disown, cd, export, set, trap, etc.
21+
Note: Esc-to-cancel is disabled in --shell mode; use Ctrl+C.
22+
1823
-h, --help
1924
Show this help.
2025
2126
Notes:
2227
- Default (non-interactive) mode runs the command in the background and buffers stdout to a temp file.
2328
This allows Esc-to-cancel, but commands that need stdin will not work properly (use -i).
29+
- If the command is a bash builtin/keyword, run_with_exit_prompt automatically switches to --shell.
30+
Use --pid for cancellable waiting on a child process.
2431
- Use "--" to separate the message from the command if you want.
2532
EOF
2633
}
2734

2835
function run_with_exit_prompt() {
29-
local interactive=0
30-
31-
# parse flags
32-
while [[ $# -gt 0 ]]; do
33-
case "$1" in
34-
-i|--interactive) interactive=1; shift ;;
35-
-h|--help) run_with_exit_prompt_help; return 0 ;;
36-
--) shift; break ;;
37-
*) break ;;
38-
esac
39-
done
40-
41-
local wait_message="$1"
42-
shift
43-
44-
# interactive path: foreground (stdin preserved)
45-
if [[ $interactive -eq 1 ]]; then
46-
gum spin --spinner minidot \
47-
--title "$wait_message (Ctrl+C to cancel)" \
48-
-- "$@"
49-
return $?
50-
fi
51-
52-
# (rest of your existing non-interactive implementation stays the same,
53-
# but make tmpfile local)
54-
local tmpfile
55-
tmpfile=$(mktemp)
56-
"$@" >"$tmpfile" &
57-
local cmd_pid=$!
58-
local spinner_pid=""
59-
60-
gum spin --spinner minidot --title "$wait_message (Press Esc or ctrl+c to exit)" -- sleep 999999 >&2 &
61-
spinner_pid=$!
62-
63-
while kill -0 "$cmd_pid" 2>/dev/null; do
64-
if read -rsn1 -t 0.1 key; then
65-
if [[ $key == $'\e' ]]; then
66-
printf "\nExiting...\n" >&2
67-
kill "$cmd_pid" 2>/dev/null || true
68-
[[ -n ${spinner_pid:-} ]] && kill "$spinner_pid" 2>/dev/null || true
69-
rm -f "$tmpfile"
70-
return 130
71-
fi
72-
fi
73-
done
74-
75-
[[ -n ${spinner_pid:-} ]] && kill "$spinner_pid" 2>/dev/null || true
76-
[[ -n ${spinner_pid:-} ]] && wait "$spinner_pid" 2>/dev/null || true
77-
78-
if wait "$cmd_pid"; then
79-
cat "$tmpfile"
80-
rm -f "$tmpfile"
81-
else
82-
rm -f "$tmpfile"
83-
return 1
84-
fi
36+
local interactive=0
37+
local shell_mode=0
38+
39+
# parse flags
40+
# # -- separates flags from positional args
41+
while [[ $# -gt 0 ]]; do
42+
case "$1" in
43+
-i | --interactive)
44+
interactive=1
45+
shift
46+
;;
47+
--shell)
48+
shell_mode=1
49+
shift
50+
;;
51+
-h | --help)
52+
run_with_exit_prompt_help
53+
return 0
54+
;;
55+
--)
56+
shift
57+
break
58+
;;
59+
*) break ;;
60+
esac
61+
done
62+
63+
# ensure we have at least 2 args left: message + command
64+
local wait_message="$1"
65+
shift
66+
67+
# If the command is a bash builtin/keyword, it must run in this shell.
68+
if [[ $# -gt 0 ]] && [[ $shell_mode -eq 0 ]]; then
69+
local t
70+
t=$(type -t -- "$1" 2>/dev/null || true)
71+
if [[ "$t" == "builtin" || "$t" == "keyword" ]]; then
72+
shell_mode=1
73+
fi
74+
fi
75+
76+
# same-shell path: foreground (stdin preserved)
77+
# Ctrl+C to cancel; Esc-to-cancel disabled
78+
if [[ $shell_mode -eq 1 ]]; then
79+
local spinner_pid=""
80+
gum spin --spinner minidot \
81+
--title "$wait_message (Ctrl+C to cancel)" \
82+
-- sleep 999999 >&2 &
83+
spinner_pid=$!
84+
85+
"$@"
86+
local rc=$?
87+
88+
kill "$spinner_pid" 2>/dev/null || true
89+
wait "$spinner_pid" 2>/dev/null || true
90+
return $rc
91+
fi
92+
93+
# interactive path: foreground (stdin preserved)
94+
# Ctrl+C to cancel; Esc-to-cancel disabled
95+
if [[ $interactive -eq 1 ]]; then
96+
gum spin --spinner minidot \
97+
--title "$wait_message (Ctrl+C to cancel)" \
98+
-- "$@" # Run command in foreground with spinner
99+
return $? # Return command exit status
100+
fi
101+
102+
# Non-interactive path: background (stdin not preserved)
103+
# Esc-to-cancel enabled
104+
# Buffer stdout to temp file to avoid interleaving with spinner
105+
local tmpfile
106+
tmpfile=$(mktemp) # safely create unique temp file
107+
"$@" >"$tmpfile" & # Run command in background, redirecting stdout to temp file
108+
local cmd_pid=$! # Capture command PID
109+
local spinner_pid="" # Initialize spinner PID variable
110+
111+
# Start spinner in background redirected to stderr (to avoid mixing with command output)
112+
gum spin --spinner minidot --title "$wait_message (Press Esc or ctrl+c to exit)" -- sleep 999999 >&2 &
113+
spinner_pid=$! # Capture spinner PID
114+
115+
# Loop to check if command is done, and read for Esc keypress to kill command if needed
116+
while kill -0 "$cmd_pid" 2>/dev/null; do
117+
# -r: raw mode (no echo), to avoid interpreting backslashes
118+
# -s: silent (no echo), do not print input to terminal
119+
# -n1: read 1 character
120+
# -t 0.1: timeout after 0.1 seconds, so we can loop and check if command is done
121+
local key=""
122+
if read -rsn1 -t 0.1 key; then
123+
if [[ $key == $'\e' ]]; then # Esc key pressed
124+
printf "\nExiting...\n" >&2
125+
kill "$cmd_pid" 2>/dev/null || true
126+
[[ -n ${spinner_pid:-} ]] && kill "$spinner_pid" 2>/dev/null || true
127+
rm -f "$tmpfile"
128+
return 130
129+
fi
130+
fi
131+
done
132+
133+
# Clean up spinner after command completes
134+
[[ -n ${spinner_pid:-} ]] && kill "$spinner_pid" 2>/dev/null || true
135+
[[ -n ${spinner_pid:-} ]] && wait "$spinner_pid" 2>/dev/null || true
136+
137+
# Check command exit status
138+
if wait "$cmd_pid"; then
139+
# On success, output buffered stdout
140+
cat "$tmpfile"
141+
rm -f "$tmpfile"
142+
else
143+
# On failure, clean up and return error
144+
rm -f "$tmpfile"
145+
return 1
146+
fi
85147
}

0 commit comments

Comments
 (0)