Vim-Skript: So übergeben Sie Varargs in timer_start an ein Lambda

Aug 28 2020

Ich konfiguriere mein vim mit Vim Script.

Ich habe meine eigene benutzerdefinierte Grep-Funktion wie folgt eingestellt:

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>)

Der obige Code funktionierte wie erwartet. Ich kann ausführen :Grep xxx, um mein benutzerdefiniertes Grep in meinem Vim auszuführen .

Jetzt möchte ich die Funktion vim 8 verwenden: timer_startUm mein benutzerdefiniertes grep asynchron zu machen, muss ich nach meinem Verständnis nur die Funktion neu codieren 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

Aber ich habe immer den Fehler bekommen : Invalid argument.

Wie kann ich das beheben?

Außerdem timer_startwusste ich , wie Sie sehen , dass ich eine Job-ID generieren würde, mit der wir den Job anhalten / stoppen können. Muss ich in meinem Fall den Job explizit beenden?

Fügen Sie noch eine Sache hinzu

Es gibt eine weitere Frage: wie den varargs des Lambda in der Funktion zu übergeben CustomGrepan die Funktion CustomGrepCore.

Ich habe diesen Link gefunden: https://www.reddit.com/r/vim/comments/3761po/vimscript_question_passing_arguments_from_a/

Aber wir haben hier einen anderen Fall. Weil wir execute()in der Funktion verwendet haben CustomGrep. Ist es also möglich, Varargs von execute()an die Funktion zu übergeben CustomGrepCore? Der Parameter von execute()ist ein String, aber wenn wir a:000in string ( string(a:000)) konvertieren , muss ich die Funktion ändern CustomGrepCore, denn wenn ich an übergebe string(a:000), CustomGrepCoreist der Wert von a:0immer 1.

Ist es also möglich, die Varargs von der execute()an eine andere Funktion zu übergeben? Wenn nicht, muss ich die Funktion ändern CustomGrepCore.

Antworten

2 BLayer Aug 29 2020 at 04:16

Diese Frage befasst sich hauptsächlich mit einigen Anschlussfragen, die OP als Antwort auf eine Antwort hatte, die ich auf eine andere Frage gegeben habe, die sie gestellt haben. In diesem Fall geht es auch um eine Lösung mit Lambda-Funktionen und -Verschlüssen, die gültig, aber komplizierter ist als für die meisten Anwendungsfälle erforderlich. Aus diesen beiden Gründen empfehle ich Ihnen, zuerst die Fragen und Antworten zu lesen : So starten Sie eine Async-Funktion in Vim 8 . Wenn Sie einen zusätzlichen Einblick in das Thema (und einige periphere Themen) wünschen, können Sie natürlich später darauf zurückkommen.

Ich frage mich, warum Sie im Originalcode ein FuncRef verwenden, um den Anruf zu tätigen CustomGrepCore(). Es scheint keine explizite Notwendigkeit dafür zu geben. Im neuen Code werden die Dinge kompliziert, da Sie praktisch zwei Indirektionsebenen (FuncRef + Lambda) anstatt nur eine für das Lambda behandeln müssen.

Mein erster Vorschlag ist also, etwas zu verwenden, call CustomGrepCore(param)bevor ich überhaupt das Timer-Zeug berühre.

Unabhängig davon sehen wir uns jedoch das zweite Argument für timer_start an. Dieses Zeug ist zweifellos knifflig, aber Sie sind wirklich von dem abgewichen, was ich geschrieben habe. Es gibt drei Fehlpaarungen zwischen dem, was Sie haben

{param -> execute("call F(param)")}

und was ich habe

{-> execute("call LongRunningFun('" . a:patt . "')", "")}

Erstens, woher kam das paramVorhergehende ->? Wirf es.

Zweitens übergebe ich zwei Argumente, die ausgeführt werden sollen, und Sie übergeben eines. Sie können verstehen, was dies bedeutet, und haben dies absichtlich getan, aber nur für den Fall, dass dies nicht der Fall ist ... Das Weglassen des zweiten Parameters entspricht dem Ausführen der Ex-Befehle im ersten Argument mit :silent. Das Übergeben einer leeren Zeichenfolge als zweiter Parameter entspricht dem Ausführen ohne :silent . Beim ersten Codieren würde ich persönlich nicht Silent verwenden. Nachdem die Dinge funktionieren, könnte ich es hinzufügen.

Schließlich ist Ihr erster Parameter eine einzelne statische Zeichenfolge, während meine eine Verkettung von zwei statischen Zeichenfolgen und einem Ausdruck ( a:patt) ist. Während Sie beabsichtigen, die in der lokalen Variablen enthaltenen Werte an das paramzu übergeben, execute()was Sie tatsächlich tun, übergeben Sie die Literalzeichenfolge "param". Dies gilt für alles, was in Anführungszeichen enthalten ist.

Um dies nicht herauszuziehen, zeige ich Ihnen, was ich tun würde. Erstens würde ich nicht -nargs=?in Ihrem Befehl verwenden. Ich würde verwenden +oder *anstelle von ?abhängig davon, ob null args ( :Grep) ein gültiger Aufruf ist. Dadurch wird jedes Argument in einen separaten Slot in der Parameterliste eingefügt. Verwenden Sie varargs ( ...) nur, wenn Sie sie wirklich benötigen. Sie fügen eine Indirektionsebene hinzu, die die Dinge schwierig macht. Vielleicht unmöglich in diesem speziellen Fall. Ändern Sie CustomGrepCore so, dass ein einzelnes Argument akzeptiert wird, das eine Liste ist. Hier ist eine Demonstration, wie es funktionieren könnte, wenn wir das timer_startrichtige Teil gefunden haben.

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'

Mit all dem können wir etwas bekommen, das funktioniert ...

" 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 .  ")", "")})

Ein nicht-offensichtlicher Teil davon ist die Notwendigkeit , die varags Liste übergeben, a:000durch string(). execute()Nimmt als ersten Parameter einen String (den er als Ausdruck auswertet). Sie können eine Zeichenfolge und eine Liste nicht verketten. Sie erhalten eine Fehlermeldung, wenn Sie es versuchen. Wir müssen also in eine Zeichenfolgendarstellung der Liste konvertieren und dann verketten.

Zusätzlicher Hinweis: Wenn Sie nicht ausdrücklich etwas anderes tun müssen, sollten Sie am Ende Ihrer Funktionssignaturen unbedingt "Abbruch" hinzufügen, damit Ihre Funktionen "schnell fehlschlagen", sondern auch dann fortgesetzt werden, wenn ein Befehl innerhalb fehlschlägt.

Update: OP hat gefragt, ob es möglich ist, varargs beizubehalten CustomGrepCore(). Dies wirft einige Herausforderungen auf, aber ... aber nichts. Lesen Sie das Update zu der Antwort, auf die im ersten Absatz dieser Frage verwiesen wird. Die dortige Methode ist der einfachste Weg, um den Anwendungsfall der Übergabe von varargs vom Aufrufer von timer_start an die vom Timer aufgerufene Rückruffunktion zu behandeln, wenn diese Funktion auch varargs akzeptiert.