Vim Script: come passare Varargs a un lambda in timer_start
Sto configurando il mio vim con Vim Script.
Ho impostato la mia funzione grep personalizzata come di seguito:
function! CustomGrepCore(...)
if a:0 == 0
" do something
else if a:0 == 1
" do something2
else
" do something3
endfunction
function! CustomGrep(...)
let param = a:000
let F = {p -> call(function('CustomGrepCore'), p)}
call F(param)
endfunction
command! -nargs=? Grep :call CustomGrep(<f-args>)
Il codice sopra ha funzionato come previsto. Posso eseguire :Grep xxxper fare il mio grep personalizzato nel mio vim.
Ora voglio usare la funzione vim 8: timer_startper rendere il mio grep personalizzato asincrono, come da mia comprensione, ho solo bisogno di ricodificare la funzione CustomGrep:
function! CustomGrep(...)
let param = a:000
let F = {p -> call(function('CustomGrepCore'), p)}
call timer_start(10, {param -> execute("call F(param)")}, "") " ERROR: Invalid argument
endfunction
Ma ho sempre ottenuto l'errore: Invalid argument.
Come risolvere questo problema?
Inoltre, come vedi ho usato timer_start, sapevo che avrebbe generato un ID lavoro, che ci permette di mettere in pausa / interrompere il lavoro. Nel mio caso, devo interrompere esplicitamente il lavoro?
Aggiungi un'altra cosa
C'è ancora un problema: come passare i vararg da lambda nella funzione CustomGrepalla funzione CustomGrepCore.
Ho trovato questo collegamento: https://www.reddit.com/r/vim/comments/3761po/vimscript_question_passing_arguments_from_a/
Ma qui abbiamo un caso diverso. Perché abbiamo usato execute()nella funzione CustomGrep. Quindi è possibile passare vararg da execute()alla funzione CustomGrepCore? Il parametro di execute()è una stringa, ma se convertiamo a:000in string ( string(a:000)), devo cambiare la funzione CustomGrepCore, perché se passo string(a:000)a CustomGrepCore, il valore di a:0sarà sempre 1.
Quindi è possibile passare i vararg dalla execute()a un'altra funzione? Se no, beh, devo cambiare la funzione CustomGrepCore.
Risposte
Questa domanda si rivolge in gran parte ad alcune domande di follow-up che OP ha avuto in risposta a una risposta che ho dato a un'altra domanda che hanno pubblicato. Questo parla anche di una soluzione che coinvolge funzioni lambda e chiusure che è valida ma più complicata del necessario per la maggior parte dei casi d'uso. Per entrambi questi motivi, ti consiglio di controllare prima quella domanda e risposta: Come avviare una funzione asincrona in Vim 8 . Se vuoi un piccolo approfondimento sull'argomento (e alcuni argomenti periferici) puoi, ovviamente, tornare su questo in seguito.
Mi chiedo perché stai usando un FuncRef nel codice originale per effettuare la chiamata a CustomGrepCore(). Non sembra esserci una necessità esplicita. Nel nuovo codice rende le cose complicate perché devi gestire, in effetti, due livelli di riferimento indiretto (FuncRef + lambda) invece di uno solo per lambda.
Quindi il mio primo suggerimento è di usare qualcosa di simile call CustomGrepCore(param)prima ancora di toccare la roba del timer.
Indipendentemente da quanto sopra, tuttavia, diamo un'occhiata al secondo argomento di timer_start. Questa roba è complicata, senza dubbio, ma ti sei davvero allontanato da quello che ho scritto. Ci sono tre discrepanze tra quello che hai
{param -> execute("call F(param)")}
e quello che ho
{-> execute("call LongRunningFun('" . a:patt . "')", "")}
Primo, da dove viene il paramprecedente ->? Lancialo.
Secondo, sto passando due argomenti da eseguire e tu ne stai passando uno. Potresti capire cosa significa e averlo fatto intenzionalmente, ma nel caso non fosse così ... Tralasciare il secondo parametro equivale a eseguire i comandi Ex nel primo argomento con :silent. Passare una stringa vuota come secondo parametro equivale a eseguirli senza :silent . Durante la prima codifica di questo, personalmente non userei il silenzio. Dopo che le cose stanno funzionando potrei aggiungerlo.
Infine, il tuo primo parametro è una singola stringa statica mentre il mio è una concatenazione di due stringhe statiche e un'espressione ( a:patt). Mentre si ha intenzione di passare il valore (s) contenuto nella variabile locale paramper execute()quello che si sta effettivamente facendo sta passando la stringa letterale "param". Questo vale per tutto ciò che è contenuto tra virgolette.
Ad ogni modo, per evitare di trascinarlo, ti mostrerò cosa farei. Primo, non lo userei -nargs=?al tuo comando. Vorrei usare +o *al posto di a ?seconda che zero args ( :Grep) sia una chiamata valida. Questo metterà quindi ogni argomento in uno slot separato nell'elenco dei parametri. Inoltre, non usare varargs ( ...) a meno che tu non ne abbia davvero bisogno. Aggiungono un livello di riferimento indiretto che rende le cose complicate. Forse impossibile in questo caso particolare. Quindi cambia CustomGrepCore in modo che accetti un singolo argomento che sarà un elenco. Ecco una dimostrazione di come potrebbe funzionare una volta ottenuta la timer_startparte giusta.
function! CustomGrepCore(args) abort
if len(a:args)
echom "First item in args list is " . a:args[0]
endif
endfunction
command! -nargs=* Grep :call CustomGrep(<f-args>)
Grep hello " prints 'First item in args list is hello'
Con tutto ciò possiamo ottenere qualcosa che funzioni ...
" You can't use string(a:000) directly in param expression. Not yet sure why.
let arglist = string(a:000)
call timer_start(50, { -> execute("call CustomGrepCore(" . arglist . ")", "")})
Una parte non ovvia di questo è la necessità di passare l'elenco dei varags,, a:000attraverso string(). execute()prende come primo parametro una stringa (che valuta come un'espressione). Non puoi concatenare una stringa e un elenco. Riceverai un errore se ci provi. Quindi dobbiamo convertire in una rappresentazione di stringa dell'elenco e quindi concatenare.
Nota aggiuntiva: Salvo l'esplicita necessità di fare diversamente, considera fortemente l'aggiunta di "abort" alla fine delle firme delle tue funzioni in modo che le tue funzioni "falliscano velocemente" piuttosto che continuare anche quando un comando all'interno fallisce.
Aggiornamento: OP ha chiesto se è possibile mantenere i vararg in CustomGrepCore(). Questo pone alcune sfide ma ... Ma niente. Leggi l'aggiornamento alla risposta a cui si fa riferimento nel primo paragrafo di questa domanda. Il metodo è il percorso più semplice per gestire il caso d'uso del passaggio di vararg dal chiamante di timer_start alla funzione di callback invocata dal timer quando anche quella funzione accetta vararg.