Ist es möglich, einen Delegaten zu verwenden oder eine Funktion als Argument in Vimscript zu übergeben?

11

Ich versuche, ein kleines Plugin zum Erlernen von Vimscript zu erstellen. Mein Ziel ist es, einige Funktionen zu erstellen, die einen ausgewählten Text verarbeiten und durch das Ergebnis ersetzen. Das Skript enthält die folgenden Elemente:

  • Zwei Funktionen, die Text verarbeiten: Sie verwenden eine Zeichenfolge als Parameter und geben die Zeichenfolge zurück, die zum Ersetzen des Originaltexts verwendet werden soll. Im Moment habe ich nur zwei, aber in ein paar Jahren könnte es noch viel mehr geben.

  • Eine Funktion, die den ausgewählten Text abruft : Diese zieht einfach die letzte Auswahl und gibt sie zurück.

  • Eine Wrapper-Funktion: Diese ruft eine Verarbeitungsfunktion auf, ruft ihr Ergebnis ab und ersetzt die alte Auswahl durch dieses Ergebnis.

Im Moment sieht meine Wrapper-Funktion folgendermaßen aus:

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

    " remove the old selection
    normal gvd

    " insert the new string
    normal "xp
endfunction

Und ich muss einen zweiten Wrapper erstellen, der die Zeile 3 durch ersetzt

let @x = Type2ProcessString(GetSelectedText())

Ich möchte meiner Wrapper-Funktion einen Parameter geben, der die Process-Funktion enthält, um einen generischen Aufruf in Zeile 3 auszuführen und zu verwenden. Im Moment habe ich versucht, callverschiedene Methoden zu verwenden, wie zum Beispiel:

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

Aber ich war nicht wirklich erfolgreich und :h callwar in Bezug auf das Delegierten-Thema nicht wirklich hilfreich.

Um es hier zusammenzufassen, sind meine Fragen:

  • Wie kann ich nur eine Wrapper-Funktion für alle verarbeitenden Funktionen erstellen?
  • Gibt es etwas, das als Delegierter in Vimscript funktioniert?
  • Wenn es keine Delegierten gibt, was wäre ein "guter" Weg, um das zu tun, was ich will?
statox
quelle

Antworten:

16

Um Ihre Frage zu beantworten: Der Prototyp call()im Handbuch ist call({func}, {arglist} [, {dict}]); Das {arglist}Argument muss buchstäblich ein List-Objekt sein, keine Liste von Argumenten. Das heißt, Sie müssen es so schreiben:

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

Dies setzt a:functionToExecuteentweder eine Funcref (siehe :help Funcref) oder den Namen einer Funktion (dh eine Zeichenfolge wie z. B. 'Type1ProcessString') voraus .

Das ist eine leistungsstarke Funktion, die Vim eine Art LISP-ähnliche Qualität verleiht, aber Sie würden sie wahrscheinlich selten wie oben verwenden. Wenn a:functionToExecutees sich um eine Zeichenfolge handelt, den Namen einer Funktion, können Sie Folgendes tun:

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

und Sie würden den Wrapper mit dem Namen der Funktion aufrufen:

call Wrapper('Type1ProcessString')

Wenn es sich dagegen a:functionToExecuteum einen Funcref handelt, können Sie ihn direkt aufrufen:

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

Sie müssen den Wrapper jedoch folgendermaßen aufrufen:

call Wrapper(function('Type1ProcessString'))

Mit können Sie das Vorhandensein von Funktionen überprüfen exists('*name'). Dies ermöglicht den folgenden kleinen Trick:

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

dh eine Funktion, die die integrierte Funktion verwendet, strwidth()wenn Vim neu genug ist, um sie zu haben, und auf etwas strlen()anderes zurückgreift (ich behaupte nicht, dass ein solcher Fallback sinnvoll ist; ich sage nur, dass dies möglich ist). :) :)

Mit Wörterbuchfunktionen (siehe :help Dictionary-function) können Sie etwas definieren, das Klassen ähnelt:

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

Dann würden Sie Objekte wie folgt instanziieren:

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

Und nennen Sie seine Methoden:

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

Sie können auch Klassenattribute und -methoden verwenden:

let g:MyClass.__meaning_of_life = 42

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

(Beachten Sie hier keine Notwendigkeit dict).

Bearbeiten: Unterklassen sind ungefähr so:

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

Der subtile Punkt hier ist die Verwendung von copy()statt deepcopy(). Der Grund dafür ist, auf die Attribute der übergeordneten Klasse als Referenz zugreifen zu können. Dies kann erreicht werden, ist aber sehr fragil und es ist alles andere als trivial, es richtig zu machen. Ein weiteres mögliches Problem ist , dass diese Art der Unterklasse verschmilzt is-amit has-a. Aus diesem Grund sind Klassenattribute den Schmerz normalerweise nicht wirklich wert.

Ok, das sollte ausreichen, um zum Nachdenken anzuregen.

Zurück zu Ihrem ursprünglichen Code-Snippet gibt es zwei Details, die verbessert werden könnten:

  • Sie müssen normal gvddie alte Auswahl nicht entfernen normal "xp, sondern ersetzen sie, auch wenn Sie sie nicht zuerst beenden
  • verwenden call setreg('x', [lines], type)statt let @x = [lines]. Dies legt den Typ des Registers explizit fest x. Andernfalls verlassen Sie sich darauf, xdass Sie bereits den richtigen Typ haben (dh zeichenweise, zeilenweise oder blockweise).
lcd047
quelle
Wenn Sie Funktionen direkt in einem Wörterbuch erstellen (dh eine "nummerierte Funktion"), benötigen Sie das dictSchlüsselwort nicht. Dies gilt für Ihre "Klassenmethoden". Siehe :h numbered-function.
Karl Yngve Lervåg
@ KarlYngveLervåg Technisch gilt dies sowohl für Klassen- als auch für Objektmethoden (dh es ist keine dictder MyClassFunktionen erforderlich ). Aber ich finde das verwirrend, deshalb neige ich dazu, dictexplizit hinzuzufügen .
lcd047
Aha. Sie fügen also dictObjektmethoden hinzu, aber nicht Klassenmethoden, um Ihre Absicht zu verdeutlichen?
Karl Yngve Lervåg
@ lcd047 Vielen Dank für diese tolle Antwort! Ich muss daran arbeiten, aber genau das habe ich gesucht!
Statox
1
@ KarlYngveLervåg Hier gibt es eine Subtilität, deren Bedeutung selffür Klassenmethoden und für Objektmethoden unterschiedlich ist - im ersten Fall ist es die Klasse selbst und im zweiten Fall die Instanz des aktuellen Objekts. Aus diesem Grund bezeichne ich die Klasse selbst immer als " g:MyClassnie verwendend" selfund sehe sie meistens dictals Erinnerung daran, dass die Verwendung in Ordnung ist self( dh eine Funktion, die dictimmer auf eine Objektinstanz gewirkt hat). Dann benutze ich nicht viel Klassenmethoden, und wenn ich das tue, neige ich auch dazu, dictüberall wegzulassen . Ja, Selbstkonsistenz ist mein zweiter Vorname. ;)
lcd047
1

Erstellen Sie den Befehl in einer Zeichenfolge und verwenden Sie ihn :exe, um ihn auszuführen. Siehe :help executefür weitere Details.

In diesem Fall, executeum die Funktion aufzurufen und das Ergebnis in das Register zu schreiben, müssen die verschiedenen Elemente des Befehls .als reguläre Zeichenfolge mit dem Operator verknüpft werden. Die Zeile 3 sollte dann werden:

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