Impossibile eseguire la ricerca di due parole nel completamento della TAB di Zsh per Man
-
20-08-2019 - |
Domanda
Problema: avere un completamento con una scheda che richiede due parole e calcola la migliore corrispondenza tra loro per Man, quindi restituisce le migliori corrispondenze
Esempio: il seguente pseudo-codice dovrebbe darmi almeno il comando di completamento del menu inverso di Zsh. Al momento, non riesco a cercare manuali all'interno di manuali senza zgrep.
man zsh:reverse <TAB>
dove ": " è il separatore che voglio.
Problema iniziale: quali file viene eseguito il completamento TAB quando premo TAB per una parola nei manuali di ricerca di Zsh?
Soluzione
Cercherò di fornire una panoramica su come funziona il sistema di completamento zsh e una soluzione incompleta a questo problema.
Il file che viene eseguito quando si utilizza il completamento TAB per man
in zsh si trova nella directory /usr/share/zsh/${ZSH_VERSION}/functions
. L'albero varia tra le distribuzioni, ma il file è denominato _man
e fornisce il completamento per apropos
, whatis
e --local-file
.
Dopo che è stato invocato _man, funziona come segue (descrizione approssimativa):
- se il completamento per
_files
emanpath
è stato specificato come primo flag, invocare il completamento dei file standard ($MANPATH
) - costruisci
zstyle ':completion:*:manuals' separate-sections true
variabile da una serie di valori predefiniti /_man_pages
. Qui è dove verranno cercate le manpage - determina se abbiamo invocato
compfiles -p pages '' '' "$matcher" '' dummy '*'
con un parametro del numero di sezione, se sì - verranno cercate solo quelle sezioni - se è stato utilizzato
pages
, separare le sezioni nell'output (non mescolarle tra loro) - invoca
$PREFIX
per fornire un elenco di pagine man per la partita -
compfiles
ora fa un po 'di magia con*
./usr/share/man/man1
è la variabile con tutte le directory che contengono le manpage per le sezioni richieste. Il modello di globbing effettivo è costruito dal parametro implicito/usr/share/man/man1/foo*
e l'ultimo parametro incompadd
-zsh:foo
in questo caso. Ciò si traduce inzsh*
da trasformare infoo
- Il nuovo elenco di pattern glob è globbed, ottenendo tutti i file che corrispondono al pattern
-
:
quindi elimina tutti i suffissi dai file e li aggiunge all'elenco dei widget di completamento delle scelte utilizzando#echo "$p matched $manpage_grep"
Ora, come puoi vedere, l'elenco delle pagine man è determinato direttamente dalla variabile <=>. Per fare in modo che <=> elenchi solo le pagine man di <=> che contengono la parola <=>, è necessario dividerle tra <=> carattere (se presente).
La seguente aggiunta in <=> risolve parzialmente il problema (zsh 4.3.4):
originale:
_man_pages() {
local matcher pages dummy sopt
zparseopts -E M+:=matcher
if (( $#matcher )); then
matcher=( ${matcher:#-M} )
matcher="$matcher"
else
matcher=
fi
pages=( ${(M)dirs:#*$sect/} )
compfiles -p pages '' '' "$matcher" '' dummy '*'
pages=( ${^~pages}(N:t) )
(($#mrd)) && pages[$#pages+1]=($(awk $awk $mrd))
# Remove any compression suffix, then remove the minimum possible string
# beginning with .<->: that handles problem cases like files called
# `POSIX.1.5'.
[[ $OSTYPE = solaris* ]] && sopt='-s '
if ((CURRENT > 2)) ||
! zstyle -t ":completion:${curcontext}:manuals.$sect" insert-sections
then
compadd "$@" - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
else
compadd "$@" -P "$sopt$sect " - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
fi
}
Modificato (cercare ## commenti mod):
_man_pages() {
local matcher pages dummy sopt
zparseopts -E M+:=matcher
if (( $#matcher )); then
matcher=( ${matcher:#-M} )
matcher="$matcher"
else
matcher=
fi
pages=( ${(M)dirs:#*$sect/} )
##mod
# split components by the ":" character
local pref_words manpage_grep orig_prefix
# save original prefix (just in case)
orig_prefix=${PREFIX}
# split $PREFIX by ':' and make it an array
pref_words=${PREFIX//:/ }
set -A pref_words ${=pref_words}
# if there are both manpage name and grep string, use both
if (( $#pref_words == 2 )); then
manpage_grep=$pref_words[2]
# PREFIX is used internally by compfiles
PREFIX=$pref_words[1]
elif (( $#pref_words == 1 )) && [[ ${PREFIX[1,1]} == ":" ]]; then
# otherwise, prefix is empty and only grep string exists
PREFIX=
manpage_grep=$pref_words[1]
fi
compfiles -p pages '' '' "$matcher" '' dummy '*'
##mod: complete, but don't strip path names
pages=( ${^~pages} )
(($#mrd)) && pages[$#pages+1]=($(awk $awk $mrd))
##mod: grep pages
# Build a list of matching pages that pass the grep
local matching_pages
typeset -a matching_pages
# manpage_grep exists and not empty
if (( ${#manpage_grep} > 0 )); then
for p in ${pages}; do
zgrep "${manpage_grep}" $p > /dev/null
if (( $? == 0 )); then
#echo "$p matched $manpage_grep"
matching_pages+=($p)
fi
done
else
# there's no manpage_grep, so all pages match
matching_pages=( ${pages} )
fi
#echo "\nmatching manpages: "${matching_pages}
pages=( ${matching_pages}(N:t) )
# keep the stripped prefix for now
#PREFIX=${orig_prefix}
# Remove any compression suffix, then remove the minimum possible string
# beginning with .<->: that handles problem cases like files called
# `POSIX.1.5'.
[[ $OSTYPE = solaris* ]] && sopt='-s '
if ((CURRENT > 2)) ||
! zstyle -t ":completion:${curcontext}:manuals.$sect" insert-sections
then
compadd "$@" - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
else
compadd "$@" -P "$sopt$sect " - ${pages%.(?|<->*(|.gz|.bz2|.Z))}
fi
}
Tuttavia, non funziona ancora completamente (se togli il commento alla riga <=>, puoi vedere che costruisce l'elenco) - sospetto che da qualche parte internamente, il sistema di completamento vede che, ad esempio, " ; & zshcompctl quot; non corrisponde al prefisso " zsh: foo " ;, e non visualizza le corrispondenze risultanti. Ho provato a mantenere <=> come è dopo aver rimosso la stringa grep, ma continua a non funzionare.
Ad ogni modo, questo almeno dovrebbe iniziare.