Skip to content

Commit 45bb4a9

Browse files
committed
fix(spec): wait for inner zsh ready and use portable mktemp
Two macOS-specific regressions in the new zpty-based mock: - `mktemp -p /var/tmp …` is GNU-only. BSD `mktemp` (macOS) treats `-p` as the prefix flag, so the inner script either pointed at a stale path or was empty. Build the path explicitly under TMPDIR (or /tmp) and `:>` it instead, which both shells honor. - `zpty mock 'zsh -fi -c …'` returns immediately, but ZLE inside the inner shell isn't ready until later. macOS pty buffering let the TAB land before `bindkey` had taken effect, so `vared` sat there forever. Have the inner shell print a `\C-E` sentinel right before `vared`, and gate `zpty -w` on receiving it. The marker uses a trailing newline because the inner shell's stdout is line-buffered. Also pass `-f` alongside `-i` so the inner shell skips user rcfiles and starts up faster.
1 parent 89cf657 commit 45bb4a9

1 file changed

Lines changed: 22 additions & 3 deletions

File tree

src/tabular/shell/zsh.ecr

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,26 @@
55
# glibc-based zsh 5.9 builds (Ubuntu 24.04, Debian sid, recent Homebrew)
66
# that combination corrupts the heap and the pty emits `free(): invalid
77
# pointer` instead of any completions. Spawning a fresh interactive
8-
# `zsh -i -c 'source <file>'` inside the zpty gives ZLE its own session and
8+
# `zsh -fi -c 'source <file>'` inside the zpty gives ZLE its own session and
99
# sidesteps the crash. We also avoid an EXIT trap on the outer shell — zpty
1010
# propagates inherited traps to the child, so an outer cleanup trap would
1111
# delete the source file out from under the next iteration.
12+
#
13+
# The inner shell prints a READY sentinel right before `vared` activates, so
14+
# the outer shell waits for it before sending input. Without that handshake,
15+
# macOS pty buffering races with `zpty -w` and the TAB lands before ZLE has
16+
# bound it, hanging on `vared` forever.
1217
autoload -U compinit
1318
compinit -u
1419

1520
<% completers.each do |comp| -%>
1621
eval "$(<%= comp %> completion <%= bin %>)"
1722
<% end %>
1823

19-
INNER=$(mktemp -p /var/tmp tabular_<%= pty %>.XXXXXX) || exit 1
24+
# `mktemp -p` is GNU-only; BSD `mktemp` (macOS) treats `-p` as the prefix flag.
25+
# Use an explicit path under TMPDIR (or /tmp) instead, which both honor.
26+
INNER="${TMPDIR:-/tmp}/tabular_<%= pty %>.$$"
27+
: > "${INNER}" || exit 1
2028
{
2129
print -r -- 'autoload -U compinit'
2230
print -r -- 'compinit -u'
@@ -25,6 +33,7 @@ INNER=$(mktemp -p /var/tmp tabular_<%= pty %>.XXXXXX) || exit 1
2533
<% end -%>
2634
print -r -- 'D_OPN=$'\''\C-B'\'
2735
print -r -- 'D_CLS=$'\''\C-C'\'
36+
print -r -- 'D_RDY=$'\''\C-E'\'
2837
print -r -- 'zstyle ":completion:*" max-matches-width 10'
2938
print -r -- 'zstyle ":completion:*" list-colors ""'
3039
print -r -- 'zle -C {,,}complete-word'
@@ -41,16 +50,26 @@ INNER=$(mktemp -p /var/tmp tabular_<%= pty %>.XXXXXX) || exit 1
4150
print -r -- ' exit'
4251
print -r -- '}'
4352
print -r -- 'bindkey "^I" complete-word'
53+
# Trailing newline is required: pty stdout is line-buffered, so a bare
54+
# `\C-E` would sit in the inner shell's buffer and the outer would block.
55+
print -r -- 'print -- "${D_RDY}"'
4456
print -r -- 'vared -c tmp'
4557
} > "${INNER}"
4658

4759
D_OPN=$'\C-B'
4860
D_CLS=$'\C-C'
61+
D_RDY=$'\C-E'
4962

5063
zmodload zsh/zpty
5164

5265
while read -r -t10 -A ARGS; do
53-
zpty <%= pty %> "zsh -i -c 'source ${INNER}'"
66+
zpty <%= pty %> "zsh -fi -c 'source ${INNER}'"
67+
68+
# Wait until the inner shell has bound TAB and is parked in `vared`.
69+
while zpty -r <%= pty %> REPLY; do
70+
[[ "${REPLY}" == *"${D_RDY}"* ]] && break
71+
done
72+
5473
zpty -w -n <%= pty %> "<%= prog %> ${ARGS[*]}"$'\t'
5574

5675
PTY_LINES=()

0 commit comments

Comments
 (0)