Czy można użyć delegata lub przekazać funkcję jako argument w Vimscript?

11

Próbuję utworzyć małą wtyczkę do nauki vimscript, moim celem jest stworzenie niektórych funkcji przetwarzających zaznaczony tekst i zastępujących go wynikiem. Skrypt zawiera następujące elementy:

  • Dwie funkcje przetwarzające tekst: przyjmują ciąg jako parametr i zwracają ciąg, który powinien zostać użyty do zastąpienia oryginalnego tekstu. Na razie mam tylko dwa, ale może za kilka razy o wiele więcej.

  • Funkcja pobierająca zaznaczony tekst: który szarpnie ostatni wybór i zwraca go.

  • Funkcja otoki: która wywołuje funkcję przetwarzania, uzyskuje wynik i zastępuje stary wybór tym wynikiem.

Na razie moja funkcja otoki wygląda następująco:

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

    " remove the old selection
    normal gvd

    " insert the new string
    normal "xp
endfunction

I muszę stworzyć drugie opakowanie, zastępujące linię 3

let @x = Type2ProcessString(GetSelectedText())

Chciałbym nadać mojej funkcji otoki parametr zawierający funkcję Process w celu wykonania i użycia ogólnego wywołania w linii 3. Na razie próbowałem na callróżne sposoby, na przykład:

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

ale tak naprawdę nie odniosłem sukcesu i :h callnie byłem tak naprawdę pomocny w delegowaniu tematu.

Podsumowując, oto moje pytania:

  • Jak mogę utworzyć tylko jedną funkcję otoki dla wszystkich przetwarzających?
  • Czy jest coś, co działa jako delegat w vimscript?
  • Jeśli delegaci nie istnieją, jaki byłby „dobry” sposób robienia tego, co chcę?
statox
źródło

Odpowiedzi:

16

Aby odpowiedzieć na twoje pytanie: prototyp call()w instrukcji jest call({func}, {arglist} [, {dict}]); {arglist}argumentem musi być dosłownie obiekt List, a nie lista argumentów. Oznacza to, że musisz to napisać w ten sposób:

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

Zakłada się, że a:functionToExecutejest to Funcref (patrz :help Funcref) lub nazwa funkcji (tj. Ciąg znaków, taki jak 'Type1ProcessString').

To potężna funkcja, która daje Vimowi jakość przypominającą LISP, ale prawdopodobnie rzadko używałbyś go jak wyżej. Jeśli a:functionToExecutejest ciągiem, nazwą funkcji, możesz to zrobić:

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

i wywołałbyś opakowanie z nazwą funkcji:

call Wrapper('Type1ProcessString')

Jeśli z drugiej strony a:functionToExecutejest Funcref, możesz wywołać go bezpośrednio:

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

ale musisz wywołać opakowanie w ten sposób:

call Wrapper(function('Type1ProcessString'))

Możesz sprawdzić istnienie funkcji za pomocą exists('*name'). Umożliwia to następującą małą sztuczkę:

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

tj. funkcja, która korzysta z wbudowanego, strwidth()jeśli Vim jest wystarczająco nowy, aby go mieć, i wraca do strlen()innych (nie twierdzę, że taka awaria ma sens; mówię tylko, że można to zrobić). :)

Za pomocą funkcji słownika (patrz :help Dictionary-function) możesz zdefiniować coś przypominającego klasy:

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

Następnie utworzyłbyś takie obiekty:

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

I nazwij jej metody:

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

Możesz także mieć atrybuty i metody klas:

let g:MyClass.__meaning_of_life = 42

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

(zauważ, że nie ma dicttu potrzeby ).

Edycja: Podklasowanie jest mniej więcej takie:

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

Subtelnym punktem tutaj jest użycie copy()zamiast deepcopy(). Powodem tego jest dostęp do atrybutów klasy nadrzędnej przez odniesienie. Można to osiągnąć, ale jest to bardzo kruche, a poprawne wykonanie go wcale nie jest trywialne. Innym potencjalnym problemem jest to, że tego rodzaju zrównują podklasy is-az has-a. Z tego powodu atrybuty klasy zwykle nie są tak naprawdę warte bólu.

Ok, to powinno wystarczyć, aby dać ci trochę do myślenia.

Wracając do początkowego fragmentu kodu, można go ulepszyć w dwóch szczegółach:

  • nie musisz normal gvdusuwać starego wyboru, normal "xpzastąpi go, nawet jeśli nie zabijesz go jako pierwszy
  • użyj call setreg('x', [lines], type)zamiast let @x = [lines]. To wyraźnie określa typ rejestru x. W przeciwnym razie polegasz na tym, że masz xjuż prawidłowy typ (tj. Znakowy, liniowy lub blokowy).
lcd047
źródło
Gdy tworzysz funkcje bezpośrednio w słowniku (tj. „Funkcja numerowana”), nie potrzebujesz dictsłowa kluczowego. Dotyczy to twoich „metod klasowych”. Zobaczyć :h numbered-function.
Karl Yngve Lervåg,
@ KarlYngveLervåg Technicznie dotyczy to zarówno metod klasowych, jak i obiektowych (tzn. Nie ma potrzeby korzystania dictz żadnej MyClassfunkcji). Uważam to jednak za mylące, dlatego staram się dictwyraźnie dodawać .
lcd047,
Widzę. Więc dodajesz dictdo metod obiektowych, ale nie do metod klasowych, aby pomóc wyjaśnić swoje zamiary?
Karl Yngve Lervåg
@ lcd047 Dziękuję bardzo za tę niesamowitą odpowiedź! Będę musiał nad tym popracować, ale tego właśnie szukałem!
statox
1
@ KarlYngveLervåg Jest tutaj subtelność, znaczenie selfjest różne dla metod klasowych i metod obiektowych - to sama klasa w pierwszym przypadku, a instancja bieżącego obiektu w drugim. Z tego powodu zawsze odnoszę się do samej klasy jako „ g:MyClassnigdy nie używam” self, a przede wszystkim widzę w dictniej przypomnienie, że można z niej korzystać self(tj. Funkcja, która dictzawsze działa na instancję obiektu). Z drugiej strony nie używam metod klasowych, a kiedy to robię, mam tendencję do omijania dictwszystkiego. Tak, moja konsekwencja to moje drugie imię. ;)
lcd047,
1

Zbuduj polecenie w ciągu i użyj go, :exeaby je uruchomić. Zobacz :help executepo więcej szczegółów.

W takim przypadku executesłuży do wywołania funkcji i umieszczenia wyniku w rejestrze, różne elementy polecenia muszą zostać połączone z .operatorem jako ciąg zwykły. Wiersz 3 powinien wtedy stać się:

execute "let @x = " . a:functionToExecute . "(GetSelectedText())"
cxw
źródło