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


11

Estoy tratando de crear un pequeño complemento para aprender vimscript, mi objetivo es crear algunas funciones que procesen un texto seleccionado y lo reemplacen 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 mucho más en poco tiempo.

  • Una función para obtener 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 de envoltura 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 de envoltura un parámetro que contenga la función Proceso para ejecutar y usar una llamada genérica en la línea 3. Por ahora he intentado usar calldiferentes formas como, por ejemplo, esto:

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

pero no he tenido mucho éxito y :h callno he sido realmente útil en el tema del delegado.

Para resumir aquí están mis preguntas:

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

Respuestas:


16

Para responder a su pregunta: el prototipo de call()en el manual es call({func}, {arglist} [, {dict}]); el {arglist}argumento 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:functionToExecutees 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á como se indicó anteriormente. Si a:functionToExecutees una cadena, el nombre de una función, 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:functionToExecutees un Funcref, puede llamarlo directamente:

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

pero necesitas llamar al contenedor así:

call Wrapper(function('Type1ProcessString'))

Puede verificar 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 incorporado strwidth()si Vim es lo suficientemente nuevo como para tenerlo, y recurre a lo strlen()contrario (no estoy argumentando que tal retroceso tenga sentido; solo digo que se puede hacer). :)

Con las funciones de diccionario (ver :help Dictionary-function) puede definir algo parecido a las 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 instancias de objetos como este:

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

Y llame 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

(Aviso no es necesario dictaquí).

Editar: Subclasificar es algo como esto:

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

El punto sutil aquí es el uso de en 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 se combina is-acon has-a. Por esta razón, los atributos de clase generalmente no valen la pena.

Ok, esto debería ser suficiente para pensar un poco.

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

  • no necesita normal gvdeliminar la selección anterior, la normal "xpreemplazará incluso si no la mata primero
  • usar en call setreg('x', [lines], type)lugar de let @x = [lines]. Esto establece explícitamente el tipo de registro x. De lo contrario, dependerá de que xya tenga el tipo correcto (es decir, en forma de caracteres, en línea o en bloque).

Cuando crea funciones en un diccionario directamente (es decir, una "función numerada"), no necesita la dictpalabra clave. Esto se aplica a sus "métodos de clase". Ver :h numbered-function.
Karl Yngve Lervåg

@ KarlYngveLervåg Técnicamente se aplica tanto a los métodos de clase como de objeto (es decir, no hay necesidad dictde ninguna de las MyClassfunciones). Pero me parece confuso, así que tiendo a agregar dictexplícitamente.
lcd047

Veo. ¿Entonces agrega dictmétodos de objeto, pero no métodos de clase, para ayudar a aclarar su intención?
Karl Yngve Lervåg

@ lcd047 ¡Muchas gracias por esta increíble respuesta! Tendré que trabajar en eso, ¡pero eso es exactamente lo que estaba buscando!
statox

1
@ KarlYngveLervåg Aquí hay una sutileza, el significado de selfes diferente para los métodos de clase y para los métodos de objeto: es la clase misma en el primer caso y la instancia del objeto actual en el segundo. Por esta razón, siempre me refiero a la clase en sí misma como g:MyClass, nunca usando self, y en su mayoría veo el dictrecordatorio de que está bien usar self(es decir, una función que dictsiempre actúa en una instancia de objeto). Por otra parte, no uso mucho los métodos de clase, y cuando lo hago, también tiendo a omitir en dicttodas partes. Sí, la autoconsistencia es mi segundo nombre. ;)
lcd047

1

Cree el comando en una cadena y úselo :exepara ejecutarlo. Ver :help executepara más detalles.

En este caso, executese utiliza para hacer la llamada a la función y poner el resultado en el registro, los diferentes elementos del comando deben concatenarse con el .operador como una cadena regular. La línea 3 debería convertirse en:

execute "let @x = " . a:functionToExecute . "(GetSelectedText())"
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.