是否可以在Vimscript中使用委托或将函数作为参数传递?


11

我正在尝试创建一个小的插件来学习vimscript,我的目标是创建一些函数来处理选定的文本并将其替换为结果。该脚本包含以下各项:

  • 两个处理文本的函数:它们将字符串作为参数,然后返回该字符串,该字符串应用于替换原始文本。目前,我只有两个人,但不久之后可能还会更多。

  • 获取所选文本的函数:只需拉动最后的选择并返回它。

  • 包装函数:调用处理函数,获取其结果并将该结果替换为旧选择。

现在,我的包装器函数如下所示:

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

    " remove the old selection
    normal gvd

    " insert the new string
    normal "xp
endfunction

我必须创建第二个包装器,将第3行替换为

let @x = Type2ProcessString(GetSelectedText())

我想给包装器函数一个参数,该参数包含要执行并在第3行中使用通用调用的Process函数。现在,我尝试使用call不同的方法,例如:

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

但是我并没有真正取得成功,:h call也没有对委托主题真正有所帮助。

总结一下,这是我的问题:

  • 我如何只为所有处理函数提供一个包装函数?
  • 在vimscript中有什么可以充当代理的吗?
  • 如果不存在代表,那么做我想要的事情的“好”方法是什么?

Answers:


16

回答您的问题:call()手册中的原型为call({func}, {arglist} [, {dict}]); 该{arglist}参数必须是名副其实的列表对象,而不是参数列表。也就是说,您必须这样编写:

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

这假定a:functionToExecute是Funcref(请参阅:help Funcref)或函数的名称(例如,字符串,例如'Type1ProcessString')。

现在,这是一项强大的功能,可为Vim提供类似LISP的质量,但您可能很少像上面那样使用它。如果a:functionToExecute是字符串,即函数的名称,则可以执行以下操作:

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

然后使用函数名称调用包装器:

call Wrapper('Type1ProcessString')

另一方面,如果a:functionToExecute是Funcref,则可以直接调用它:

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

但是您需要这样调用包装器:

call Wrapper(function('Type1ProcessString'))

您可以使用来检查功能是否存在exists('*name')。这使得以下小技巧成为可能:

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

例如,strwidth()如果Vim足够新,可以使用内置函数,然后回退到strlen()其他函数(我并不是说这样的回退是有意义的;我只是说可以做到)。:)

使用字典函数(请参阅:help Dictionary-function参考资料),您可以定义类似于类的东西:

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

然后,您将实例化这样的对象:

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

并调用其方法:

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

您还可以具有类属性和方法:

let g:MyClass.__meaning_of_life = 42

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

(请注意,dict此处不需要)。

编辑: 子类化是这样的:

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

这里的微妙之处是使用copy()代替deepcopy()。这样做的原因是能够通过引用访问父类的属性。这是可以实现的,但是它非常脆弱,正确处理绝非易事。另一个潜在的问题是此类子类is-a与合并has-a。由于这个原因,类属性通常并不值得。

好的,这应该足以让您思考。

回到您的原始代码段,有两个可以改进的细节:

  • 您无需normal gvd删除旧选择,normal "xp即使您不先杀死它也将替换它
  • 使用call setreg('x', [lines], type)代替let @x = [lines]。这将显式设置寄存器的类型x。否则,您将依赖于x已经具有正确的类型(即,字符,行或块)。

直接在字典中创建函数(即“编号函数”)时,不需要dict关键字。这适用于您的“类方法”。请参阅:h numbered-function
Karl YngveLervåg,2015年

@KarlYngveLervåg从技术上讲,它同时适用于类和对象方法(即,不需要dict任何MyClass功能)。但是我感到困惑,所以我倾向于dict明确添加。
lcd047

我知道了。因此,您添加dict对象方法而不是类方法是为了帮助阐明您的意图?
Karl YngveLervåg2015年

@ lcd047非常感谢这个惊人的答案!我将不得不对其进行处理,但这正是我所寻找的!
statox

1
@KarlYngveLervåg这里有一个微妙之处,self对于类方法和对象方法而言,的含义是不同的-在前一种情况下,它是类本身,而在后一种情况下,它是当前对象的实例。因此,我总是将类本身称为g:MyClass,从不使用self,并且大多数情况下,我将其dict视为可以使用的提醒self(即,dict始终对对象实例起作用的函数)。再说一次,我不太使用类方法,而当我这样做时,我也倾向于在dict各处省略。是的,自洽是我的中间名。;)
lcd047

1

将命令构建为字符串并用于:exe运行它。请参阅:help execute以获取更多详细信息。

在这种情况下,execute用于调用函数并将结果存储在寄存器中,必须将命令的不同元素与.运算符连接为常规字符串。第3行应变为:

execute "let @x = " . a:functionToExecute . "(GetSelectedText())"
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.