Learn Vimscript The Hard Way - 53. Autoloading
Autoloading
이제 우리는 우리의 Potion 플러그인에 꽤 많은 기능을 추가하였다. 마치기 전에 중요한 기능을 더 추가하여 더 빛나게 만들어보자.
먼저 우리는 autoloading 기능을 추가하여 플러그인을 더 효율적으로 만들어 볼 것이다.
How Autoload Works
지금은 유저가 플러그인을 로드하면 (Potion 파일을 열면) 모든 기능이 한 번에 로드된다. 우리의 플러그인은 아직 작기 때문에 큰 문제가 되지 않지만, 큰 플러그인의 경우 모든 코드를 로드하는 데에는 꽤 많은 시간이 소요된다.
vim은 "autoload"를 사용하여 이 문제를 해결한다. Autoload는 코드 로딩을 실제로 필요할 때로 지연시킨다. 전체적으로는 별로 큰 퍼포먼스 향상이 이뤄지지 않을 수 있지만, 사용자가 당신의 플러그인의 모든 코드를 다 사용하지 않는다면 꽤 큰 속도 향상이 이뤄질 수 있다.
어떻게 동작하는지 알아보자. 다음 명령어를 살펴보자.
:call somefile#Hello()
해당 명령어를 실행할 때, vim은 일반 함수가 실행될 때와는 조금 다르게 동작한다.
만약 해당 함수가 이미 로드되었다면 vim은 단순히 이를 호출한다.
그렇지 않다면 vim은 ~/.vim
디렉토리(와 Pathogen, plug 등의 디렉토리)의 autoload/somefile.vim
파일을 찾아서 호출한다.
해당 파일이 존재하면, vim은 해당 파일을 load/source한다. 그리고 함수 호출을 시도한다.
파일 안에 함수는 다음과 같이 정의되어야 한다.
function somefile#Hello()
" ...
endfunction
subdirectory를 명시하기 위해 함수명에 #
를 여러 번 사용할 수 있다.
:call myplugin#somefile#Hello()
위 명령어는 autoload/myplugin/somefile.vim
파일을 찾아 autoload한다. 해당 파일 안에는 다음과 같이 경로가 구성되어야 한다.
function myplugin#somefile#Hello()
" ...
endfunction
Experimenting
어떻게 동작하는지 느끼기 위해 한 번 시도해보자. ~/.vim/autoload/example.vim
파일을 만들고 다음을 추가해보자.
echom "Loading..."
function! example#Hello()
echom "Hello, world!"
endfunction
echom "Done loading."
파일을 저장하고 :call example#Hello()
를 호출해보자. 다음 내용이 출력되는 것ㅇ르 볼 수 있다.
Loading...
Done loading.
Hello, world!
이는 다음과 같은 내용을 보여준다.
- vim은 그때그때 확인하면서
example.vim
을 호출한다. 이는 vim 이 열렸을 때에는 존재하지 않았기 때문에 시작할 때에는 로드되지 않는다. - vim이 autoload가 필요할 때, 함수를 호출하기 전에 전체 파일을 로드한다.
"Vim을 닫지 말고," 함수의 정의를 다음과 같이 변경해보자.
echom "Loading..."
function! example#Hello()
echom "Hello AGAIN, world!"
endfunction
echom "Done loading."
파일을 저장하고 "Vim을 닫지 말고" :call example#Hello()
를 호출해보자. 다음이 출력될 것이다.
Hello, world!
vim은 이미 example#Hello
함수를 알고 있기 때문에 이를 다시 로드하지 않는다.
- 함수 밖의 코드는 다시 실행되지 않는다.
- 함수의 변경사항을 적용시키지 않는다.
이제 :call example#BadFunction()
을 실행해보자. 에러가 나타나기 전에 loading message가 출력되는 것을 볼 수 있다. 다시 :call example#Hello()
를 실행해보자. 업데이트 된 메세지가 출력되는 것을 볼 수 있다!
Loading...
Done Loading.
E117: 모르는 함수: example#BadFunction
이제 vim이 autoload-style 함수를 호출 할 때 어떤 일이 발생하는지 대략적으로 알게 되었을 것이다.
- 해당 이름의 함수가 이미 정의되었는지 확인한다. 만약 그렇다면, 이를 호출한다.
- 그렇지 않으면 함수 이름을 기반으로 파일을 찾아 source 시킨다.
- 그리고 함수 호출을 시도한다. 작동하면 잘 된 것이고, 실패하면 에러 메세지를 출력한다.
만약 개념이 완전히 잡히지 않았다면 다시 돌아가서 몇가지 실험을 해 보고 어떻게 작동하는지 이해해보자.
What to Autoload
Autoloading은 공짜가 아니다. 코드에 이상한 함수 이름이 사용되는 것은 물론이고 (조금의) 오버로드도 있다.
유저가 vim 세션을 열 대마다 사용하는 플러그인이 아니라면 함수를 최대한 autoload 파일로 옮기는 것이 좋다. 이는 당신의 플러그인이 사용자의 startup 시간(사용자가 많은 vim plugin을 설치하는 데 아주 중요한)에 미치는 영향을 감소시켜준다.
그럼, 어떤 종류의 것들이 안전하게 autoload 될 수 있을까? 답은 기본적으로 유저가 직접 호출하지 않는 모든 것이다. 매핑이나 custom command는 autoload 될 수 없다. (유저가 직접 호출하면 autoload는 작동하지 않기 때문에?)
우리의 potion plugin을 살펴보고 어떤 것들이 autoload 될 수 있는지 살펴보자.
Adding Autoloading to the Potion Plugin
먼저 complie and run 기능부터 시작할 것이다. 저번 장에서 작업한 ftplugin/potion/running.vim
파일은 다음과 같다.
if !exists("g:potion_command")
let g:potion_command = "potion"
endif
function! PotionCompileAndRunFile()
silent !clear
execute "!" . g:potion_command . " " . bufname("%")
endfunction
function! PotionShowBytecode()
" Get the bytecode.
let bytecode = system(g:potion_command . " -c -V " . bufname("%"))
" Open a new split and set it up.
vsplit __Potion_Bytecode__
normal! ggdG
setlocal filetype=potionbytecode
setlocal buftype=nofile
" Insert the bytecode.
call append(0, split(bytecode, '\v\n'))
endfunction
nnoremap <buffer> <localleader>r :call PotionCompileAndRunFile()<cr>
nnoremap <buffer> <localleader>b :call PotionShowBytecode()<cr>
이 파일은 Potion 파일이 로드되면 이미 한 번 호출되기 때문에 vim이 시작될 때 따로 오버헤드를 주지는 않는다. 하지만 몇몇 사용자들은 이 기능이 필요없을 수 있기 때문에 이를 autoload로 변경하여 Potion 파일을 열 때 마다 몇 밀리세컨드 정도를 절약해 줄 수 있다.
그렇다. 이 케이스에서는 큰 절약이 이뤄지지는 않는다. 하지만 로드되는데 아주 많은 시간이 필요한 몇천줄 짜리 코드에서는 효과가 훨씬 큰 것을 상상할 수 있다.
autoload/potion/running.vim
파일을 열고 아래 두 함수를 이름을 바꿔 옮겨보자.
echom "Autoloading..."
function! potion#running#PotionCompileAndRunFile()
silent !clear
execute "!" . g:potion_command . " " . bufname("%")
endfunction
function! potion#running#PotionShowBytecode()
" Get the bytecode.
let bytecode = system(g:potion_command . " -c -V " . bufname("%"))
" Open a new split and set it up.
vsplit __Potion_Bytecode__
normal! ggdG
setlocal filetype=potionbytecode
setlocal buftype=nofile
" Insert the bytecode.
call append(0, split(bytecode, '\v\n'))
endfunction
potion#running
부분은 해당 파일이 존재하는 위치에 따라 상대적이라는 것을 기억하라. 이제 ftplugin/potion/running.vim
파일에서 함수를 제거하고 다음과 같이 수정해보자.
if !exists("g:potion_command")
let g:potion_command = "potion"
endif
nnoremap <buffer> <localleader>r
\ :call potion#running#PotionCompileAndRunFile()<cr>
nnoremap <buffer> <localleader>b
\ :call potion#running#PotionShowBytecode()<cr>
파일을 저장하고, vim을 닫고 factorial.pn
파일을 열어보자. <localleader>b
, <localleader>r
매핑을 사용하여 여전히 잘 작동하는지 확인해보자.
우리가 추가한 Autoloading...
메세지가 잘 나오는지 확인해보자. 첫 매핑을 사용할 때만 나올 것이다. (메세지를 보려면 :messages
명령어를 실행해야 할 것이다.) 해당 메세지가 잘 나오고 autoload가 잘 동작하는 것을 확인한 이후에 해당 메세지를 제거해주자.
위에서 본 것처럼, 우리는 nnoremap
은 그대로 남겨두었다. 이를 autoloading 시켜버리면 유저가 해당 함수를 initiate 할 방법이 없기 때문이다!
이는 vim plugin들에서 쉽게 볼 수 있는 흔한 패턴이다. 함수의 많은 부분은 autoloaded 함수로 구성되 어 있고, nnoremap
이나 command
명령어들만 vim이 매번 로드하는 파일에 남아있다. vim 플러그인을 작성할 때마다 이를 기억하자.
Exercises
Read
:help autoload
.Experiment a bit and find out how autoloading variables behaves.
아래와 같이 경로를 변수로 설정하여 사용해도 된다. 긴 서브디렉토리를 중복으로 사용할 때 이런 방식으로 사용하면 좋을듯.
let runningFile = "potion#running"
nnoremap <buffer> <localleader>r
\ execute ":call " . runningFile . "PotionCompileAndRunFile()<cr>"
- Suppose you wanted to programatically force a reload of an autoload file Vim has already loaded, without bothering the user. How might you do this? You may want to read
:help :silent
. Please don't ever do this in real life.