Apakah mungkin menggunakan delegasi atau meneruskan fungsi sebagai argumen di Vimscript?

11

Saya mencoba membuat plugin kecil untuk mempelajari vimscript, tujuan saya adalah membuat beberapa fungsi memproses teks yang dipilih dan menggantinya dengan hasilnya. Script berisi item berikut:

  • Dua fungsi memproses teks: mereka mengambil string sebagai parameter mengembalikan string yang harus digunakan untuk mengganti teks asli. Untuk saat ini saya hanya memiliki dua tetapi mungkin ada lebih banyak dalam beberapa waktu.

  • Sebuah fungsi mendapatkan teks yang dipilih: yang hanya mencabut pilihan terakhir dan mengembalikannya.

  • Fungsi wrapper: yang memanggil fungsi pemrosesan, dapatkan hasilnya, dan ganti pilihan yang lama dengan hasil ini.

Untuk saat ini fungsi pembungkus saya terlihat seperti ini:

function! Wrapper()
    " Get the string to insert
    let @x = Type1ProcessString(GetSelectedText())

    " remove the old selection
    normal gvd

    " insert the new string
    normal "xp
endfunction

Dan saya harus membuat pembungkus kedua menggantikan baris 3 dengan

let @x = Type2ProcessString(GetSelectedText())

Saya ingin memberikan kepada fungsi pembungkus saya parameter yang berisi fungsi Proses untuk mengeksekusi dan menggunakan panggilan generik pada baris 3. Untuk saat ini saya telah mencoba menggunakan callberbagai cara seperti, misalnya, ini:

let @x = call('a:functionToExecute', GetSelectedText()) 

tetapi saya belum benar-benar berhasil dan :h callbelum benar-benar membantu tentang topik delegasi.

Singkatnya, inilah pertanyaan saya:

  • Bagaimana saya bisa membuat hanya satu fungsi pembungkus untuk semua yang diproses?
  • Apakah ada sesuatu yang berfungsi sebagai delegasi dalam vimscript?
  • Jika delegasi tidak ada, apa yang akan menjadi cara "baik" untuk melakukan apa yang saya inginkan?
statox
sumber

Jawaban:

16

Untuk menjawab pertanyaan Anda: prototipe call()dalam manual adalah call({func}, {arglist} [, {dict}]); yang {arglist}argumen perlu harfiah objek Daftar, tidak daftar argumen. Artinya, Anda harus menulis seperti ini:

let @x = call(a:functionToExecute, [GetSelectedText()])

Asumsi ini a:functionToExecuteadalah Funcref (lihat :help Funcref), atau nama fungsi (yaitu string, seperti 'Type1ProcessString').

Nah, itu fitur hebat yang memberi Vim semacam kualitas seperti LISP, tetapi Anda mungkin jarang menggunakannya seperti di atas. Jika a:functionToExecuteadalah string, nama fungsi, maka Anda dapat melakukan ini:

function! Wrapper(functionToExecute)
    " ...
    let s:processing = function(a:functionToExecute)
    let @x = s:processing(GetSelectedText())
    " ...
endfunction

dan Anda akan memanggil pembungkus dengan nama fungsi:

call Wrapper('Type1ProcessString')

Jika di sisi lain a:functionToExecuteadalah Funcref, Anda dapat memanggilnya langsung:

function! Wrapper(functionToExecute)
    " ...
    let @x = a:functionToExecute(GetSelectedText())
    " ...
endfunction

tetapi Anda perlu memanggil pembungkus seperti ini:

call Wrapper(function('Type1ProcessString'))

Anda dapat memeriksa keberadaan fungsi dengan exists('*name'). Ini memungkinkan trik kecil berikut:

let s:width = function(exists('*strwidth') ? 'strwidth' : 'strlen')

yaitu fungsi yang menggunakan built-in strwidth()jika Vim cukup baru untuk memilikinya, dan jatuh kembali ke strlen()sebaliknya (saya tidak berpendapat bahwa mundur seperti itu masuk akal; saya hanya mengatakan itu bisa dilakukan). :)

Dengan fungsi kamus (lihat :help Dictionary-function) Anda dapat menentukan sesuatu yang menyerupai kelas:

let g:MyClass = {}

function! g:MyClass.New(...)
    let newObj = copy(self)

    if a:0 && type(a:1) == type({})
        let newObj._attributes = deepcopy(a:1)
    endif
    if exists('*MyClassProcess')
        let newObj._process = function('MyClassProcess')
    else
        let newObj._process = function('s:_process_default')
    endif

    return newObj
endfunction

function! g:MyClass.getFoo() dict
    return get(get(self, '_attributes', {}), 'foo')
endfunction

function! g:MyClass.setFoo(val) dict
    if !has_key(self, '_attributes')
        let self._attributes = {}
    endif
    let self._attributes['foo'] = a:val
endfunction

function! g:MyClass.process() dict
    call self._process()
endfunction

function! s:_process_default()
    echomsg 'nothing to see here, define MyClassProcess() to make me interesting'
endfunction

Maka Anda akan instantiate objek seperti ini:

let little_object = g:MyClass.New({'foo': 'bar'})

Dan sebut metode-metodenya:

call little_object.setFoo('baz')
echomsg little_object.getFoo()
call little_object.process()

Anda juga dapat memiliki atribut dan metode kelas:

let g:MyClass.__meaning_of_life = 42

function g:MyClass.GetMeaningOfLife()
    return get(g:MyClass, '__meaning_of_life')
endfunction

(perhatikan tidak perlu untuk di dictsini).

Sunting: Subclassing adalah sesuatu seperti ini:

let g:MySubclass = copy(g:MyClass)
call extend(g:MySubclass, subclass_attributes)

Titik halus di sini adalah penggunaan copy()bukan deepcopy(). Alasannya adalah untuk dapat mengakses atribut dari kelas induk dengan referensi. Ini bisa dicapai, tetapi sangat rapuh dan membuatnya benar jauh dari sepele. Masalah lain yang potensial adalah bahwa jenis conflates subclass is-adengan has-a. Untuk alasan ini atribut kelas biasanya tidak terlalu sepadan dengan rasa sakitnya.

Ok, ini sudah cukup untuk membuatmu berpikir.

Kembali ke cuplikan kode awal Anda, ada dua detail yang dapat diperbaiki:

  • Anda tidak perlu normal gvdmenghapus pilihan lama, normal "xpakan menggantinya bahkan jika Anda tidak membunuhnya terlebih dahulu
  • gunakan call setreg('x', [lines], type)sebagai ganti let @x = [lines]. Ini secara eksplisit menetapkan jenis register x. Kalau tidak, Anda mengandalkan xsudah memiliki jenis yang benar (yaitu characterwise, linewise, atau blockwise).
lcd047
sumber
Ketika Anda membuat fungsi dalam kamus secara langsung (yaitu "fungsi bernomor"), Anda tidak memerlukan dictkata kunci. Ini berlaku untuk "metode kelas" Anda. Lihat :h numbered-function.
Karl Yngve Lervåg
@ KarlYngveLervåg Secara teknis ini berlaku untuk metode kelas dan objek (yaitu tidak perlu untuk fungsi dictapa pun MyClass). Tapi saya merasa itu membingungkan, jadi saya cenderung menambahkan dictsecara eksplisit.
lcd047
Saya melihat. Jadi Anda menambahkan dictuntuk metode objek, tetapi tidak untuk metode kelas, untuk membantu memperjelas maksud Anda?
Karl Yngve Lervåg
@ lcd047 Terima kasih banyak atas jawaban yang luar biasa ini! Saya harus mengerjakannya tetapi itulah yang saya cari!
statox
1
@ KarlYngveLervåg Ada subtilitas di sini, artinya selfberbeda untuk metode kelas dan untuk metode objek - itu adalah kelas itu sendiri dalam kasus sebelumnya, dan contoh dari objek saat ini di yang terakhir. Untuk alasan ini saya selalu merujuk ke kelas itu sendiri g:MyClass, tidak pernah menggunakan self, dan saya kebanyakan melihat dictsebagai pengingat bahwa itu ok untuk digunakan self(yaitu, fungsi yang dictselalu bertindak pada instance objek). Lagi-lagi, saya tidak menggunakan metode kelas banyak, dan ketika saya melakukannya saya juga cenderung menghilangkan di dictmana-mana. Ya, konsistensi diri adalah nama tengah saya. ;)
lcd047
1

Bangun perintah dalam sebuah string dan gunakan :exeuntuk menjalankannya. Lihat :help executeuntuk lebih jelasnya.

Dalam hal ini, executedigunakan untuk membuat panggilan ke fungsi dan memasukkan hasilnya dalam register, elemen-elemen yang berbeda dari perintah harus digabungkan dengan .operator sebagai string biasa. Baris 3 seharusnya menjadi:

execute "let @x = " . a:functionToExecute . "(GetSelectedText())"
cxw
sumber