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 delet @x = [lines]
. Esto establece explícitamente el tipo de registrox
. De lo contrario, está confiando en quex
ya tiene el tipo correcto (es decir, por caracteres, por líneas o por bloques).