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())

래퍼 함수에 3 행에서 일반 호출을 실행하고 사용하기 위해 Process 함수를 포함하는 매개 변수를 제공하고 싶습니다 call.

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

그러나 나는 실제로 성공 :h call하지 못했고 대리자 주제에 대해 실제로 도움이되지 않았습니다.

요약하면 여기 내 질문이 있습니다.

  • 모든 처리 기능에 대해 하나의 랩퍼 기능 만 만들려면 어떻게해야합니까?
  • vimscript의 대리자로 작동하는 것이 있습니까?
  • 대리인이 존재하지 않으면 내가 원하는 것을 수행하는 "좋은"방법은 무엇입니까?

답변:


16

귀하의 질문에 대답하기 위해 : call()매뉴얼 의 프로토 타입 은 call({func}, {arglist} [, {dict}]); {arglist}인수는 인수 목록 문자 목록 객체가 아니라 할 필요가있다. 즉, 다음과 같이 작성해야합니다.

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

이는 a:functionToExecuteFuncref (참조 :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:functionToExecuteFuncref 인 경우 직접 호출 할 수 있습니다.

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의도를 명확히하기 위해 객체 메소드 를 추가 하지만 클래스 메소드는 추가 하지 않습니까?
Karl 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())"
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.