Скрипт Vim: как передать Varargs в лямбду в timer_start

Aug 28 2020

Я настраиваю свой vim с помощью Vim Script.

Я установил свою собственную функцию grep, как показано ниже:

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

Приведенный выше код работал, как ожидалось. Я могу выполнить :Grep xxxсвой собственный grep в моем vim.

Теперь я хочу использовать функцию vim 8: timer_startчтобы сделать мой собственный grep async, насколько я понимаю, мне нужно только перекодировать функцию 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

Но я всегда получал ошибку: Invalid argument.

Как это исправить?

Кроме того, как вы видите, я использовал timer_start, я знал, что он сгенерирует идентификатор задания, который позволяет нам приостановить / остановить задание. В моем случае нужно ли останавливать работу явно?

Добавить еще одну вещь

Есть еще одна проблема: как передать varargs из лямбды в функции CustomGrepв функцию CustomGrepCore.

Я нашел эту ссылку: https://www.reddit.com/r/vim/comments/3761po/vimscript_question_passing_arguments_from_a/

Но у нас здесь другой случай. Поскольку мы использовали execute()в функции CustomGrep. Так можно ли передать varargs из execute()функции CustomGrepCore? Параметр execute()- это строка, но если мы преобразуем ее a:000в string ( string(a:000)), мне придется изменить функцию CustomGrepCore, потому что, если я перейду string(a:000)к CustomGrepCore, значение a:0всегда будет 1.

Так можно ли передать varargs из execute()другой функции? Если нет, то я должен изменить функцию CustomGrepCore.

Ответы

2 BLayer Aug 29 2020 at 04:16

Этот вопрос в основном касается некоторых последующих вопросов, которые OP имел в ответ на ответ, который я дал на другой вопрос, который они разместили. Здесь также говорится о решении, включающем лямбда-функции и замыкания, которое является допустимым, но более сложным, чем необходимо для большинства случаев использования. По обеим этим причинам я рекомендую вам сначала ознакомиться с этими вопросами и ответами: Как запустить асинхронную функцию в Vim 8 . Если вам нужно немного больше узнать о теме (и некоторых второстепенных темах), вы, конечно, можете вернуться к этой теме позже.

Мне интересно, почему вы используете FuncRef в исходном коде для вызова CustomGrepCore(). Похоже, в этом нет явной необходимости. В новом коде это усложняет задачу, потому что вам, по сути, приходится иметь дело с двумя уровнями косвенного обращения (FuncRef + lambda) вместо одного для лямбда.

Итак, мое первое предложение - использовать что-то подобное, call CustomGrepCore(param)прежде чем даже касаться таймера.

Однако, несмотря на вышесказанное, давайте посмотрим на второй аргумент timer_start. Без сомнения, это непросто, но вы действительно отклонились от того, что я написал. Есть три несоответствия между тем, что у вас есть

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

и что у меня есть

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

Во-первых, откуда взялось paramпредыдущее ->? Брось это.

Во-вторых, я передаю два аргумента для выполнения, а вы передаете один. Вы можете понять, что это означает, и сделали это намеренно, но на всякий случай это не так ... Отключение второго параметра эквивалентно запуску команд Ex в первом аргументе с :silent. Передача пустой строки в качестве второго параметра эквивалентна их запуску без :silent . При первом кодировании я лично не стал бы использовать молчание. После того, как все заработает, я могу добавить его.

Наконец, ваш первый параметр - это одна статическая строка, а мой - это объединение двух статических строк и выражения ( a:patt). Пока вы намереваетесь передать значение (я), содержащееся в локальной переменной, paramв execute()то, что вы на самом деле делаете, - это передача буквальной строки «param». Это касается всего, что содержится в кавычках.

В любом случае, чтобы не затягивать с этим, я покажу вам, что я буду делать. Во-первых, я бы не стал использовать -nargs=?в вашей команде. Я бы использовал +или *вместо, в ?зависимости от того, является ли нулевой args ( :Grep) допустимым вызовом. Затем каждый аргумент будет помещен в отдельный слот в списке параметров. Кроме того, не используйте varargs ( ...), если они вам действительно не нужны. Они добавляют уровень косвенности, что усложняет задачу. Возможно, в данном случае это невозможно. Поэтому измените CustomGrepCore, чтобы он принимал единственный аргумент, который будет списком. Вот демонстрация того, как это может работать, если мы сделаем все timer_startправильно.

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'

Со всем этим мы можем получить что-то, что работает ...

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

Неочевидной частью этого является необходимость передать список переменных,, a:000через string(). execute()принимает в качестве первого параметра строку (которая оценивается как выражение). Вы не можете объединить строку и список. Вы получите сообщение об ошибке, если попытаетесь. Поэтому нам нужно преобразовать список в строковое представление, а затем объединить.

Дополнительное примечание: исключая явную необходимость поступить иначе, настоятельно рекомендуется добавить слово «прерывание» в конце сигнатуры функций, чтобы ваши функции «быстро выходили из строя», а не продолжали работать, даже если внутренняя команда не работает.

Обновление: OP спросил, можно ли сохранить varargs CustomGrepCore(). Это создает некоторые проблемы, но ... Но ничего. Прочтите обновление ответа, указанного в первом абзаце этого вопроса. Этот метод представляет собой самый простой способ обработки варианта использования передачи varargs от вызывающего timer_start функции обратного вызова, вызываемой таймером, когда эта функция также принимает varargs.