Vimscriptでデリゲートを使用したり、関数を引数として渡すことはできますか?


11

私はvimscriptを学習するための小さなプラグインを作成しようとしています。私の目標は、選択したテキストを処理し、それを結果に置き換える関数をいくつか作成することです。スクリプトには次の項目が含まれています。

  • テキストを処理する2つの関数:パラメーターとして文字列を取り、元のテキストを置き換えるために使用する文字列を返します。現時点では2つしかありませんが、しばらくするとさらに増える可能性があります。

  • 選択されたテキストを取得する関数:これは、最後の選択をヤンクしてそれを返すだけです。

  • ラッパー関数:処理関数を呼び出し、その結果を取得し、古い選択をこの結果に置き換えます。

今のところ、ラッパー関数は次のようになります。

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行目を次のように置き換える2番目のラッパーを作成する必要があります。

let @x = Type2ProcessString(GetSelectedText())

ラッパー関数に、実行し、3行目でジェネリックコールを使用するためのProcess関数を含むパラメーターを与えたいのですが、今のところcall、たとえば次のようなさまざまな方法を試してみました。

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

しかし、私は本当に成功し:h callていませんし、デリゲートトピックについてはあまり役に立ちませんでした。

ここでそれを要約すると、私の質問です:

  • すべての処理用のラッパー関数を1つだけ作成するにはどうすればよいですか?
  • vimscriptでデリゲートとして機能するものはありますか?
  • デリゲートが存在しない場合、私がやりたいことを行う「良い」方法は何でしょうか?

回答:


16

あなたの質問に答えるために:call()マニュアルののプロトタイプはcall({func}, {arglist} [, {dict}])です。{arglist}引数は、引数のリストではなく、文字通りListオブジェクトである必要があります。つまり、次のように記述する必要があります。

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です。このため、通常、クラス属性は実際に苦労する価値はありません。

さて、これはあなたに思考のためのいくつかの食べ物を与えるのに十分でなければなりません。

最初のコードスニペットに戻ると、改善の余地がある2つの詳細があります。

  • normal gvd古い選択を削除する必要normal "xpはなく、最初に削除しなくても置き換えられます
  • call setreg('x', [lines], type)代わりに使用してくださいlet @x = [lines]。これにより、レジスタのタイプが明示的に設定されxます。それ以外の場合は、xすでに正しいタイプ(つまり、文字単位、行単位、またはブロック単位)に依存しています。

辞書に関数を直接作成する場合(つまり、「番号付き関数」)、dictキーワードは必要ありません。これは「クラスメソッド」に適用されます。を参照してください:h numbered-function
Karl YngveLervåg15年

@KarlYngveLervåg技術的には、クラスメソッドとオブジェクトメソッドの両方に適用されます(つまりdict、どのMyClass関数も必要ありません)。しかし、それはわかりにくいので、dict明示的に追加する傾向があります。
lcd047

そうですか。ではdict、目的を明確にするために、オブジェクトメソッドを追加しますが、クラスメソッドは追加しませんか?
Karl YngveLervåg2015

@ lcd047この素晴らしい答えをありがとう!私はそれに取り組む必要がありますが、それはまさに私が探していたものです!
statox

1
@KarlYngveLervågここには微妙さがあり、の意味はselfクラスメソッドとオブジェクトメソッドで異なります。前者の場合はクラス自体であり、後者の場合は現在のオブジェクトのインスタンスです。このため、私は常にクラス自体をg:MyClass、決して使用しないselfと呼び、ほとんどの場合、を使用しても問題ないことをdict思い出させますself(つまり、dict常にオブジェクトインスタンスに作用する関数)。繰り返しになりますが、私はクラスメソッドをあまり使用していません。そのため、dictどこでも省略しがちです。ええ、私の一貫性は私のミドルネームです。;)
lcd047 2015

1

文字列でコマンドを作成し、:exeそれを実行するために使用します。詳細については:help execute、を参照してください。

この場合、executeを使用して関数を呼び出し、結果をレジスターに入れ.ます。コマンドのさまざまな要素を、通常の文字列として演算子と連結する必要があります。行3は次のようになります。

execute "let @x = " . a:functionToExecute . "(GetSelectedText())"
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.