¿Es posible usar un delegado o pasar una función como argumento en Vimscript?

Pregunta:

Estoy tratando de crear un pequeño complemento para aprender vimscript, mi objetivo es crear algunas funciones que procesan un texto seleccionado y lo reemplazan con el resultado. El script contiene los siguientes elementos:

  • Dos funciones que procesan texto: toman una cadena como parámetro y devuelven la cadena que debe usarse para reemplazar el texto original. Por ahora solo tengo dos, pero podría haber muchos más en poco tiempo.

  • Una función que obtiene el texto seleccionado: que simplemente tira de la última selección y la devuelve.

  • Una función contenedora: que llama a una función de procesamiento, obtiene su resultado y reemplaza la selección anterior con este resultado.

Por ahora, mi función contenedora se ve así:

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

    " remove the old selection
    normal gvd

    " insert the new string
    normal "xp
endfunction

Y tengo que crear un segundo contenedor reemplazando la línea 3 con

let @x = Type2ProcessString(GetSelectedText())

Me gustaría darle a mi función contenedora un parámetro que contenga la función Process para ejecutar y usar una llamada genérica en la línea 3. Por ahora he intentado usar call diferentes formas como, por ejemplo, esta:

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

pero no he tenido mucho éxito y :h call no ha sido realmente útil en el tema de los delegados.

Para resumir, aquí están mis preguntas:

  • ¿Cómo puedo hacer que solo funcione una envoltura para todos los procesadores?
  • ¿Hay algo que funcione como delegado en vimscript?
  • Si los delegados no existen, ¿cuál sería una "buena" manera de hacer lo que quiero?

Respuesta:

Para responder a su pregunta: el prototipo de call() en el manual es call({func}, {arglist} [, {dict}]) ; el argumento {arglist} debe ser literalmente un objeto List, no una lista de argumentos. Es decir, tienes que escribirlo así:

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

Esto supone que a:functionToExecute es un Funcref (ver :help Funcref ), o el nombre de una función (es decir, una cadena, como 'Type1ProcessString' ).

Ahora, esa es una característica poderosa que le da a Vim una especie de calidad similar a LISP, pero probablemente rara vez la usaría como se indicó anteriormente. Si a:functionToExecute es una cadena, el nombre de una función, entonces puede hacer esto:

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

y llamarías al contenedor con el nombre de la función:

call Wrapper('Type1ProcessString')

Si por otro lado a:functionToExecute es un Funcref, puedes llamarlo directamente:

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

pero necesitas llamar al contenedor de esta manera:

call Wrapper(function('Type1ProcessString'))

Puede comprobar la existencia de funciones con exists('*name') . Esto hace posible el siguiente pequeño truco:

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

es decir, una función que usa el strwidth() si Vim es lo suficientemente nuevo para tenerlo, y vuelve a strlen() contrario (no estoy argumentando que tal alternativa tenga sentido; solo digo que puede ser hecho). 🙂

Con las funciones de diccionario (ver :help Dictionary-function ) puede definir algo parecido a clases:

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

Entonces crearías una instancia de objetos como este:

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

Y llama a sus métodos:

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

También puede tener atributos y métodos de clase:

let g:MyClass.__meaning_of_life = 42

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

(observe que no hay necesidad de dict aquí).

La subclasificación es algo como esto:

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

El punto sutil aquí es el uso de copy() lugar de deepcopy() . La razón de esto es poder acceder a los atributos de la clase padre por referencia. Esto se puede lograr, pero es muy frágil y hacerlo bien está lejos de ser trivial. Otro problema potencial es que este tipo de subclase combina is-a con has-a . Por esta razón, los atributos de clase no suelen valer la pena.

Ok, esto debería ser suficiente para darle algo en que pensar.

Volviendo a su fragmento de código inicial, hay dos detalles que podrían mejorarse:

  • No necesita normal gvd para eliminar la selección anterior, normal "xp lo reemplazará incluso si no lo elimina primero
  • Utilice call setreg('x', [lines], type) lugar de let @x = [lines] . Esto establece explícitamente el tipo de registro x . De lo contrario, está confiando en que x ya tiene el tipo correcto (es decir, por caracteres, por líneas o por bloques).

Leave a Comment

Your email address will not be published.

Scroll to Top

istanbul avukat

-

web tasarım