열려진 모든 파일에서 찾기 (Vim)

:bufdo 명령

:bufdo는 버퍼 리스트의 모든 버퍼에 대해서 동일한 명령을 수행하는 기능으로, 찾기/바꾸기나 그 외에 여러 파일에 대해서 한꺼번에 적용될 수 있는 명령을 수행할 수 있다. 참고로 문자열이 아닌 일반 명령 시퀀스가 뒤에 오게 된다. 예를 들어 vim에서 열어본 모든 파일에 대해서 ‘sample’ 이라는 단어를 ‘fixed’라고 변경하려면, 모든 파일을 일일이 옮겨다닐 필요없이 :bufdo %s/sample/fixed/g를 입력하면 된다.

이와 비슷하게 :windo 명령은 모든 윈도우에 대해서 적용되며, 모든 탭에 대해서 적용할 수 있는 :tabdo 명령도 있다. (참고로 :bufdo는 버퍼 리스트 상의 모든 버퍼에 대해 적용된다. :windo는 버퍼 중에서 창을 통해 표시되는 버퍼만 해당한다.)

:bufdo 명령을 사용하면 모든 버퍼에서 특정 패턴을 검색할 수 있을 것 같다. 검색을 위한 명령을 살펴보자.

vim에서는 문자열이나 패턴을 한 번에 검색하는 명령줄 툴인 grep과 비슷한 :vimgrep 명령을 제공한다.

:vimgrep /{pattern}/ {files...}

참고로 외부 명령을 사용하여 검색하는 :grep 명령도 있다. &grepprg, &grepformat 옵션을 잘 설정하면 좀 더 나은 성능을 보여주기도 한다.

이 명령은 패턴을 /..../ 으로 감싸고 파일 목록을 전달하면 검색한 결과를 quick-fix 창에 표시한다. (실제로는 수동으로 quick-fix 창을 열어야 한다. :copen이나 :cwindow 명령을 사용) 현재 파일에서 검색하려면 파일 명을 % 으로 준다. 다음 명령은 현재 파일에서 “await”가 들어있는 라인을 모두 검색하고 quick-fix 창을 열어준다.

:vimgrep /await/ % | copen

참고 – 버퍼와 창

버퍼는 vim에서 열어놓은 파일을 의미한다. :e 명령은 파일을 열어서 버퍼로 읽어들이고 현재 창에서 해당 버퍼를 편집하겠다는 의미이다. 그럼 윈도우와 탭은 버퍼와 어떻게 다를까?

일반적인 텍스트 편집기의 경우, 파일을 열게 되면 해당 파일을 편집하기 위한 창(윈도)이 생긴다. 그리고 탭을 지원하는 편집기라면 하나의 응용 프로그램 윈도우 내에 여러개의 윈도우를 넣어서 탭으로 구분한다.

vim에도 윈도우와 탭의 개념이 있지만, 일반적인 GUI 프로그램에서 말하는 창이나 탭하고는 조금 다르다. 파일을 열면 버퍼가 생긴다. vim의 윈도우는 버퍼를 실제로 화면에 출력하는 영역이다. 즉 :e a.txt 명령은 a.txt파일을 열어서 버퍼를 만들고 해당 버퍼를 현재 윈도우에 열어준다. 이 때 :e b.txt 명령을 사용하면 b.txt 파일을 연다. 그럼 조금 전까지 편집하던 a.txt 파일은 닫혔을까? 아니다. 모든 편집 내용은 버퍼에 그대로 남아있되, 현재 윈도우가 지금 b.txt에 대한 버퍼를 출력하고 있을 뿐이다. :e a.txt라고 하면 다시 조금 전까지 편집하던 a.txt 파일의 내용을 그대로 확인할 수 있다.

:sp, :vsp 명령으로 창을 분리하면 새로운 윈도우가 만들어진다. 이 때 편집 중인 버퍼는 분할되어 새로 생성된 창에 다시 표시된다. 따라서 분할된 창에서 이전 버퍼를 열면, 두 개의 버퍼를 같이 볼 수 있는 것이다. 기본적으로 한 번 열린 파일은 모두 버퍼로 관리되고, 열려있는 버퍼는 지금 당장 창을 통해 보이지 않더라도 버퍼 리스트를 통해서 확인할 수 있다. (버퍼 리스트를 보는 화면은 :ls 명령이다.)

탭은 우리가 흔히 아는 그 탭 개념과 비슷한데, 탭은 탭 끼리 전환될 수 있으며 하나의 탭은 하나 이상의 윈도우가 화면 분할을 통해서 구성한 형태를 유지한다.

다시 정리하자면 :bufdo는 버퍼리스트의 모든 버퍼에 대해서 수행하는 작업이다. :windo는 현재 탭 내의 모든 창에 대해서 수행하는 작업이다. 즉 화면에 표시되는 모든 버퍼를 가리킨다. :tabdo는 모든 탭에 대해서 ‘현재창’으로 지정된 창에 대해서 같은 동작을 반복한다.

여러 파일/버퍼에서 하나의 quick-fix 창을 쓰기

:vimgrep 명령은 매 실행시마다 에러리스트를 초기화하기 때문에 하나의 버퍼에서 수행하는 것은 문제가 되지 않는데, :bufdo를 써서 반복 수행하면 마지막 버퍼의 결과만 남게 된다. 전체 버퍼의 찾기 결과를 하나의 창으로 설정하려면 다른 방법을 써야 한다. 이를 위해 사용할 수 있는 변형 명령이 있는데, 바로 :vimgrepadd 이다. 명령을 이용하면 :vimgrep과는 다르게 이전의 결과를 지우지 않고 계속 결과를 추가할 수 있다. 따라서 이전 실행의 결과를 수동으로 초기화해야 한다. 이는 setqflist() 함수를 사용해 quick-fix 창에 표시될 에러 목록을 변경할 수 있다는 점을 이용하자. 최종 구현은 다음과 같다.

function! FindAllBuf(item)
  call setqflist([])
  silent! exec "bufdo vimgrepadd /" . a:item . "/ %"
  copen
endfunc

command! -nargs=1 FindAll silent! call FindAllBuf(<q-args>)

이 기능을 자주 쓰게 된다면 따로 사용자 정의 명령으로 지정해두면 도움이 된다. 그런데 명령줄 입력시에 따옴표를 치지 않고 입력한 값들은 표현식으로 인식되기 때문에, 에러 없이 함수의 인자로 넘겨주기 위해서는 <args> 대신 q-를 붙인 <q-args>를 사용하도록 하자.

:lvimgrep – 위치 창으로 대신하기

quick-fix 창은 본래 컴파일-에러확인-편집 사이클을 빠르게 하기 위해 사용되는 도구로, 여러 파일의 에러/경고가 발생한 위치를 저장하는 용도로 쓰인다. 이에 비해 위치창(location window)은 윈도에 종속된 window-local의 quick-fix 창으로 이해할 수 있다. vim 명령중에 QF창을 쓰는 여러 명령들에는 앞에 l(소문자 L)이 붙어서 QF창 대신 위치 창을 쓰도록 하는 명령들이 있다. :lmake, :lgrep, :lhelpgrep, :lvimgrep등이 그러한 명령들이다. 위치 창은 각 창에 종속되며, 각각의 창은 별개의 위치창을 가질 수 있다. 또한 위치창은 QF 창과도 그 내용이 별개로 관리된다.

따라서 “현재 버퍼에서 모두 찾기” 같은 명령은 다음으로 실행할 수 있다.

:lvimgrep /keyword/ % | lwindow

함수로 만든다면 다음과 같이 작성할 수 있다. setloclist()는 특정 창의 위치 목록을 대체할 수 있게 한다. 별도로 아래와 같은 코드를 vimrc에 추가하여 현재 커서 위치의 단어를 버퍼에서 모두 찾는 명령을 만들 수 있다.

function SearchAllInBuf()
  call setloclist(0, [])
  silent! exec 'lvimgrep /' . expand('<cword>') . '/ %' | lwindow
endfunction

command! -nargs=0 FindAll call SearchAllInBuf()
nnoremap <leader>fw :FindAll<cr>