Можно ли использовать делегат или передать функцию в качестве аргумента в 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?
  • Если бы делегатов не было, что было бы "хорошим" способом сделать то, что я хочу?
statox
источник

Ответы:

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уже имеющих правильный тип (т.е. посимвольный, построчной или покадрово).
lcd047
источник
При создании функции в словаре непосредственно (т.е. «номер функции»), вам не нужно dictключевое слово. Это относится к вашим "методам класса". См :h numbered-function.
Карл Yngve Lervåg
@ KarlYngveLervåg Технически это применимо как к классу, так и к объектным методам (то есть нет необходимости dictв какой-либо из MyClassфункций). Но я нахожу это запутанным, поэтому я склонен добавлять dictявно.
lcd047
Понимаю. Таким образом , вы добавите dictк методам объекта, но не методы класса для того, чтобы помочь прояснить ваши намерения?
Карл Yngve Lervåg
@ 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())"
CXW
источник