É possível usar um delegado ou passar uma função como argumento no Vimscript?

11

Estou tentando criar um pequeno plugin para aprender vimscript, meu objetivo é criar algumas funções processando um texto selecionado e substituindo-o pelo resultado. O script contém os seguintes itens:

  • Duas funções processando texto: elas usam uma string como parâmetro e retornam a string que deve ser usada para substituir o texto original. Por enquanto, tenho apenas dois, mas pode haver muito mais em alguns momentos.

  • Uma função que obtém o texto selecionado: que simplesmente puxa a última seleção e a retorna.

  • Uma função de wrapper: que chama uma função de processamento, obtém seu resultado e substitui a seleção antiga por esse resultado.

Por enquanto, minha função wrapper fica assim:

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 eu tenho que criar um segundo wrapper substituindo a linha 3 por

let @x = Type2ProcessString(GetSelectedText())

Gostaria de fornecer à minha função wrapper um parâmetro que contenha a função Process para executar e usar uma chamada genérica na linha 3. Por enquanto, tentei usar callmaneiras diferentes, como por exemplo:

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

mas não tive muito sucesso e :h callnão ajudei muito no tópico de delegado.

Para resumir aqui estão minhas perguntas:

  • Como posso fazer apenas uma função de wrapper para todas as de processamento?
  • Existe algo que funciona como delegado no vimscript?
  • Se não houver delegados, qual seria uma maneira "boa" de fazer o que eu quero?
statox
fonte

Respostas:

16

Para responder à sua pergunta: o protótipo de call()no manual é call({func}, {arglist} [, {dict}]); o {arglist}argumento precisa ser literalmente um objeto de lista, não uma lista de argumentos. Ou seja, você deve escrevê-lo assim:

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

Isso pressupõe que a:functionToExecuteé um Funcref (consulte :help Funcref) ou o nome de uma função (ou seja, uma string, como 'Type1ProcessString').

Agora, esse é um recurso poderoso que oferece ao Vim uma qualidade semelhante ao LISP, mas você provavelmente nunca o usaria como acima. Se a:functionToExecutefor uma string, o nome de uma função, você poderá fazer isso:

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

e você chamaria o wrapper com o nome da função:

call Wrapper('Type1ProcessString')

Se, por outro lado, a:functionToExecuteé um Funcref, você pode chamá-lo diretamente:

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

mas você precisa chamar o wrapper assim:

call Wrapper(function('Type1ProcessString'))

Você pode verificar a existência de funções com exists('*name'). Isso possibilita o seguinte pequeno truque:

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

ou seja, uma função que usa o built-in strwidth()se o Vim é novo o suficiente para tê-lo e volta ao strlen()contrário (não estou argumentando que esse fallback faz sentido; estou apenas dizendo que pode ser feito). :)

Com as funções de dicionário (consulte :help Dictionary-function), você pode definir algo parecido com classes:

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

Então você instanciaria objetos como este:

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

E chame seus métodos:

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

Você também pode ter atributos e métodos de classe:

let g:MyClass.__meaning_of_life = 42

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

(observe que não é necessário dictaqui).

Edit: Subclassing é algo como isto:

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

O ponto sutil aqui é o uso de em copy()vez de deepcopy(). O motivo para isso é poder acessar os atributos da classe pai por referência. Isso pode ser alcançado, mas é altamente frágil e acertar está longe de ser trivial. Outro problema potencial é que este tipo de conflates subclasse is-acom has-a. Por esse motivo, os atributos de classe geralmente não valem a pena.

Ok, isso deve ser suficiente para lhe dar um pouco de reflexão.

De volta ao seu snippet de código inicial, existem dois detalhes que podem ser aprimorados:

  • você não precisa normal gvdremover a seleção antiga, normal "xpsubstitui-a mesmo que não a mate primeiro
  • use em call setreg('x', [lines], type)vez de let @x = [lines]. Isso define explicitamente o tipo do registro x. Caso contrário, você xjá conta com o tipo correto (ou seja, caractere, linha ou bloco).
lcd047
fonte
Quando você cria funções em um dicionário diretamente (ou seja, uma "função numerada"), não precisa da dictpalavra - chave. Isso se aplica aos seus "métodos de classe". Veja :h numbered-function.
Karl Yngve Lervåg
@ KarlYngveLervåg Tecnicamente, aplica-se aos métodos de classe e de objeto (ou seja, não há necessidade dictde nenhuma das MyClassfunções). Mas acho isso confuso, por isso tento adicionar dictexplicitamente.
Lcd047 11/07/2015
Entendo. Então você adiciona dictmétodos de objeto, mas não métodos de classe, para ajudar a esclarecer sua intenção?
Karl Yngve Lervåg
@ lcd047 Muito obrigado por esta resposta incrível! Vou ter que trabalhar nisso, mas é exatamente isso que eu estava procurando!
Statox
1
@ KarlYngveLervåg Há uma subtilidade aqui, o significado de selfé diferente para métodos de classe e métodos de objeto - é a própria classe no primeiro caso e a instância do objeto atual no último. Por esse motivo, sempre me refiro à própria classe como g:MyClass, nunca usando self, e vejo principalmente dictcomo um lembrete de que é aceitável usar self(ou seja, uma função que dictsempre atua em uma instância de objeto). Mais uma vez, não uso muito os métodos de classe e, quando faço isso, também tendem a omitir dicttodos os lugares. Sim, autoconsistência é o meu nome do meio. ;)
lcd047
1

Crie o comando em uma sequência e use-o :exepara executá-lo. Veja :help executepara mais detalhes.

Nesse caso, executeé usado para fazer a chamada para a função e colocar o resultado no registro, os diferentes elementos do comando devem ser concatenados com o .operador como uma sequência regular. A linha 3 deve então se tornar:

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