È 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.