vim 플러그인 만들기
간단한 vim 플러그인을 직접 만들어보고 싶을 때 참고하면 좋을 자료
vim에서 ‘어떤 기능을 추가’하는 것은 크게 세 가지 정도로 구현됩니다. 먼저 일련의 반복적인 동작을 하나의 키 맵으로 정의하는 것이 있습니다. 두 번째로 사용자 정의명령을 만들어서 vim의 명령줄 명령이나 스크립트를 손쉽게 실행하는 방법이 있습니다. 세 번째로는 사용자 정의 함수를 작성하여, 보다 복잡한 기능을 구현하는 방법이 있습니다.
그런데 이 방법들은 단독으로 사용되기 보다는 함께 사용되는 경향이 있습니다. 복잡한 기능을 구현하기 위해서는 vimscript로 사용자 정의 함수를 사용하지만, :call MySpecialFunction() 과 같은 식으로 직접 함수를 호출하는 명령을 입력해서 사용하는 일은 거의 없습니다. 대부분은 간단한 이름의 사용자 정의 명령으로 함수를 호출하거나, 약속된 키 맵을 따로 사용하는 경우가 대부분이죠.
어떤 새로운 기능을 구현했을 때, 함수와 명령, 키 맵을 모두 정의한다면 이것들은 하나의 세트를 이루게 됩니다. 이럴 때, 이 세트만 별도의 파일로 만들어 둘 수 있다면 나의 다른 컴퓨터나 다른 사람의 컴퓨터에도 손쉽게 설치가 가능할 것입니다. 즉, 하나의 새로운 vim 플러그인을 만들 수 있는 것이죠.
요즘 인기 있는 플러그인들은 모두 플러그인 관리자를 통해서 설치하는 경우가 많습니다만, 간단한 플러그인의 경우에는 파일 하나에 기능을 모두 담을 수 있을 겁니다.
vim 플러그인은 기본적으로 런타임 디렉토리 중 하나의 plugin 디렉토리 아래에 있는 .vim 파일입니다. 여기에 플러그인 파일을 작성해두면 vim은 시작될 때 플러그인 디렉토리를 모두 뒤져서 플러그인들을 읽어들입니다.
그러면 지난 글에서 소개했던 스크립트를 플러그인 형식으로 만들어서 사용하는 방법에 대해 살펴보겠습니다.
런타임 디렉토리
vim의 기능과 동작에 영향을 주는 여러 설정들은 ‘런타임 디렉토리’에 저장됩니다. 런타임 디렉토리는 다시 시스템 런타임 디렉토리와 사용자 런타임 디렉토리로 나뉩니다. 시스템 런타임 디렉토리는 vim이 설치되어 있는 디렉토리이고, 사용자 런타임 디렉토리는 보통 사용자 홈 아래에 /.vim, %USERPROFILE%\vimfiles 이런 경로로 있습니다. vim에서 :echo $MYVIMDIR을 실행해서 확인할 수 있습니다.
런타임 디렉토리는 다시 아래의 구조를 가집니다. 모든 디렉토리를 가지고 있을 필요는 없습니다.
~/.vim/
├── autoload/ # 자동으로 로드되는 함수들
├── colors/ # 컬러 스키마 파일들
├── compiler/ # 컴파일러 설정
├── doc/ # 도움말 문서
├── ftplugin/ # 파일타입별 플러그인
├── indent/ # 파일타입별 들여쓰기 설정
├── keymap/ # 키보드 매핑
├── lang/ # 다국어 메시지
├── plugin/ # 일반 플러그인
├── syntax/ # 문법 강조 파일
└── tutor/ # Vim 튜터리얼
몇 가지 디렉토리에 대해 설명하면 다음과 같습니다.
- ftplugin : 특정한 타입의 파일이 열렸을 때, 파일 타입과 이름이 같은 스크립트가 실행됩니다.
- plugin : vim이 시작될 때 로딩될 플러그인 파일들을 이 곳에 보관합니다.
- autoload : 선택적으로 로딩해도 되는 함수들은 이곳에 작성합니다. 함수가 호출될 때 로딩하여, vim의 초기 로딩 속도를 빠르게 하는데 도움이 됩니다.
플러그인의 기능을 키맵으로 사용하기
플러그인에서 제공하는 함수를 매번 :call ... 명령으로 호출하는 것은 불편합니다. 그래서 이를 쉽게 호출할 수 있는 다른 방법이 필요합니다.
하나는 :command 명령을 사용해서 사용자 정의 명령을 하나 만들고 이를 실행할 수 있게 하는 것이고, 다른 하나는 키맵을 정의하는 것입니다.
그런데 이 두 가지 방법 모두 주의깊게 생각해야 할 부분이 있습니다. 명령이름이나 키맵은 다른 기능과 언제든지 충돌할 수 있는 가능성이 있습니다. 일반적으로는 두 가지 방법을 적절하게 절충하여 사용합니다.
- 사용자 정의 명령을 제공하는 경우, 사용자 정의 명령은 플러그인 이름을 접두어로 붙여서 사용합니다. 그리고 사용자 정의 명령의 경우에는 추가 인자를 지정하여 다양한 동작을 수행하는 경우에 사용하기 좋습니다.
- 키맵은 실제 키보드의 키에 대해서는 맵을 만들지 않고 가상 키맵을 만듭니다. 그리고 필요한 경우 사용자가 해당 가상 키맵으로 연결되는 실제 키맵을 각자 정의하여 사용합니다.
아래의 코드를 먼저 보겠습니다. 이 코드는 다른 글에서도 잠깐 소개했던, 이전에 검색한 패턴에 매치되는 모든 내용을 새로운 버퍼로 추출하는 기능을 구현한 것입니다.
vim9script
def ExtractSearchedText()
if @/ == ''
echo "There is no search pattern"
return
endif
var temp = @a
@a = ''
silent! :%s//\=setreg('A', submatch(0), 'al')/gn
if len(@a) > 0
new
put! a
normal gg
else
echo 'No matched'
endif
@a = temp
nohls
redraw
enddef
# nnoremap <Plug>(extract-searched) <Cmd>call <SID>ExtractSearchedText()<CR>
# 이 키맵은 좀 더 세련되게 아래와 같이 표현합니다.
nnoremap <Plug>(extract-searched) <ScriptCmd>call ExtractSearchedText()<CR>
맨 아래에 스크립트에서 작성한 함수를 호출하는 키 맵을 정의했습니다.
<Plug>라는 이름으로 시작하는 키맵이 가상 키맵입니다.<Plug>는 일반적인 키보드로는 입력할 수 없는 특정한 문자열로 대체되고, 입력된 경우에는 오른쪽의 키 시퀀스로 변경됩니다.<Cmd>는 :를 입력하여 명령줄 모드로 전환한다는 것을 좀 더 고상하게 표현한 것입니다.<ScriptCmd>는 명령어에서 참조하는 함수의 범위를 현재 스크립트로 제한합니다. 기존에는<SID>라는 접두어를 함수 이름 앞에 붙였으나, 이 기능을 사용하면 코드를 좀 더 깔끔하게 유지할 수 있습니다.- 함수 이름 앞에
를 붙인 것은 플러그인이 로드될 때 결정되는 스크립트ID로 치환됩니다. 따라서 다른 스크립트 파일에 같은 이름의 함수가 있더라도 겹치지 않습니다.
이 파일을 (이름은 아무거나 상관 없습니다.) plugin 디렉토리에 두고 vim을 다시 시작하면 플러그인이 로드됩니다. 기능을 호출하기 위해서는 $MYVIMRC 파일에 다음 키 맵핑을 추가합니다. 이 때 키 맵은 자신이 원하는 맵핑을 쓰면 됩니다.
nmap <nowait> <leader>cc <Plug>(extract-searched)
중복 로딩 방지
plugin 디렉토리 하위에 다시 서브 디렉토리를 두고 파일을 구성하는 것도 가능합니다. 이 경우 디렉토리 구조 내에서 같은 이름의 파일이 여러 번 들어갈 수도 있습니다. 이 때에는 가장 먼저 로드되는 1개 파일만 로드됩니다.
ftplugin 디렉토리는 이와 다르게 이름이 같은 파일이 발견되면 누적하여 로드되며, 반복해서 로딩될 수도 있습니다. 중복 로딩을 방지하기 위해서, 보통은 다음의 테크닉이 사용됩니다.
vim9script
# 한 번 로드된 적이 있는지를 체크
if exists('g:blahblahblah')
finish
endif
g:blahblahblah = 1
Pack
패키지 기능은 vim8에 도입된 것으로, 서드 파티 플러그인 관리자 없이도 외부 플러그인들을 쉽게 설치할 수 있게 해줍니다. dist/, local/로 로컬에서 개발하는 플러그인과 다른 곳에서 배포된 플러그인을 구분하고, 다시 각각의 디렉토리는 start/, opt/ 로 구분하여 시작 시 로드될 것과 선택적으로 로드할 것들을 구분합니다.
이 아래에 각 디렉토리별로 구분된 플러그인들은 독립적으로 런타임 디렉토리 구조를 가질 수 있습니다. 따라서 git 명령으로 적절한 위치에 플러그인 저장소를 복제해두면 설치해서 사용할 수 있게끔 되어 있습니다.
~/.vim/ # 사용자 런타임 디렉토리 (Unix/Linux)
├── pack/ # 패키지 디렉토리 (Vim 8+)
│ ├── dist/ # 서드파티 플러그인용
│ │ ├── start/ # Vim 시작 시 자동 로드
│ │ │ ├── nerdtree/ # 각 플러그인 디렉토리
│ │ │ └── fugitive/
│ │ └── opt/ # :packadd로 선택적 로드
│ │ ├── coc/
│ │ └── youcompleteme/
│ │
│ └── local/ # 로컬 개발 플러그인용
│ ├── start/ # 자동 로드
│ │ └── myplugin/
│ └── opt/ # 선택적 로드
│ └── myoptionalplugin/
├── autoload/ # 필요할 때 로드되는 함수들
│ └── myutil/ # 네임스페이스로 사용되는 하위 디렉토리
│
├── ftdetect/ # 파일타입 감지 스크립트
│
├── ftplugin/ # 파일타입별 설정
│ ├── python.vim # Python 전용 설정
│ ├── javascript.vim # JavaScript 전용 설정
│ └── ruby/ # 디렉토리로도 구성 가능
│
└── plugin/ # 일반 플러그인
├── settings.vim # 전역 설정
└── mappings.vim # 키 매핑