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(@@)
endfunction
special 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-u
Read
:help operatorfunc
Read
: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 |