Case Study: Grep Operator, Part Two
앞 장에서는 기본적인 해결책을 작성해 보았다. 이제는 그 명령어에 살을 붙여보자.
우리의 원래 목표는 "grep operator"를 만드는 것이다. 새로운 것들을 많이 다루어야 하겠지만, 우선 저번 장에서 했던 방식으로 진행해보자: 간단하게 시작해서 필요한 기능으로 바꾸기
이전 장에서 사용한 매핑 (<leader>g)을 동일하게 사용할 것이기 때문에 이전 장에서 ~/.vimrc에 추가한 매핑을 주석처리 해주자.
vim 파일에서 주석은 줄 앞에
"를 추가하면 된다.
" nnoremap <leader>g :silent execute "grep! -R " . shellescape(expand("<cWORD>")) . " ."<cr>:copen<cr>operator를 만드는 것은 여러 코멘드를 쉽게 작성하게 해준다. 이를 ~/.vimrc에 추가할 수도 있지만, 이번에는 분리된 파일로 만들어보자.
우선, vim plugin 디렉토리를 찾아야 한다. 맥이나 Linux는 ~/.vim/plugin에 있을 것이고, 윈도우라면 vimfiles 디렉토리에 있을것이다.
vim이 어디에 있는지 궁금하다면
:echo $HOME명령어를 실행시키면 알 수 있다. 보통은 사용자 디렉토리에 있을것이다.Plug같은 플러그인 매니저를 사용중이라면plugged,bundle같은 해당 디렉토리로 들어가면 된다.
Skkeleton
Vim operator는 두가지 컴포넌트로 구성된다 - 함수와 매핑이다. 우선 다음 명령어를 grep-opeerator.vim 파일에 추가해보자.
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
function! GrepOperator(type)
echom "Test"
endfunction해당 파일을 다 작성하고 :source %로 적용시켜보자. <leader>giw와 같은 명령어 (grep inside word)를 실행시키면 vim은 iw 모션을 받은 이후 Test를 출력할 것이다.
위 예시에서 함수 자체는 새로운 내용이 없지만, 매핑은 조금 더 복잡하다.
우선 우리는 operatorfunc에 우리가 만든 함수를 set한 뒤, g@로 해당 함수를 opeerator로 호출하였다.
일단은 해당 내용을 magic이라고 생각하고 이후 이 내용을 더 깊게 살펴볼 것이다.
Visual Mode
위 예시는 operator를 normal mode에서 추가하였다. (nnoremap) 이를 visual mode에도 추가해보자. 아래 명령어를 아까 만든 파일 (grep-operator.vim)의 맨 윗줄에 추가해주자.
vnoremap <ledaer>g :<c-u>call GrepOperator(visualmode())<cr>해당 파일을 적용해주고 (:source %) visual mode로 아무 텍스트를 선택한 후 <leader>g를 사용해보자. vim이 Test를 출력하는 것을 볼 수 있다.
<c-u>는 우리가 지금까지 보지 못한 새로운 매핑이다. 아무 텍스트를 선택한 후 :를 눌러보자. :를 눌렀을 뿐인데 command line에 :'<,'>가 자동으로 써진 것을 볼 수 있을것이다.
vim은 vimsual mode에서 선택된 범위를 코멘드에서 사용할 수 있게 제공해준다 ('<,'>) 하지만 위의 예시에서는 선택된 텍스트가 중요하지 않으므로 <c-u>를 눌러서 "커서로부터 첫줄까지를 제거" 해준다. 이는 :만 남겨두고, call 명령을 실행시킬 수 있게 해준다.
위 설명이 헷갈리다면,
:로 명령어 모드로 진입한 후 아무 명령어나 입력하고<c-u>를 눌러보자. 작성한 명령어가 모두 제거된 것을 확인할 수 있다.
call GrepOperator()는 단순히 함수를 호출한 것이지만 함수의 인자로 넣은 visualmode()는 처음 보는 것이다. 이는 vim의 빌트인 함수로 visual mode에서 가장 마지막에 선택한 항목에 따라 "v"나 "V", Ctrl-v를 반환한다.
문자열을 선택한 경우
"v", 라인을 선택한 경우"V", 문단을 선택한 경우Ctrl-v를 반환한다. visual mode로 문자, 라인, 문단을 각각 선택해보고 (각각 v, V, Ctrl-v로 선택 가능하다):echo visualmode()를 실행시켜보면 확실하게 확인 가능하다.
Motion Types
우리가 작성한 GrepOperator 함수는 type 인자를 받는다. visual mode에서는 visualmode() 함수로 반환된 내용을 type으로 넘기는 것을 알 수 있었다. 하지만 normal mode에서 operator를 사용하면ㅁ 어떻게 type을 넘길 수 있을까?
파일의 내용을 아래와 같이 수정해보자. echom "Test" 대신 echom a:type으로 인자로 받은 type을 그대로 출력하게 변경하였다.
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>
function! GrepOperator(type)
echom a:type
endfunction:source %를 실행시키고 여러가지 방법으로 테스트를 해보자. 다음은 예시
viw<leader>g는v를 출력: characterwise visual modeVjj<leader>g는V를 출력: linewise visual mode<leader>giw는char를 출력: used characterwise motion with the operator<leader>gG는line을 출력: used linewise mohion with the operator
한다.
Copying the Text
함수에서는 user가 검색하고자 하는 text를 가져와야 하는데, 가장 쉬운 방법은 이를 복사하는 방법이다. 함수를 아래와 같이 수정해보자.
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>
function! GrepOperator(type)
if a:type ==# 'v'
execute "normal! `<v`>y"
elseif a:type ==# 'char'
execute "normal! `[v`]y"
else
return
endif
echom @@
endfunction새로운 내용이 많이 추가되었다. :source %로 적용시킨 후에 여러가지 명령어를 테스트 해 보자. <leader>giw, <leader>g2e, (괄호 안에 커서를 두고) vi(<leader>g 등... 명령어를 실행시킬 때마다 해당 motion에 해당하는 텍스트를 출력하는 것을 볼 수 있다.
그럼 새로운 코드를 하나씩 분석해보자. 먼저 if문에서 a:type 인자를 체크한다. 만약 타입이 v라면 우리가 문자열 visual mode를 사용(v)한 것으로 visual mode에서 선택된 텍스트를 복사한다.
==# (case-sensitive 비교)를 사용한 것을 주목하자. 일반 비교 ==를 사용하면 ignorecase 옵션을 가진 유저가 V를 입력해도 v와 동일하게 처리될 것이다. 방어적으로 코딩하자!
두 번 째 케이스는 noraml mode에서 characterwise motion (위 예시에서 iw, 2e 등)을 사용하는 경우이다.
마지막 케이스는 단순히 리턴만 한다. linewise(V)/blockwise(Ctrl-v)의 경우는 무시하게 되는데, 그 이유는 grep이 기본적으로 라인을 넘나들지 않기 때문이다.
두 가지 if 케이스 모두 normal! 명령어를 실행시킨다.
- 우리가 원하는 텍스트 범위를 비주얼로 선택
- mark를 선택된 범위의 가장 앞으로 이동 (
<) - characterwise visual mode 진입 (
v) - mark를 선택된 범위의 가장 끝으로 이동 (
>)
- mark를 선택된 범위의 가장 앞으로 이동 (
- 선택된 text를 복사
mark에 대해서는 지금 정확히 알 필요 없다. 이 챕터의 마지막에서 제대로 배우게 될 것.
마지막 라인ㄴ의 @@는 "unnamed" 레지스터를 얘기한다. (마지막으로 복사하거나 지운)
Escaping the Search Term
이전 장에서 사용한 것처럼 string escape 를 적용해보자.
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>
function! GrepOperator(type)
if a:type ==# 'v'
execute "normal! `<v`>y"
elseif a:type ==# 'char'
execute "normal! `[v`]y"
else
return
endif
echom shellescape(@@)
endfunctionspecial character (' 등)가 포함된 text를 visual mode로 선택한 뒤 <leader>g를 실행시켜보자. 해당 텍스트가 escaped 된 것을 볼 수 있다.
Running Grep
이제 실제 검색을 위해 grep! 명령어를 추가할 준비가 되었다. echom 줄을 다음과 같이 수정해보자.
nnoremap <leader>g :set operatorfunc=GrepOperator<cr>g@
vnoremap <leader>g :<c-u>call GrepOperator(visualmode())<cr>
function! GrepOperator(type)
if a:type ==# 'v'
execute "normal! `<v`>y"
elseif a:type ==# 'char'
execute "normal! `[v`]y"
else
return
endif
silent execute "grep! -R " . shellescape(@@) . " ."
copen
endfunction저번 장에서 사용하였던 silent execute "grep! ..." 명령어를 실행시켰다.
:source %를 한 뒤 명령어를 실행시켜보자.
완전히 새로운 vim operator를 추가하였기 때문에 다양한 방식으로 사용이 가능하다.
viw<leader>g: word를 비주얼 모드로 선택한 후 grep<leader>g4w: 앞의 네 word를 grep<leader>gt;:;전까지 grep<leader>gi[: 대괄호 안의 텍스트를 grep
Exercises
Read
:help visualmode()Read
:help c_ctrl-uRead
:help operatorfuncRead
:help map-operator
help text에 나온 space를 세는 예시
nmap <leader>cs :set operatorfunc=CountSpace<CR>g@
vmap <leader>cs :<c-u>call CountSpace(visualmode(), 1)<CR>
function! CountSpace(type, ...)
let sel_save = &selection
let &selection = "inclusive"
let reg_save = @@
if a:0 " Invoked from visual mode, use gv command."
silent execute "normal! gvy"
elseif a:type == 'line'
silent execute "normal! '[V']y"
else
silent execute "normal! `[v`]y"
endif
echomsg strlen(substitute(@@, '[^ ]', '', 'g'))
let &selection = sel_save
let @@ = reg_save
endfunction'tools > vim' 카테고리의 다른 글
| Learn Vimscript The Hard Way - 35. List (0) | 2020.04.15 |
|---|---|
| Learn Vimscript The Hard Way - Grep Operator, Part Threee (0) | 2020.04.13 |
| vim 자동완성의 끝판왕, coc.nvim (0) | 2020.04.11 |
| Learn Vimscript The Hard Way - 32. Case Study: Grep Operator, Part One - 2/2 (0) | 2020.04.10 |
| Learn Vimscript The Hard Way - 32. Case Study: Grep Operator, Part One - 1/2 (0) | 2020.04.09 |