Skip to content

Commit 42cebb9

Browse files
authored
Merge pull request matheusml#25 from matheusml/unifying-both-commands
Unifying both commands
2 parents 2a657c4 + 4c56af6 commit 42cebb9

File tree

3 files changed

+162
-66
lines changed

3 files changed

+162
-66
lines changed

lib/utils.zsh

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,21 @@ _zsh_ai_query() {
2929
fi
3030
}
3131

32+
# Shared function to handle AI command execution
33+
_zsh_ai_execute_command() {
34+
local query="$1"
35+
local cmd=$(_zsh_ai_query "$query")
36+
37+
if [[ -n "$cmd" ]] && [[ "$cmd" != "Error:"* ]] && [[ "$cmd" != "API Error:"* ]]; then
38+
echo "$cmd"
39+
return 0
40+
else
41+
# Return error
42+
echo "$cmd"
43+
return 1
44+
fi
45+
}
46+
3247
# Optional: Add a helper function for users who prefer explicit commands
3348
zsh-ai() {
3449
if [[ $# -eq 0 ]]; then
@@ -47,15 +62,40 @@ zsh-ai() {
4762
fi
4863

4964
local query="$*"
50-
local cmd=$(_zsh_ai_query "$query")
5165

52-
if [[ -n "$cmd" ]] && [[ "$cmd" != "Error:"* ]] && [[ "$cmd" != "API Error:"* ]]; then
53-
echo "$cmd"
54-
echo -n "Execute? [y/N] "
55-
read -r response
56-
if [[ "$response" =~ ^[Yy]$ ]]; then
57-
eval "$cmd"
58-
fi
66+
# Animation frames - rotating dots (same as widget)
67+
local dots=("" "" "" "" "" "" "" "" "" "")
68+
local frame=0
69+
70+
# Create a temp file for the response
71+
local tmpfile=$(mktemp)
72+
73+
# Disable job control notifications (same as widget)
74+
setopt local_options no_monitor no_notify
75+
76+
# Start the API query in background
77+
(_zsh_ai_execute_command "$query" > "$tmpfile" 2>/dev/null) &
78+
local pid=$!
79+
80+
# Animate while waiting
81+
while kill -0 $pid 2>/dev/null; do
82+
echo -ne "\r${dots[$((frame % ${#dots[@]}))]} "
83+
((frame++))
84+
sleep 0.1
85+
done
86+
87+
# Clear the line
88+
echo -ne "\r\033[K"
89+
90+
# Get the response and exit code
91+
wait $pid
92+
local exit_code=$?
93+
local cmd=$(cat "$tmpfile")
94+
rm -f "$tmpfile"
95+
96+
if [[ $exit_code -eq 0 ]] && [[ -n "$cmd" ]] && [[ "$cmd" != "Error:"* ]] && [[ "$cmd" != "API Error:"* ]]; then
97+
# Put the command in the ZLE buffer (same as # method)
98+
print -z "$cmd"
5999
else
60100
print -P "%F{red}Failed to generate command%f"
61101
if [[ -n "$cmd" ]]; then

lib/widget.zsh

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ _zsh_ai_accept_line() {
3030
# Disable job control notifications
3131
setopt local_options no_monitor no_notify
3232

33-
# Start the API query in background
33+
# Start the API query in background using the shared function
3434
# Only redirect stdout to tmpfile, let stderr go to /dev/null to avoid mixing error output
35-
(_zsh_ai_query "$query" > "$tmpfile" 2>/dev/null) &
35+
(_zsh_ai_execute_command "$query" > "$tmpfile" 2>/dev/null) &
3636
local pid=$!
3737

3838
# Animate while waiting
@@ -46,6 +46,7 @@ _zsh_ai_accept_line() {
4646

4747
# Get the response
4848
local cmd=$(cat "$tmpfile")
49+
local exit_code=$?
4950
rm -f "$tmpfile"
5051

5152
if [[ -n "$cmd" ]] && [[ "$cmd" != "Error:"* ]] && [[ "$cmd" != "API Error:"* ]]; then

tests/utils.test.zsh

Lines changed: 111 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ test_shows_ollama_model_in_usage() {
107107
teardown_test_env
108108
}
109109

110-
test_executes_command_when_user_confirms() {
110+
test_shows_command_without_executing() {
111111
setup_test_env
112112
export ZSH_AI_PROVIDER="anthropic"
113113
export ANTHROPIC_API_KEY="test-key"
@@ -117,51 +117,55 @@ test_executes_command_when_user_confirms() {
117117
echo "echo 'Hello World'"
118118
}
119119

120-
# Mock read to simulate user input
121-
read() {
122-
response="y"
123-
}
124-
125-
# Track eval execution
120+
# Track eval execution - should NOT be called
126121
local eval_called=0
127-
local eval_command=""
128122
eval() {
129123
eval_called=1
130-
eval_command="$1"
124+
}
125+
126+
# Mock print -z to capture buffer command
127+
local buffer_cmd=""
128+
print() {
129+
if [[ "$1" == "-z" ]]; then
130+
buffer_cmd="$2"
131+
else
132+
builtin print "$@"
133+
fi
131134
}
132135

133136
zsh-ai "say hello" >/dev/null 2>&1
134137

135-
assert_equals "$eval_called" "1"
136-
assert_equals "$eval_command" "echo 'Hello World'"
138+
# Should put command in buffer but NOT execute it
139+
assert_equals "$eval_called" "0"
140+
assert_equals "$buffer_cmd" "echo 'Hello World'"
137141

138142
teardown_test_env
139143
}
140144

141-
test_does_not_execute_when_user_declines() {
145+
test_puts_command_in_buffer() {
142146
setup_test_env
143147
export ZSH_AI_PROVIDER="anthropic"
144148
export ANTHROPIC_API_KEY="test-key"
145149

146150
# Mock query function
147151
_zsh_ai_query() {
148-
echo "rm -rf /"
152+
echo "ls -la"
149153
}
150154

151-
# Mock read to simulate user input
152-
read() {
153-
response="n"
155+
# Mock print -z to capture buffer command
156+
local buffer_cmd=""
157+
print() {
158+
if [[ "$1" == "-z" ]]; then
159+
buffer_cmd="$2"
160+
else
161+
builtin print "$@"
162+
fi
154163
}
155164

156-
# Track eval execution
157-
local eval_called=0
158-
eval() {
159-
eval_called=1
160-
}
165+
zsh-ai "list files" >/dev/null 2>&1
161166

162-
zsh-ai "dangerous command" >/dev/null 2>&1
163-
164-
assert_equals "$eval_called" "0"
167+
# Should put command in buffer
168+
assert_equals "$buffer_cmd" "ls -la"
165169

166170
teardown_test_env
167171
}
@@ -176,9 +180,9 @@ test_handles_api_errors_in_zsh_ai() {
176180
echo "Error: API connection failed"
177181
}
178182

179-
# Capture output
183+
# Capture output with stderr
180184
local output
181-
output=$(zsh-ai "test query")
185+
output=$(zsh-ai "test query" 2>&1)
182186
local result=$?
183187

184188
assert_equals "$result" "1"
@@ -198,9 +202,9 @@ test_handles_empty_response_in_zsh_ai() {
198202
echo ""
199203
}
200204

201-
# Capture output
205+
# Capture output with stderr
202206
local output
203-
output=$(zsh-ai "test query")
207+
output=$(zsh-ai "test query" 2>&1)
204208
local result=$?
205209

206210
assert_equals "$result" "1"
@@ -214,24 +218,30 @@ test_combines_multiple_arguments() {
214218
export ZSH_AI_PROVIDER="anthropic"
215219
export ANTHROPIC_API_KEY="test-key"
216220

217-
# Mock query function to echo the query
218-
_zsh_ai_query() {
219-
echo "query:$1"
221+
# Mock execute command function
222+
_zsh_ai_execute_command() {
223+
echo "find . -name '*.py'"
220224
}
221225

222-
# Mock read to decline execution
223-
read() {
224-
response="n"
226+
# Mock print -z to capture buffer command
227+
local buffer_cmd=""
228+
print() {
229+
if [[ "$1" == "-z" ]]; then
230+
buffer_cmd="$2"
231+
else
232+
builtin print "$@"
233+
fi
225234
}
226235

227-
local output
228-
output=$(zsh-ai find all python files)
229-
assert_contains "$output" "query:find all python files"
236+
zsh-ai find all python files >/dev/null 2>&1
237+
238+
# Should put command in buffer
239+
assert_equals "$buffer_cmd" "find . -name '*.py'"
230240

231241
teardown_test_env
232242
}
233243

234-
test_shows_generated_command_before_prompt() {
244+
test_puts_generated_command_in_buffer() {
235245
setup_test_env
236246
export ZSH_AI_PROVIDER="anthropic"
237247
export ANTHROPIC_API_KEY="test-key"
@@ -241,20 +251,25 @@ test_shows_generated_command_before_prompt() {
241251
echo "ls -la"
242252
}
243253

244-
# Mock read to decline execution
245-
read() {
246-
response="n"
254+
# Mock print -z to capture buffer command
255+
local buffer_cmd=""
256+
print() {
257+
if [[ "$1" == "-z" ]]; then
258+
buffer_cmd="$2"
259+
else
260+
builtin print "$@"
261+
fi
247262
}
248263

249-
local output
250-
output=$(zsh-ai "list files")
251-
assert_contains "$output" "ls -la"
252-
assert_contains "$output" "Execute? [y/N]"
264+
zsh-ai "list files" >/dev/null 2>&1
265+
266+
# Should put command in buffer
267+
assert_equals "$buffer_cmd" "ls -la"
253268

254269
teardown_test_env
255270
}
256271

257-
test_case_insensitive_confirmation() {
272+
test_no_execution_happens() {
258273
setup_test_env
259274
export ZSH_AI_PROVIDER="anthropic"
260275
export ANTHROPIC_API_KEY="test-key"
@@ -264,18 +279,57 @@ test_case_insensitive_confirmation() {
264279
echo "pwd"
265280
}
266281

267-
# Test with uppercase Y
268-
read() {
269-
response="Y"
270-
}
271-
282+
# Track eval execution - should NOT be called
272283
local eval_called=0
273284
eval() {
274285
eval_called=1
275286
}
276287

288+
# Mock print -z to capture buffer command
289+
local buffer_cmd=""
290+
print() {
291+
if [[ "$1" == "-z" ]]; then
292+
buffer_cmd="$2"
293+
else
294+
builtin print "$@"
295+
fi
296+
}
297+
277298
zsh-ai "show directory" >/dev/null 2>&1
278-
assert_equals "$eval_called" "1"
299+
300+
# Should NOT execute the command
301+
assert_equals "$eval_called" "0"
302+
# Should put command in buffer
303+
assert_equals "$buffer_cmd" "pwd"
304+
305+
teardown_test_env
306+
}
307+
308+
test_shows_loading_spinner() {
309+
setup_test_env
310+
export ZSH_AI_PROVIDER="anthropic"
311+
export ANTHROPIC_API_KEY="test-key"
312+
313+
# Mock query function with delay to simulate API call
314+
_zsh_ai_query() {
315+
sleep 0.3
316+
echo "ls -la"
317+
}
318+
319+
# Mock print -z to capture buffer command
320+
local buffer_cmd=""
321+
print() {
322+
if [[ "$1" == "-z" ]]; then
323+
buffer_cmd="$2"
324+
else
325+
builtin print "$@"
326+
fi
327+
}
328+
329+
zsh-ai "list files" >/dev/null 2>&1
330+
331+
# Should put command in buffer
332+
assert_equals "$buffer_cmd" "ls -la"
279333

280334
teardown_test_env
281335
}
@@ -287,10 +341,11 @@ test_routes_to_ollama_provider && echo "✓ Routes to Ollama provider when confi
287341
test_checks_ollama_availability_before_querying && echo "✓ Checks Ollama availability before querying"
288342
test_shows_usage_without_arguments && echo "✓ Shows usage when called without arguments"
289343
test_shows_ollama_model_in_usage && echo "✓ Shows Ollama model in usage for Ollama provider"
290-
test_executes_command_when_user_confirms && echo "Executes command when user confirms"
291-
test_does_not_execute_when_user_declines && echo "Does not execute command when user declines"
344+
test_shows_command_without_executing && echo "Shows command without executing"
345+
test_puts_command_in_buffer && echo "Puts command in buffer"
292346
test_handles_api_errors_in_zsh_ai && echo "✓ Handles API errors in zsh-ai command"
293347
test_handles_empty_response_in_zsh_ai && echo "✓ Handles empty response in zsh-ai command"
294348
test_combines_multiple_arguments && echo "✓ Combines multiple arguments in zsh-ai command"
295-
test_shows_generated_command_before_prompt && echo "✓ Shows generated command before execution prompt"
296-
test_case_insensitive_confirmation && echo "✓ Case insensitive confirmation acceptance"
349+
test_puts_generated_command_in_buffer && echo "✓ Puts generated command in buffer"
350+
test_no_execution_happens && echo "✓ No execution happens"
351+
test_shows_loading_spinner && echo "✓ Shows loading spinner during command generation"

0 commit comments

Comments
 (0)