vim에서 파이썬이나 자바스크립트 코드를 작성하고 바로 실행하는 가장 쉬운 방법은 :!python %
과 같이 외부 명령을 바로 호출하는 것이다. 이 경우 vim은 잠시 숨겨지고 해당 명령을 실행하는 상태로 화면이 전환된다.
보다 IDE와 비슷한 느낌(느낌 알잖아요…)이 중요하다면 vim내에서 창을 나누고 그 창에서 실행 결과를 보는 방법이 있다. vim8에서 도입된 job 기능을 사용하여 백그라운드에서 해당 프로그램이 실행되면 그 결과를 새 버퍼로 받아서 보여주는 방법도 있고, 또 아예 :term
명령으로 해당 파일을 여는 방법도 있다.
새로운 vim 스크립트 파일을 하나 생성하고 아래와 같이 작성해보자.
function! s:run_script() abort
" 현재 버퍼가 파이썬 파일이 아니면 실행하지 않는다.
if &l:filetype !~# 'python'
return
endif
" 이전에 실행해서 열어둔 결과창이 있으면 닫는다.
let commands = ['python', expand('%:p')->fnameescape()]
if exists('b:outbufnr') && bufwinnr(b:outbufnr) > -1
exec bufwinnr(b:outbufnr) . 'hide'
endif
" 빈 새 버퍼를 만들고, 그 번호를 outbufnr 에 기억해둔다.
let b:outbufnr = bufadd('')
let temp = bufnr()
" 새 버퍼를 분할창에 표시한다. 이제 새 버퍼로 옮겨온 상태이고, 버퍼를 설정한다.
exec 'belowright sbuffer ' .. b:outbufnr
let b:srcbufnr = temp
setlocal buftype=nofile nobuflisted bufhidden=wipe noswapfile nowritebackup
nnoremap <buffer><silent> <Esc> <Cmd>hide<CR>
" job을 실행하고 결과를 버퍼에 표시하도록 한다.
let jobid = job_start(commands, #{
\out_io: 'buffer',
\out_buf: bufnr(),
\})
endfunction
" 키 맵을 설정해놓는다.
nnoremap <Plug>(run_python) <Cmd> call <SID>run_script()<CR>
nmap <F7> <Plug>(run_python)
위 vim 스크립트를 파일로 저장한 후, so %
명령으로 로딩한다. 이후 파이썬 파일을 열고 <F7>
키를 누르면 아래쪽으로 창이 열리고 출력되는 값들이 여기에 표시된다.
해설
코드가 동작하는 순서는 다음과 같다.
- 현재 버퍼의 정보를 사용해서 실행할 명령을 미리 생성해 놓고 이를
commands
변수에 저장한다. - 만약 이전에도 이 파일에 대해서 실행을 한 적이 있다면
b:outbufnr
이라는 변수가 있을 것이다. 이 변수에 대한 창이 보이고 있는 상태라면 닫아야 한다.bufwinnr()
함수는 특정 버퍼를 출력하고 있는 창 번호를 리턴하는데, 이 값이 0보다 작으면 버퍼를 표시하는 창이 없다는 의미이다. 그렇지 않은 경우에는{창번호}hide
를 실행해서 창을 가린다. 뒤에서 설정하는 내용이 있지만, 결과 창은 숨겨질 때 그 버퍼의 내용이 제거된다. addbuf('')
함수를 통해서 새 버퍼를 백그라운드에서 만들고 해당 버퍼 번호를 얻을 수 있다. 이를b:outbufnr
에 저장해둔다.b:
로 시작하는 변수들은 모두 특정 버퍼에서만 통용된다.- 새로 만든 버퍼를 화면에 보이게하려면
sbuffer {bufnr}
명령을 사용하면 된다. 아래쪽 기준으로 분할창을 만들기 위해서belowright
와 함께 사용한다. - 버퍼를 분할하고나면 새 버퍼창으로 진입한 상태가 된다. 여기서
setlocal
을 사용해서 버퍼에 대한 몇 가지 설정을 한다. 이 버퍼는 저장할 용도가 아닌 단순한 출력용의 창이다.bufhidden=wipe
를 설정해두면 버퍼가 보이지 않게 되었을 때 내용을 제거한다는 의미이다. - 새 버퍼에서 Esc 키를 누르면 닫히도록 (
:hide
) 키맵을 정의한다. 이 때<buffer>
옵션을 사용해서 해당 버퍼에서만 사용되도록 한다. - 다시 소스 버퍼로 넘어왔으면, 여기서 현재 파일 경로를 파이썬에게 넘겨서 백그라운드 작업을 시작한다. 이 때 job의 옵션에
out_io
키를'buffer'
로 주면 버퍼의 입력을 job의 출력과 연결할 수 있다. - 작업 시작한 후 다시 출력 버퍼로 이동한다.
입력이 가능한 버전
이상의 코드는 출력 결과만을 버퍼로 보여준다. 따라서 적절한 파일 타입에 대해서, commands
값만 잘 세팅한다면 다른 종류의 스크립트의 실행이나, 컴파일 결과 등을 결과창에서 볼 수 있게 만들 수 있다.
하지만 이 방법은 ‘출력’만을 담고 있는 창을 만들게 된다. 만약 파이썬 스크립트가 input()
함수를 써서 키보드 입력을 받는다면, 이 방법은 사용할 수 없다. 실제 IDE가 하는 것처럼 실행창에서 값을 직접 입력할 수 있는 형태로 구현할 수는 없을까? vim 8부터는 아예 터미널 창을 만들 수 있게도 되었으니, 이를 사용하겠다. 터미널 창은 백그라운드에서 job을 실행하면서 이 프로세스의 표준입출력을 버퍼와 연결하는 것이다.
- 터미널 창을 열 때, 인자로 실행될 명령을 주고, 대신
job_start()
함수를 사용하지 않는다. - 터미널 버퍼는 숨겨질 때 제거되어야 하므로
setl bufhidden=delete
로 옵션을 준다. - 터미널에서의 입력모드 (터미널모드)에서 빠져나올 필요가 있을 때
<Esc>
키를 누를 수 있도록 맵핑을 추가해준다. - 터미널이 종료될 때 콜백을 실행하고 싶다면, 터미널 버퍼에서
term_getjob('%')
을 사용해서 해당 터미널의 job id를 얻어서,job_setoptions()
함수에서exit_cb
옵션을 지정해주면 된다.
function! s:run_in_terminal() abort
if &l:filetype !~# 'python' || exists('b:srcbufnr')
return
endif
" close output window that is preivously opened
let commands = ['python', expand('%:p')->fnameescape()]
if exists('b:outbufnr')
exec bufwinnr(b:outbufnr) . 'hide'
endif
let temp = bufnr()
let b:outbufnr = term_start(commands)
" now you are in new buffer
let b:srcbufnr = temp
setlocal nobuflisted bufhidden=delete noswapfile nowritebackup
nnoremap <buffer><silent> <Esc> <C-\><C-n>:<C-u>hide<CR>
endfunction
명령 및 맵핑을 버퍼 범위로 지정하고, 위 코드를 ftplugin/python.vim 등에 기록해두면 파이썬 파일을 열 때마다 맵핑이 지정되어 호출할 수 있다. 이전에 소개했던 간단한 맵핑과는 달리, 이를 통해서 작성된 코드를 vim을 벗어나지 않고 분할창에서 실행할 수 있는 기능을 사용할 수 있다.
실제로 이 방식은 작성할 코드를 터미널에서 실행할 명령만 구성하면 자바스크립트나 그외 어떤 다른 언어에 대해서도 동일한 기능을 구성할 수 있기에 얼마든지 확장하여 사용할 수도 있을 것이다. 개인적으로는 autoload 폴더 속에 실제 분할 및 터미널 실행 코드를 함수로 작성해두고, 실행할 명령과 콜백들을 ftplugin 폴더에 파일 타입별로 정의하고, 버퍼별 맵핑을 지정하여 사용하고 있다.