|
1 | 1 | # SEE: https://unix.stackexchange.com/questions/668618/how-to-write-automated-tests-for-zsh-completion/668827#668827 |
| 2 | +# |
| 3 | +# Background: |
| 4 | +# The Stack Exchange recipe runs `vared -c tmp` inside `zpty {,}fn`, but on |
| 5 | +# glibc-based zsh 5.9 builds (Ubuntu 24.04, Debian sid, recent Homebrew) |
| 6 | +# that combination corrupts the heap and the pty emits `free(): invalid |
| 7 | +# pointer` instead of any completions. Spawning a fresh interactive |
| 8 | +# `zsh -i -c 'source <file>'` inside the zpty gives ZLE its own session and |
| 9 | +# sidesteps the crash. We also avoid an EXIT trap on the outer shell — zpty |
| 10 | +# propagates inherited traps to the child, so an outer cleanup trap would |
| 11 | +# delete the source file out from under the next iteration. |
2 | 12 | autoload -U compinit |
3 | | -compinit |
| 13 | +compinit -u |
4 | 14 |
|
5 | 15 | <% completers.each do |comp| -%> |
6 | 16 | eval "$(<%= comp %> completion <%= bin %>)" |
| 17 | +<% end %> |
| 18 | + |
| 19 | +INNER=$(mktemp -p /var/tmp tabular_<%= pty %>.XXXXXX) || exit 1 |
| 20 | +{ |
| 21 | + print -r -- 'autoload -U compinit' |
| 22 | + print -r -- 'compinit -u' |
| 23 | +<% completers.each do |comp| -%> |
| 24 | + print -r -- 'eval "$(<%= comp %> completion <%= bin %>)"' |
7 | 25 | <% end -%> |
| 26 | + print -r -- 'D_OPN=$'\''\C-B'\' |
| 27 | + print -r -- 'D_CLS=$'\''\C-C'\' |
| 28 | + print -r -- 'zstyle ":completion:*" max-matches-width 10' |
| 29 | + print -r -- 'zstyle ":completion:*" list-colors ""' |
| 30 | + print -r -- 'zle -C {,,}complete-word' |
| 31 | + print -r -- 'complete-word () {' |
| 32 | + print -r -- ' unset "compstate[vared]"' |
| 33 | + print -r -- ' compstate[insert]=menu' |
| 34 | + print -r -- ' compadd -x "${D_OPN}"' |
| 35 | + print -r -- ' _main_complete "$@"' |
| 36 | + print -r -- ' if [ -n "$compstate[unambiguous]" ] && [ "$compstate[nmatches]" = "1" ]; then' |
| 37 | + print -r -- ' result=${compstate[unambiguous]/#$BASH_REMATCH}' |
| 38 | + print -r -- ' echo -e "${D_OPN}\n${result}\n${D_CLS}"' |
| 39 | + print -r -- ' fi' |
| 40 | + print -r -- ' compadd -J -last- -x "${D_CLS}"' |
| 41 | + print -r -- ' exit' |
| 42 | + print -r -- '}' |
| 43 | + print -r -- 'bindkey "^I" complete-word' |
| 44 | + print -r -- 'vared -c tmp' |
| 45 | +} > "${INNER}" |
| 46 | + |
| 47 | +D_OPN=$'\C-B' |
| 48 | +D_CLS=$'\C-C' |
8 | 49 |
|
9 | | -compmock () { |
10 | | - export D_OPN=$'\C-B' |
11 | | - export D_CLS=$'\C-C' |
12 | | - |
13 | | - <%= pty %> () { |
14 | | - zstyle ':completion:*' max-matches-width 10 # Ensure alt-names on separate rows |
15 | | - zstyle ':completion:*' list-colors '' # Disable colouring |
16 | | - |
17 | | - # Bind a custom widget to TAB. |
18 | | - bindkey '^I' complete-word |
19 | | - zle -C {,,}complete-word |
20 | | - complete-word () { |
21 | | - unset 'compstate[vared]' # Disguise a "normal" command-line |
22 | | - compstate[insert]=menu # Ensure menu is always displayed |
23 | | - compadd -x "${D_OPN}" # Open delimiter |
24 | | - _main_complete "$@" # Run completion |
25 | | - # Intercept unambiguous match |
26 | | - if [ -n "$compstate[unambiguous]" ] && [ "$compstate[nmatches]" = '1' ]; then |
27 | | - result=${compstate[unambiguous]/#$BASH_REMATCH} |
28 | | - echo -e "${D_OPN}\n${result}\n${D_CLS}" |
29 | | - fi |
30 | | - compadd -J -last- -x "${D_CLS}" # close delimiter |
31 | | - exit |
32 | | - } |
33 | | - |
34 | | - vared -c tmp # Start line editor |
35 | | - } |
36 | | - |
37 | | - zmodload zsh/zpty # Load the pseudo terminal module. |
38 | | - trap 'zpty -d' ABRT EXIT HUP INT QUIT TERM # Delete the pty. |
39 | | - |
40 | | - while read -r -t10 -A ARGS; do |
41 | | - zpty {,}<%= pty %> # Create a new pty and run our function in it. |
42 | | - zpty -w -n <%= pty %> "$1 $ARGS[*]"$'\t' # Simulate a command being typed and tabbed. |
43 | | - |
44 | | - # Capture terminal output |
45 | | - PTY_LINES=() |
46 | | - while zpty -r <%= pty %> REPLY; do |
47 | | - [[ "${REPLY}" =~ "${D_CLS}" ]] && break |
48 | | - [[ "${REPLY}" =~ "${D_OPN}" ]] \ |
49 | | - && PTY_LINES=() \ |
50 | | - || PTY_LINES+=("${REPLY%%$'\n'}") |
51 | | - done |
52 | | - |
53 | | - print -nrC1 -- "${PTY_LINES[@]}" | sed -r -e 's/\x1b\[[0-9;]*m?//g' -e 's/\r//g' -e 's/ *$//g' |
54 | | - print -n -- $'\C-@' |
55 | | - zpty -d <%= pty %> |
| 50 | +zmodload zsh/zpty |
| 51 | + |
| 52 | +while read -r -t10 -A ARGS; do |
| 53 | + zpty <%= pty %> "zsh -i -c 'source ${INNER}'" |
| 54 | + zpty -w -n <%= pty %> "<%= prog %> ${ARGS[*]}"$'\t' |
| 55 | + |
| 56 | + PTY_LINES=() |
| 57 | + while zpty -r <%= pty %> REPLY; do |
| 58 | + [[ "${REPLY}" == *"${D_CLS}"* ]] && break |
| 59 | + if [[ "${REPLY}" == *"${D_OPN}"* ]]; then |
| 60 | + REPLY="${REPLY##*${D_OPN}}" |
| 61 | + PTY_LINES=() |
| 62 | + fi |
| 63 | + PTY_LINES+=("${REPLY%%$'\n'}") |
56 | 64 | done |
57 | | -} |
58 | 65 |
|
59 | | -compmock <%= prog %> |
| 66 | + print -nrC1 -- "${PTY_LINES[@]}" \ |
| 67 | + | sed -r -e 's/\x1b\[[0-9;]*m?//g' -e 's/\r//g' -e 's/ *$//g' \ |
| 68 | + | sed '/^$/d' |
| 69 | + print -n -- $'\C-@' |
| 70 | + zpty -d <%= pty %> |
| 71 | +done |
| 72 | + |
| 73 | +rm -f "${INNER}" |
0 commit comments