텍스트 오브젝트
vim의 가장 독특한 기능 중 하나를 꼽으라면 “텍스트 오브젝트”를 취급하는 기능입니다. 예를 들어 di)
같은 명령으로 괄호 내부의 텍스트를 삭제하거나, dd
를 사용하여 한 줄 전체를 지우는 것 등이 있습니다. 이러한 동작을 커스텀 함수를 통해서 사용할 수 있을까요?
Act on text objects with custom functions
예를 들어 \u
라는 키 맵을 URL을 브라우저로 여는 동작으로 만든다고 칩시다. 그렇다면 \ui]
를 사용해서 대괄호로 둘러싸인 내부의 URL을 브라우저로 열 수 있다면 좋겠습니다. \u$
를 써서 현재 커서 위치로부터 줄 끝까지를 URL로 잡을 수도 있겠고, 혹은 \uu
를 사용하여 한 줄 전체를 사용할 수 있겠죠.
how it works
텍스트 오브젝트를 사용하는 노멀 명령은 흔히 ‘연산자’라고 부릅니다. d3w
라는 명령은 delete 3 * word
로 해석되어 커서 위치로부터 3개의 단어를 삭제한다는 뜻이지요. 텍스트 오브젝트를 피연산자로 보는 이 관점은 다시 연산자와 오브젝트가 각각 카운트 값을 가지는 형태로 정말이지 온갖 방법으로 확장 가능해집니다. 이러한 연산자에는 다음과 같은 것들이 있습니다.
c
(변경),d
(삭제),y
(복사)~
(대소문자 반전)g~
(대소문자 반전)gu
(소문자화)gU
(대문자화)gq
(텍스트 포맷)gw
(텍스트 포맷, 커서 이동 안함)>
(들여쓰기)<
(내어쓰기)zf
(폴드 정의하기)g@
(커스텀 동작)
커스텀 동작은 'operatorfunc'
옵션이 있는 경우, 이 옵션의 값을 사용해서 텍스트 오브젝트를 조작할 수 있게 합니다. operatorfunc( 줄여서 'opfunc'
) 옵션의 값은 다름 아닌 함수가 됩니다. 특정한 사용자 정의 함수를 작성하고, 함수를 이 옵션의 값으로 지정해 놓으면 “g@” 명령을 사용해서 조작합니다.
- opfunc 옵션으로 지정된 함수는 텍스트 오브젝트의 ‘타입’을 인자로 받습니다. 이는 텍스트 오브젝트가 글자 단위인지, 줄단위인지, 비주얼블럭 단위인지 등을 나타냅니다.
- 함수 내에서 텍스트 오브젝트의 시작과 끝은 각각,
'[
,']
마커로 참조할 수 있습니다.
이러한 기능을 주로 키맵에 연결하고, 여러 기능이 이런식으로 작동하려면 결국 키 맵에서 opfunc를 설정하고 g@
를 실행하도록 해야 합니다.1g@
명령은 ‘operatorfunc’ 옵션에서 지정한 함수를 호출하는 명령입니다. 이를 위해 키맵이 어떤 함수를 실행하면서 함수 내에서 normal g@ 를 쓰는 방법도 있겠지만, expression 키 맵을 쓰는 것이 가장 깔끔합니다.
간단한 예제를 하나 보겠습니다. 아래 s:objfunc()
함수는 텍스트 오브젝트를 처리하는 연산자로 작동하는 기능입니다. 예제에서는 이 함수를 <F5>
키를 누를 때 발동하는 것으로 설정합니다.
이 설정 아래에서 <F5>iw
를 누르게 되면 다음과 같이 작동하게 됩니다.
- 우선
<F5>
를 누르는 시점에 키 맵에 의해s:objfunc()
가 인자 없이 먼저 호출됩니다. 이 동작에서는 함수 내부에서'operatorfunc'
옵션에s:objcfunc
함수를 세팅하고'g@'
를 리턴합니다. - 표현식 키맵의 결과로,
<F5>
키를 누르고 나면g@
를 누른 것과 같은 효과가 납니다. 이 상태에서는 연산할 텍스트 오브젝트 입력을 기다립니다. iw
가 입력됩니다. 결과적으로는g@iw
가 입력된 것이니<SID>objfunc(type='char')
가 호출되는 셈입니다.
아래는 예제 코드입니다. 대부분의 텍스트 오브젝트 연산자는 이 방식대로 만들게 됩니다.
function s:txtobj(type='')
if a:type == ''
let &operatorfunc = expand('<SID>') .. 'txtobj'
return 'g@'
endif
call popup_dialog('triggered: ' .. a:type, #{time: 1000})
endfunction
nnoremap <expr> <F5> <SID>txtobj()