Чи можливо використовувати делегата або передати функцію як аргумент у 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())

Я хотів би надати своїй функції обгортки параметр, що містить функцію Process, щоб виконувати та використовувати загальний виклик у рядку 3. Наразі я спробував використовувати callрізні способи, наприклад, такий:

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

але я не був дуже успішним і :h callне дуже допомагав у темі делегата.

Підводячи підсумок, ось мої запитання:

  • Як я можу зробити лише одну функцію обгортки для всіх обробних?
  • Чи є щось, що працює як делегат у vimscript?
  • Якщо делегатів не існує, що було б «хорошим» способом робити те, що я хочу?

Відповіді:


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 Yngve Lervåg

@ KarlYngveLervåg Технічно він застосовується як до класових, так і до об'єктних методів (тобто немає потреби dictв жодній із MyClassфункцій). Але я вважаю це заплутаним, тому я, як правило, dictявно додаю .
lcd047

Я бачу. Отже, ви додаєте dictдля об'єктних методів, але не для методів класу, щоб допомогти уточнити свій намір?
Карл Інгве Лервег

@ 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())"
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.