Script Vim: Comment passer Varargs à un lambda dans timer_start

Aug 28 2020

Je configure mon vim avec Vim Script.

J'ai défini ma propre fonction grep personnalisée comme ci-dessous:

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

Le code ci-dessus a fonctionné comme prévu. Je peux exécuter :Grep xxxpour faire mon grep personnalisé dans mon vim.

Maintenant, je veux utiliser la fonctionnalité vim 8: timer_startpour rendre mon grep personnalisé asynchrone, comme je le comprends, il me suffit de recoder la fonction 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

Mais je suis toujours l'erreur: Invalid argument.

Comment régler ceci?

De plus, comme vous le voyez, j'ai utilisé le timer_start, je savais qu'il générerait un ID de travail, ce qui nous permet de suspendre / arrêter le travail. Dans mon cas, dois-je arrêter le travail explicitement?

Ajoutez encore une chose

Il y a un autre problème: comment passer les varargs du lambda dans la fonction CustomGrepà la fonction CustomGrepCore.

J'ai trouvé ce lien: https://www.reddit.com/r/vim/comments/3761po/vimscript_question_passing_arguments_from_a/

Mais nous avons un cas différent ici. Parce que nous avons utilisé execute()dans la fonction CustomGrep. Alors, est-il possible de passer des varargs execute()à la fonction CustomGrepCore? Le paramètre de execute()est une chaîne, mais si nous convertissons a:000en string ( string(a:000)), je dois changer la fonction CustomGrepCore, car si je passe string(a:000)à CustomGrepCore, la valeur de a:0sera toujours 1.

Est-il donc possible de passer les varargs de la execute()à une autre fonction? Sinon, eh bien, je dois changer la fonction CustomGrepCore.

Réponses

2 BLayer Aug 29 2020 at 04:16

Cette question répond en grande partie à certaines questions de suivi qu'OP avait en réponse à une réponse que j'ai donnée à une autre question qu'ils ont postée. Celui-ci parle également d'une solution impliquant des fonctions lambda et des fermetures qui est valide mais plus compliquée que nécessaire pour la plupart des cas d'utilisation. Pour ces deux raisons, je vous recommande de consulter d'abord ces questions-réponses: Comment démarrer une fonction asynchrone dans Vim 8 . Si vous voulez un peu plus d'informations sur le sujet (et quelques sujets périphériques), vous pouvez bien sûr revenir sur celui-ci par la suite.

Je me demande pourquoi vous utilisez un FuncRef dans le code d'origine pour faire l'appel CustomGrepCore(). Il ne semble pas y avoir de besoin explicite. Dans le nouveau code, cela complique les choses car vous devez gérer, en effet, deux niveaux d'indirection (FuncRef + lambda) au lieu d'un seul pour le lambda.

Donc ma première suggestion est d'utiliser quelque chose comme call CustomGrepCore(param)avant même de toucher le truc de la minuterie.

Indépendamment de ce qui précède, examinons le deuxième argument de timer_start. Ce truc est délicat, sans aucun doute, mais vous vous êtes vraiment éloigné de ce que j'ai écrit. Il y a trois discordances entre ce que vous avez

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

et ce que j'ai

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

Premièrement, d'où vient ce qui paramprécède ->? Lancez-le.

Deuxièmement, je passe deux arguments à exécuter et vous en passez un. Vous pouvez comprendre ce que cela signifie et l'avez fait intentionnellement, mais juste au cas où ce ne serait pas le cas ... Laisser le second paramètre équivaut à exécuter les commandes Ex dans le premier argument avec :silent. Passer une chaîne vide comme second paramètre équivaut à les exécuter sans :silent . Lors du premier codage, je n'utiliserais personnellement pas silencieux. Une fois que les choses auront fonctionné, je pourrais l'ajouter.

Enfin, votre premier paramètre est une seule chaîne statique tandis que le mien est une concaténation de deux chaînes statiques et d'une expression ( a:patt). Alors que vous avez l'intention de transmettre la ou les valeurs contenues dans la variable locale paramà execute()ce que vous faites réellement, c'est de passer la chaîne littérale "param". Cela s'applique à tout ce qui est contenu entre guillemets.

Quoi qu'il en soit, pour éviter de traîner ça, je vais vous montrer ce que je ferais. Premièrement, je n'utiliserais pas -nargs=?dans votre commande. J'utiliserais +ou *à la place de ?selon que zéro args ( :Grep) est un appel valide. Cela placera ensuite chaque argument dans un emplacement séparé dans la liste des paramètres. N'utilisez pas non plus varargs ( ...) sauf si vous en avez vraiment besoin. Ils ajoutent un niveau d'indirection qui rend les choses délicates. Peut-être impossible dans ce cas particulier. Modifiez donc CustomGrepCore pour qu'il accepte un seul argument qui sera une liste. Voici une démonstration de la façon dont cela pourrait fonctionner une fois que nous avons obtenu la timer_startbonne pièce.

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'

Avec tout cela, nous pouvons obtenir quelque chose qui fonctionne ...

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

Une partie non évidente de ceci est la nécessité de passer la liste des varags,, à a:000travers string(). execute()prend comme premier paramètre une chaîne (qu'il évalue comme une expression). Vous ne pouvez pas concaténer une chaîne et une liste. Vous obtiendrez une erreur si vous essayez. Nous devons donc convertir en une représentation sous forme de chaîne de la liste, puis concaténer.

Remarque supplémentaire: à moins d'un besoin explicite de faire autrement, envisagez fortement d'ajouter «abandonner» à la fin de vos signatures de fonction afin que vos fonctions «échouent rapidement» plutôt que de continuer même lorsqu'une commande échoue.

Mise à jour: OP a demandé s'il était possible de conserver les varargs CustomGrepCore(). Cela pose quelques défis mais ... Mais rien. Lisez la mise à jour de la réponse mentionnée dans le premier paragraphe de cette question. La méthode est le chemin le plus simple pour gérer le cas d'utilisation du passage de varargs de l'appelant de timer_start à la fonction de rappel appelée par le timer lorsque cette fonction prend également des varargs.