Section 01execute — 문자열을 명령으로
execute 명령은 문자열을 마치 Vimscript 명령인 것처럼 평가합니다. 임의의 문자열로 명령을 조립할 수 있게 해주는 매우 강력한 도구입니다.
:execute "echom 'Hello, world!'" :execute "rightbelow vsplit " . bufname("#")
둘째 줄은 "rightbelow vsplit "와 bufname("#")의 결과(이전 버퍼 경로)를 점 연산자로 이어 명령 문자열을 만들고, 그것을 실행합니다.
execute는 다릅니다. ① Vimscript 코드는 보통 단 한 사람 — 사용자 자신 — 의 입력만 받습니다. ② Vimscript 문법이 까다로워서, execute가 무언가를 해내는 가장 쉽고 직접적인 길일 때가 많습니다.
Section 02normal! — 키 입력 흉내
normal 명령은 일련의 키를 받아, 그것이 노멀 모드에서 입력된 것처럼 동작하게 합니다. 플러그인을 쓸 때는 항상 normal!(느낌표 붙은 형태)을 쓰세요 — 사용자가 어떤 키를 매핑했을지 신뢰할 수 없기 때문입니다.
:normal! ggdd " 첫 줄로 가서 삭제
normal!은 <cr> 같은 특수 문자를 인식하지 못합니다. :normal! /foo<cr>는 글자 그대로 "f, o, o, <, c, r, >"를 검색하려 듭니다. 검색을 실행하는 엔터가 전달되지 않습니다.
해결책은 execute와 normal!을 결합하는 것입니다. 이것은 Vimscript의 핵심 관용구입니다:
:execute "normal! gg/foo\\<cr\>dd"
execute가 받는 문자열에서는 \<cr> 같은 이스케이프 시퀀스가 진짜 캐리지 리턴 문자로 변환됩니다. 이 줄은 "파일 맨 위로 → foo 검색 → 검색 실행 → 그 줄 삭제"를 수행합니다. 이 관용구로, 바닐라 Vim에서 할 줄 아는 모든 것을 Vimscript 코드 안에서 할 수 있습니다.
Section 03정규표현식과 very magic
Vim은 텍스트 편집기이므로 Vimscript 코드의 상당 부분이 텍스트를 다룹니다. Vim은 강력한 정규표현식을 지원하지만, 별난 점이 있습니다 — Vim에는 정규표현식 파싱 모드가 네 가지나 있습니다!
기본 모드에서는 + 앞에 백슬래시가 필요합니다(\+가 "하나 이상"). 이 혼란을 피하는 가장 제정신인 해법 — 모든 정규표현식을 \v로 시작하세요:
:execute "normal! gg" . '/\vfor .+ in .+:' . "\\<cr\>"
\v("very magic")로 시작하세요. 그러면 Vim의 다른 세 가지 정신 나간 정규표현식 모드를 신경 쓸 필요가 없습니다 — \v 모드는 Python·Perl·Ruby에서 익숙한 것과 거의 같습니다.
:set hlsearch incsearch로 검색 강조를 켜 두면 정규표현식을 실험하기 좋습니다. hlsearch는 모든 일치를 강조하고, incsearch는 입력하는 동안 다음 일치를 강조합니다.
Section 04사례 연구 — Grep 연산자
이제 배운 모든 것을 모아 진짜 Vim 연산자를 만듭니다 — "grep 연산자". 어떤 모션이든 받아서, 그 텍스트를 외부 grep으로 검색하는 연산자입니다. <leader>giw는 "단어 안쪽을 grep", <leader>gi'는 "작은따옴표 문자열 안쪽을 grep".
새 연산자는 함수와 매핑 두 부분으로 시작합니다:
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@ vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr> function! GrepOperator(type) echom a:type endfunction
operatorfunc 옵션을 함수로 설정한 뒤 g@를 실행하면, Vim이 그 함수를 연산자처럼 호출합니다. 함수의 type 인자는 어떤 종류의 모션이 쓰였는지 알려줍니다(v, V, char, line).
이제 함수가 실제로 텍스트를 복사하고, 이스케이프하고, grep을 실행하게 채워 봅시다:
function! GrepOperator(type) if a:type ==# 'v' normal! `<v`>y elseif a:type ==# 'char' normal! `[v`]y else return endif silent execute "grep! -R " . shellescape(@@) . " ." copen endfunction
① 모션 타입을 검사한다(v=비주얼, char=문자 모션).
② 해당 범위를 마크(`<, `[ 등)로 비주얼 선택하고 y로 복사한다.
③ 복사된 텍스트는 무명 레지스터 @@에 들어간다.
④ shellescape()로 셸 인자를 안전하게 이스케이프한 뒤, silent execute로 grep을 실행하고 copen으로 결과 창을 연다.
Section 05배려하는 Vimscript 시민
연산자는 잘 동작하지만, Vimscript를 쓴다는 것의 일부는 사용자를 배려하는 것입니다. 두 가지를 더 합니다.
레지스터 보존
무명 레지스터에 텍스트를 yank하면 그 안에 있던 것이 파괴됩니다. yank 전에 저장하고, 끝나면 복원하세요:
function! GrepOperator(type) let saved_unnamed_register = @@ " ... 텍스트 복사, grep 실행 ... let @@ = saved_unnamed_register endfunction
네임스페이싱
스크립트가 전역 네임스페이스에 GrepOperator를 만들면 충돌 위험이 있습니다. 함수 이름을 s:로 시작해 스크립트-로컬로 만들고, 매핑에서는 <SID>를 붙여 그 함수를 찾게 합니다:
nnoremap <leader>g :set operatorfunc=<SID>GrepOperator<cr>g@ function! s:GrepOperator(type) " ... endfunction
s:로 스크립트-로컬로 만들어 전역 네임스페이스를 오염시키지 마세요. 이것이 사용자를 놀라게 하지 않는, 배려하는 Vimscript 시민의 자세입니다.
Section 06연습문제
:help execute,:help :normal,:help silent를 읽으세요.:help magic와:help pattern-overview를 읽고, Vim 정규표현식이 지원하는 것들을 보세요.:help expr-quote를 읽고, 문자열에서 쓸 수 있는 이스케이프 시퀀스를 확인하세요.- 완성한 grep 연산자를
~/.vim/plugin/grep-operator.vim에 두고 실제로 써 보세요. :help operatorfunc,:help map-operator,:help visualmode(),:help <SID>를 읽으세요.:help :grep,:help cnext,:help cprevious를 읽고, 결과 사이를 빠르게 오가는 매핑을 만드세요.