15

I am trying to create a small plugin to learn vimscript, my goal is to create some functions processing a selected text and replacing it with the result. The script contains the following items:

  • Two functions processing text: they take a string as parameter an return the string which should be used to replace the original text. For now I have only two but there might by a lot more in a few time.

  • A function getting the selected text: which simply yank the last selection and return it.

  • A wrapper function: which call a processing function, get its result and replace the old selection with this result.

For now my wrapper function looks like this:

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

    " remove the old selection
    normal gvd

    " insert the new string
    normal "xp
endfunction

And I have to create a second wrapper replacing the line 3 with

let @x = Type2ProcessString(GetSelectedText())

I'd like to give to my wrapper function a parameter containing the Process function to execute and use a generic call in line 3. For now I have tried using call different ways like, for example, this:

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

but I haven't been really succesful and :h call hasn't been really helpful on the delegate topic.

To sum it up here are my questions:

  • How can I make only one wrapper function for all the processing ones?
  • Is there something which works as a delegate in vimscript?
  • If delegates doesn't exist what would be a "good" way to do what I want?

2 Answers 2

20

To answer your question: the prototype of call() in the manual is call({func}, {arglist} [, {dict}]); the {arglist} argument needs to be literally a List object, not a list of arguments. That is, you have to write it like this:

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

This assumes a:functionToExecute is either a Funcref (see :help Funcref), or the name of a function (i.e. a string, such as 'Type1ProcessString').

Now, that's a powerful feature that gives Vim a sort of LISP-like quality, but you'd probably seldom use it as above. If a:functionToExecute is a string, the name of a function, then you can do this:

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

and you'd call the wrapper with the name of the function:

call Wrapper('Type1ProcessString')

If on the other hand a:functionToExecute is a Funcref, you can call it directly:

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

but you need to call the wrapper like this:

call Wrapper(function('Type1ProcessString'))

You can check for existence of functions with exists('*name'). This makes possible the following little trick:

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

i.e. a function that uses the built-in strwidth() if Vim is new enough to have it, and falls back to strlen() otherwise (I'm not arguing that such a fallback makes sense; I'm just saying it can be done). :)

With dictionary functions (see :help Dictionary-function) you can define something resembling 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

Then you'd instantiate objects like this:

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

And call its methods:

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

You can also have class attributes and methods:

let g:MyClass.__meaning_of_life = 42

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

(notice no need for dict here).

Subclassing is something like this:

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

The subtle point here is the use of copy() instead of deepcopy(). The reason for this is to be able to access the attributes of the parent class by reference. This can be achieved, but it's highly fragile and getting it right is far from trivial. Another potential problem is that this kind of subclass conflates is-a with has-a. For this reasons class attributes are usually not really worth the pain.

Ok, this should be enough to give you some food for thought.

Back to your initial code snippet, there are two details with it that could be improved:

  • You don't need normal gvd to remove the old selection, normal "xp will replace it even if you don't kill it first
  • Use call setreg('x', [lines], type) instead of let @x = [lines]. This explicitly sets the type of the register x. Otherwise you're relying on x already having the correct type (i.e. characterwise, linewise, or blockwise).
11
  • When you create functions in a dictionary directly (i.e. a "numbered function"), you don't need the dict keyword. This applies to your "class methods". See :h numbered-function. Commented Jul 11, 2015 at 19:33
  • @KarlYngveLervåg Technically it applies to both class and object methods (i.e. there's no need for dict for any of the MyClass functions). But I find that confusing, so I tend to add dict explicitly.
    – lcd047
    Commented Jul 11, 2015 at 20:46
  • I see. So you add dict for object methods, but not for class methods, in order to help clarify your intent? Commented Jul 12, 2015 at 11:08
  • @lcd047 Thanks a lot for this amazing answer! I'll have to work on it but that is exactly what I was looking for!
    – statox
    Commented Jul 12, 2015 at 12:02
  • 1
    @KarlYngveLervåg There's a subtility here, the meaning of self is different for class methods and for object methods -- it's the class itself in the former case, and the instance of the current object in the latter. For this reason I always refer to the class itself as g:MyClass, never using self, and I mostly see the dict as a reminder that it's ok to use self (that is, a function that has dict always acts on an object instance). Than again, I don't use class methods much, and when I do that I also tend to omit dict everywhere. Yeah, self-consistency is my middle name. ;)
    – lcd047
    Commented Jul 12, 2015 at 16:25
1

Build the command in a string and use :exe to run it. See :help execute for more details.

In this case, execute is used to make the call to the function and put the result in the register, the different elements of the command must be concatenated with the . operator as a regular string. The line 3 should then become:

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

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.