Vim Script: Cara melewatkan Varargs ke lambda di timer_start

Aug 28 2020

Saya mengonfigurasi vim saya dengan Vim Script.

Saya telah mengatur fungsi grep kustom saya sendiri seperti di bawah ini:

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

Kode di atas berfungsi seperti yang diharapkan. Saya dapat mengeksekusi :Grep xxxuntuk melakukan grep kustom saya di vim saya.

Sekarang saya ingin menggunakan fitur vim 8: timer_startuntuk membuat grep kustom saya async, menurut pemahaman saya, saya hanya perlu mengkodekan ulang fungsinya 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

Tapi aku selalu punya kesalahan: Invalid argument.

Bagaimana cara mengatasinya?

Selain itu, seperti yang Anda lihat, saya menggunakan timer_start, saya tahu itu akan menghasilkan ID pekerjaan, yang memungkinkan kita untuk menjeda / menghentikan pekerjaan. Dalam kasus saya, perlukah saya menghentikan pekerjaan secara eksplisit?

Tambahkan satu hal lagi

Ada satu masalah lagi: cara meneruskan vararg dari lambda dalam fungsi CustomGrepke fungsi CustomGrepCore.

Saya telah menemukan tautan ini: https://www.reddit.com/r/vim/comments/3761po/vimscript_question_passing_arguments_from_a/

Tapi kami punya kasus berbeda di sini. Karena kami digunakan execute()dalam fungsi tersebut CustomGrep. Jadi apakah mungkin untuk melewatkan vararg dari execute()ke fungsi CustomGrepCore? Parameter dari execute()adalah string, tetapi jika kita convert a:000ke string ( string(a:000)), saya harus mengubah fungsinya CustomGrepCore, karena jika saya meneruskan string(a:000)ke CustomGrepCore, nilai dari a:0akan selalu menjadi 1.

Jadi apakah mungkin untuk meneruskan vararg dari fungsi execute()ke fungsi lain? Jika tidak, ya, saya harus mengubah fungsinya CustomGrepCore.

Jawaban

2 BLayer Aug 29 2020 at 04:16

Pertanyaan ini sebagian besar membahas beberapa pertanyaan lanjutan yang dimiliki OP sebagai tanggapan atas jawaban yang saya berikan untuk pertanyaan lain yang mereka posting. Yang ini juga berbicara tentang solusi yang melibatkan fungsi lambda dan closure yang valid tetapi lebih rumit daripada yang dibutuhkan untuk kebanyakan kasus penggunaan. Untuk kedua alasan tersebut, saya sarankan Anda memeriksa T&J itu terlebih dahulu: Bagaimana cara memulai fungsi async di Vim 8 . Jika Anda ingin sedikit wawasan tambahan tentang topik (dan beberapa topik periferal) Anda dapat, tentu saja, kembali ke topik ini setelahnya.

Saya bertanya-tanya mengapa Anda menggunakan FuncRef dalam kode asli untuk melakukan panggilan ke CustomGrepCore(). Sepertinya tidak ada kebutuhan eksplisit untuk itu. Dalam kode baru itu membuat hal-hal menjadi rumit karena Anda harus berurusan dengan, pada dasarnya, dua tingkat tipuan (FuncRef + lambda), bukan hanya satu untuk lambda.

Jadi saran pertama saya adalah menggunakan sesuatu seperti call CustomGrepCore(param)bahkan sebelum menyentuh barang pengatur waktu.

Terlepas dari hal di atas, mari kita lihat argumen kedua untuk timer_start. Hal ini memang rumit, tidak diragukan lagi, tetapi Anda benar-benar menyimpang dari apa yang saya tulis. Ada tiga ketidakcocokan antara apa yang Anda miliki

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

dan apa yang saya miliki

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

Pertama, mana yang paramsebelumnya ->berasal? Lempar.

Kedua, saya memberikan dua argumen untuk dieksekusi dan Anda melewati satu argumen. Anda mungkin mengerti apa artinya ini dan telah melakukannya dengan sengaja tetapi kalau-kalau bukan itu masalahnya ... Meninggalkan parameter kedua sama dengan menjalankan perintah Ex di argumen pertama dengan :silent. Meneruskan string kosong sebagai param kedua sama dengan menjalankannya tanpa :silent . Saat pertama kali mengkodekan ini, saya pribadi tidak akan menggunakan silent. Setelah semuanya berfungsi, saya mungkin menambahkannya.

Terakhir, parameter pertama Anda adalah string statis tunggal, sedangkan param saya adalah rangkaian dua string statis dan sebuah ekspresi ( a:patt). Meskipun Anda bermaksud untuk meneruskan nilai yang terdapat dalam variabel lokal paramke execute()apa yang sebenarnya Anda lakukan adalah meneruskan string literal "param". Ini berlaku untuk apa pun yang terkandung dalam kutipan.

Bagaimanapun, untuk menghindari menyeret ini keluar, saya akan menunjukkan kepada Anda apa yang akan saya lakukan. Pertama, saya tidak akan menggunakan -nargs=?perintah Anda. Saya akan menggunakan +atau *menggantikan ?bergantung pada apakah nol args ( :Grep) adalah panggilan yang valid. Ini kemudian akan menempatkan setiap argumen di slot terpisah di daftar params. Selain itu, jangan gunakan varargs ( ...) kecuali Anda benar-benar membutuhkannya. Mereka menambahkan tingkat tipuan yang membuat segalanya menjadi rumit. Mungkin tidak mungkin dalam kasus khusus ini. Jadi ubah CustomGrepCore sehingga menerima satu argumen yang akan menjadi daftar. Berikut adalah demonstrasi bagaimana itu bisa berhasil setelah kita mendapatkan timer_startbagian yang benar.

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'

Dengan semua itu kita bisa mendapatkan sesuatu yang berhasil ...

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

Bagian yang tidak jelas dari ini adalah kebutuhan untuk melewati daftar varags a:000,, melalui string(). execute()mengambil sebagai param pertama string (yang dievaluasi sebagai ekspresi). Anda tidak dapat menggabungkan string dan daftar. Anda akan mendapatkan pesan kesalahan jika Anda mencoba. Jadi kita perlu mengubah representasi string dari list dan kemudian menggabungkannya.

Catatan tambahan: Membatasi kebutuhan eksplisit untuk melakukan sebaliknya, sangat pertimbangkan untuk menambahkan "abort" di akhir tanda tangan fungsi Anda sehingga fungsi Anda "gagal dengan cepat" alih-alih melanjutkan bahkan ketika perintah di dalamnya gagal.

Pembaruan: OP bertanya apakah mungkin untuk menyimpan vararg CustomGrepCore(). Ini menimbulkan beberapa tantangan tapi ... Tapi tidak ada. Bacalah pembaruan untuk jawaban yang dirujuk di paragraf pertama pertanyaan ini. Metode di sana adalah jalur paling sederhana untuk menangani kasus penggunaan dengan meneruskan vararg dari pemanggil timer_start ke fungsi callback yang dipanggil oleh timer ketika fungsi itu juga mengambil vararg.