È possibile utilizzare un delegato o passare una funzione come argomento in Vimscript?


11

Sto cercando di creare un piccolo plugin per imparare vimscript, il mio obiettivo è creare alcune funzioni elaborando un testo selezionato e sostituendolo con il risultato. Lo script contiene i seguenti elementi:

  • Due funzioni che elaborano il testo: prendono una stringa come parametro e restituiscono la stringa che dovrebbe essere usata per sostituire il testo originale. Per ora ne ho solo due, ma potrebbero essercene molto di più tra qualche tempo.

  • Una funzione che ottiene il testo selezionato: che semplicemente strappa l'ultima selezione e la restituisce.

  • Una funzione wrapper: che chiama una funzione di elaborazione, ottiene il suo risultato e sostituisce la vecchia selezione con questo risultato.

Per ora la mia funzione wrapper si presenta così:

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

    " remove the old selection
    normal gvd

    " insert the new string
    normal "xp
endfunction

E devo creare un secondo wrapper sostituendo la riga 3 con

let @x = Type2ProcessString(GetSelectedText())

Vorrei dare alla mia funzione wrapper un parametro contenente la funzione Process per eseguire e utilizzare una chiamata generica nella riga 3. Per ora ho provato a utilizzare calldiversi modi come, ad esempio, questo:

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

ma non ho avuto molto successo e :h callnon mi è stato di grande aiuto sull'argomento dei delegati.

Per riassumere, ecco le mie domande:

  • Come posso fare una sola funzione wrapper per tutte quelle di elaborazione?
  • C'è qualcosa che funziona come delegato in vimscript?
  • Se i delegati non esistono quale sarebbe un "buon" modo di fare quello che voglio?

Risposte:


16

Per rispondere alla tua domanda: il prototipo di call()nel manuale è call({func}, {arglist} [, {dict}]); l' {arglist}argomento deve essere letteralmente un oggetto Elenco, non un elenco di argomenti. Cioè, devi scriverlo in questo modo:

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

Ciò presuppone che a:functionToExecutesia Funcref (vedi :help Funcref) o il nome di una funzione (cioè una stringa, come 'Type1ProcessString').

Ora, questa è una potente funzionalità che dà a Vim una sorta di qualità simile a LISP, ma probabilmente la useresti raramente come sopra. Se a:functionToExecuteè una stringa, il nome di una funzione, puoi farlo:

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

e chiamereste il wrapper con il nome della funzione:

call Wrapper('Type1ProcessString')

Se d'altra parte a:functionToExecuteè un Funcref, puoi chiamarlo direttamente:

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

ma devi chiamare il wrapper in questo modo:

call Wrapper(function('Type1ProcessString'))

È possibile verificare l'esistenza di funzioni con exists('*name'). Questo rende possibile il seguente piccolo trucco:

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

vale a dire una funzione che utilizza il built-in strwidth()se Vim è abbastanza nuovo per averlo, e ricade in strlen()altro modo (non sto sostenendo che un tale fallback abbia senso; sto solo dicendo che può essere fatto). :)

Con le funzioni del dizionario (vedi :help Dictionary-function) puoi definire qualcosa di simile alle classi:

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

Quindi istanziate oggetti come questo:

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

E chiama i suoi metodi:

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

Puoi anche avere attributi e metodi di classe:

let g:MyClass.__meaning_of_life = 42

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

(notare che non è necessario dictqui).

Modifica: la sottoclasse è qualcosa del genere:

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

Il punto sottile qui è l'uso copy()invece di deepcopy(). Il motivo di ciò è di poter accedere agli attributi della classe genitore per riferimento. Questo può essere ottenuto, ma è molto fragile e farlo nel modo giusto è tutt'altro che banale. Un altro potenziale problema è che questo tipo di sottoclasse si confonde is-acon has-a. Per questo motivo gli attributi di classe di solito non valgono davvero la pena.

Ok, questo dovrebbe bastare a darti un po 'di spunti di riflessione.

Torna allo snippet di codice iniziale, ci sono due dettagli che potrebbero essere migliorati:

  • non è necessario normal gvdrimuovere la vecchia selezione, normal "xpla sostituirà anche se non la uccidi per prima
  • utilizzare call setreg('x', [lines], type)invece di let @x = [lines]. Questo imposta esplicitamente il tipo di registro x. Altrimenti stai facendo affidamento sul fatto di xavere già il tipo corretto (ovvero, a livello di carattere, di linea o di blocco).

Quando si creano direttamente funzioni in un dizionario (ovvero una "funzione numerata"), non è necessaria la dictparola chiave. Questo vale per i tuoi "metodi di classe". Vedere :h numbered-function.
— Karl Yngve Lervåg, l'

@ KarlYngveLervåg Tecnicamente si applica a entrambi i metodi di classe e oggetto (cioè non è necessario dictper nessuna delle MyClassfunzioni). Ma lo trovo confuso, quindi tendo ad aggiungere dictesplicitamente.
— lcd047

Vedo. Quindi aggiungi dictmetodi per oggetti, ma non per metodi di classe, al fine di aiutarti a chiarire il tuo intento?
— Karl Yngve Lervåg,

@ lcd047 Grazie mille per questa fantastica risposta! Ci dovrò lavorare ma è esattamente quello che stavo cercando!
— statox

1
@ KarlYngveLervåg Qui c'è una sottigliezza, il significato di selfè diverso per i metodi di classe e per i metodi oggetto: è la classe stessa nel primo caso e l'istanza dell'oggetto corrente in quest'ultimo. Per questo motivo mi riferisco sempre alla classe stessa come g:MyClass, non usando mai self, e per lo più vedo dictcome promemoria che va bene usare self(cioè una funzione che dictagisce sempre su un'istanza di oggetto). Di nuovo, non uso molto i metodi di classe e quando lo faccio tendo a omettere dictovunque. Sì, l'autoconsistenza è il mio secondo nome. ;)
— lcd047

1

Compilare il comando in una stringa e utilizzarlo :exeper eseguirlo. Vedi :help executeper maggiori dettagli.

In questo caso, executeviene utilizzato per effettuare la chiamata alla funzione e inserire il risultato nel registro, i diversi elementi del comando devono essere concatenati con l' .operatore come una stringa normale. La riga 3 dovrebbe quindi diventare:

execute "let @x = " . a:functionToExecute . "(GetSelectedText())"
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.