Apakah ada cara untuk secara sistematis mengakses dan menyimpan daftar kandidat penyelesaian di Zsh?

5

Di Zsh secara default tabkuncinya terikat expand-or-complete. Saya ingin secara sistematis mengakses daftar kandidat yang telah selesai diproduksi oleh penekanan tab, sehingga saya dapat menulis fungsi saya sendiri dan memfilter daftar sendiri. Saya mengerti bahwa ada "kerangka penyelesaian" yang datang dengan Zsh tapi saya ingin melakukannya sendiri.

Ada juga list-choicesfungsi / widget yang menghasilkan output yang sama seperti expand-or-completetetapi tidak menawarkan fungsi tab cycling.

Saya telah melakukan pencarian yang cukup luas di Google dan juga mencolek sumber Zsh tetapi muncul kering. Bantuan apa pun akan dihargai.

John Lunzer
sumber
1
Pertanyaan menarik. Sudahkah Anda melihat implementasi Bash dari konsep yang sama ?
JakeGould

Jawaban:

4

Secara tidak langsung berkat JakeGould saya stumbled atas satu solusi: zsh-capture-completion. Sebenarnya ada dua lainnya pertanyaan yang hampir identik pada situs Unix Stack Exchange, baik dengan jawaban yang saya berikan di sini.

Kode sumber skrip untuk zsh-capture-completiondapat ditemukan di sini:

#!/bin/zsh

zmodload zsh/zpty || { echo 'error: missing module zsh/zpty' >&2; exit 1 }

# spawn shell
zpty z zsh -f -i

# line buffer for pty output
local line

setopt rcquotes
() {
    zpty -w z source $1
    repeat 4; do
        zpty -r z line
        [[ $line == ok* ]] && return
    done
    echo 'error initializing.' >&2
    exit 2
} =( <<< '
# no prompt!
PROMPT=
# load completion system
autoload compinit
compinit -d ~/.zcompdump_capture
# never run a command
bindkey ''^M'' undefined
bindkey ''^J'' undefined
bindkey ''^I'' complete-word
# send a line with null-byte at the end before and after completions are output
null-line () {
    echo -E - $''\0''
}
compprefuncs=( null-line )
comppostfuncs=( null-line exit )
# never group stuff!
zstyle '':completion:*'' list-grouped false
# don''t insert tab when attempting completion on empty line
zstyle '':completion:*'' insert-tab false
# no list separator, this saves some stripping later on
zstyle '':completion:*'' list-separator ''''
# we use zparseopts
zmodload zsh/zutil
# override compadd (this our hook)
compadd () {
    # check if any of -O, -A or -D are given
    if [[ ${@[1,(i)(-|--)]} == *-(O|A|D)\ * ]]; then
        # if that is the case, just delegate and leave
        builtin compadd "$@"
        return $?
    fi
    # ok, this concerns us!
    # echo -E - got this: "$@"
    # be careful with namespacing here, we don''t want to mess with stuff that
    # should be passed to compadd!
    typeset -a __hits __dscr __tmp
    # do we have a description parameter?
    # note we don''t use zparseopts here because of combined option parameters
    # with arguments like -default- confuse it.
    if (( $@[(I)-d] )); then # kind of a hack, $+@[(r)-d] doesn''t work because of line noise overload
        # next param after -d
        __tmp=${@[$[${@[(i)-d]}+1]]}
        # description can be given as an array parameter name, or inline () array
        if [[ $__tmp == \(* ]]; then
            eval "__dscr=$__tmp"
        else
            __dscr=( "${(@P)__tmp}" )
        fi
    fi
    # capture completions by injecting -A parameter into the compadd call.
    # this takes care of matching for us.
    builtin compadd -A __hits -D __dscr "$@"
    setopt localoptions norcexpandparam extendedglob
    # extract prefixes and suffixes from compadd call. we can''t do zsh''s cool
    # -r remove-func magic, but it''s better than nothing.
    typeset -A apre hpre hsuf asuf
    zparseopts -E P:=apre p:=hpre S:=asuf s:=hsuf
    # append / to directories? we are only emulating -f in a half-assed way
    # here, but it''s better than nothing.
    integer dirsuf=0
    # don''t be fooled by -default- >.>
    if [[ -z $hsuf && "${${@//-default-/}% -# *}" == *-[[:alnum:]]#f* ]]; then
        dirsuf=1
    fi
    # just drop
    [[ -n $__hits ]] || return
    # this is the point where we have all matches in $__hits and all
    # descriptions in $__dscr!
    # display all matches
    local dsuf dscr
    for i in {1..$#__hits}; do
        # add a dir suffix?
        (( dirsuf )) && [[ -d $__hits[$i] ]] && dsuf=/ || dsuf=
        # description to be displayed afterwards
        (( $#__dscr >= $i )) && dscr=" -- ${${__dscr[$i]}##$__hits[$i] #}" || dscr=
        echo -E - $IPREFIX$apre$hpre$__hits[$i]$dsuf$hsuf$asuf$dscr
    done
}
# signal success!
echo ok')

zpty -w z "$*"$'\t'

integer tog=0
# read from the pty, and parse linewise
while zpty -r z; do :; done | while IFS= read -r line; do
    if [[ $line == *$'\0\r' ]]; then
        (( tog++ )) && return 0 || continue
    fi
    # display between toggles
    (( tog )) && echo -E - $line
done

return 2

Berikut ini adalah contoh penggunaan skrip:

══► % cd ~/.zsh_plugins
══► % zsh ./zsh-capture-completion/capture.zsh 'cd '
zaw/
zsh-capture-completion/
zsh-syntax-highlighting/
zsh-vimode-visual/

Perhatikan karakter spasi dalam perintah di atas. Dengan spasi skrip menyediakan daftar folder yang dapat Anda cdmasuki dari direktori saat ini. Tanpanya script akan menyediakan semua penyelesaian untuk sebuah perintah yang dimulai dengan cd.

Saya juga harus mencatat bahwa bahkan penulis script / plugin yang disediakan menganggap solusinya "hacky". Jika ada yang tahu solusi yang lebih pendek atau lebih lurus saya akan senang menerimanya sebagai jawabannya.

John Lunzer
sumber
1
Kerja bagus! Menambahkan kode sumber untuk skrip itu sendiri karena — pada akhirnya — dari kode itu cukup pendek, selalu lebih baik untuk mempostingnya dalam jawaban karena tautan (dan isinya) sering kali dapat menghilang.
JakeGould
Terima kasih, saya agak di pagar tentang menambahkannya. Saya pikir itu hanya pada tebing menjadi "cukup pendek" atau "terlalu lama". Mengingat pengalaman Anda, saya akan tunduk pada penilaian Anda.
John Lunzer
Tidak masalah. Perhatikan bagaimana kode sumber ditempatkan dalam elemen gulir. Jadi panjang bukan urusan penuh kecuali itu benar-benar panjang dan di luar kendali.
JakeGould
1
Juga, ada bahasa yang berpotensi menyinggung / tidak sensitif dalam kode sumber.
John Lunzer
Astaga! Setidaknya itu hanya komentar dan saya menghapusnya.
JakeGould