vim에서 파이썬이나 자바스크립트 코드를 작성하고 바로 실행하는 가장 쉬운 방법은 :!python %
과 같이 외부 명령을 바로 호출하는 것이다. 이 경우 vim은 잠시 숨겨지고 해당 명령을 실행하는 상태로 화면이 전환된다.
보다 IDE와 비슷한 느낌(느낌 알잖아요…)이 중요하다면 vim내에서 창을 나누고 그 창에서 실행 결과를 보는 방법이 있다. vim8에서 도입된 job 기능을 사용하여 백그라운드에서 해당 프로그램이 실행되면 그 결과를 새 버퍼로 받아서 보여주는 방법도 있고, 또 아예 :term
명령으로 해당 파일을 여는 방법도 있다.
새로운 vim 스크립트 파일을 하나 생성하고 아래와 같이 작성해보자.
" job이 끝났을 때 알려주는 콜백
function! s:callback_exit(jobid, status) abort
call popup_notification((a:status == 0 ? 'Done' : 'Error'), {})
endfunction
function! s:run_python_file() abort
" 파이썬이 아니면 실행하지 않는다.
if &l:filetype !~# 'python' | return | endif
" 새 버퍼를 만들기
let b:outname = 'out_' . expand('%:t:r')
exec 'belowright new ' . b:outname
" new 명령으로 새 창을 만들었으며, 현재는 새창에 있는 상태
" 새 버퍼 설정
let l:temp = bufnr()
setl buftype=nofile nobuflisted nobackup bufhidden=wipe
nmap <buffer><silent> <Esc> :<c-u>hide<CR>
" 이전 창으로 돌아가기
exec bufwinnr(bufnr('#')) . 'wincmd w'
" job 실행
let b:outbf = l:temp
let b:job = job_start(['python', expand('%:p')]->join(' ' ), #{
\out_io: 'buffer',
\out_buf: b:outbf,
\exit_cb: function('s:callback_exit')})
" 결과창으로 이동하기
exec b:outbf . 'wincmd w'
endfunction
" 실행명령 정의
command! -nargs=0 Test call <SID>run_python()
위 vim 스크립트를 파일로 저장한 후, so %
명령으로 로딩한다. 이후 파이썬 파일을 열고 :Test
를 실행하면 창이 하나 분할되고, 해당 파이썬 프로그램에서 출력한 내용이 버퍼에 담기게 된다.
해설
코드가 동작하는 순서는 다음과 같다.
- 현재 파일 이름에서 ‘out_’을 앞에붙이고 확장자를 뗀 이름만 사용해서 버퍼의 이름을 정한다. 이 이름은 해당 버퍼 범위의 변수로 만드는데, 반복 실행될 때 유지하기 위해서이다.
belowright new 버퍼이름
명령을 실행해서 새 버퍼를 아래나 오른쪽에 만든다.:new
명령으로 새 버퍼를 만들면, 그 시점부터 이후 스크립트가 그 새로운 버퍼에서 실행되는 것처럼 간주된다. 따라서bufnr()
함수는 새 버퍼의 번호를 리턴하며,b:
스코프를 가진 변수들은 모두 새 버퍼의 것을 따른다.- 새 버퍼를 설정한다. 파일로 기록되지 않는
nofile
타입의 버퍼 타입을 지정하고,nobuflisted
를 설정하여 버퍼 목록에 보이지 않도록 한다. 또bufhidden=wipe
옵션을 주어 버퍼가 화면에서 사라지면 제거되도록 한다. - 새 버퍼에서 Esc 키를 누르면 닫히도록 (
:hide
) 키맵을 정의한다. 이 때<buffer>
옵션을 사용해서 해당 버퍼에서만 사용되도록 한다. - 현재 버퍼의 번호를 함수 로컬 스코프 범위에 저장한다. 이렇게하면 원래 버퍼로 돌아갔을 때, 현재 버퍼의 번호를 사용할 수 있다.
- 파이썬 파일인 원래 버퍼는 현 시점에서
'#'
로 참조된다.bufwinnr()
함수를 사용하여 이전 버퍼의 창 번호를 알 수 있다. 참고로winbufnr()
,bufwinnr()
함수가 엄청 헷갈리는데buf->winnr()
로 이해하면 된다. 버퍼를 창번호로 변환한다고 이해할 수 있다. {winnr}wincmd w
명령을 사용하면 해당 창으로 이동할 수 있다.wincmd 명령
은 노멀모드에서<c-w> 명령
과 동일하다.- 다시 소스 버퍼로 넘어왔으면, 여기서 현재 파일 경로를 파이썬에게 넘겨서 백그라운드 작업을 시작한다. 이 때 job의 옵션에
out_io
키를'buffer'
로 주면 버퍼의 입력을 job의 출력과 연결할 수 있다. - 작업 시작한 후 다시 출력 버퍼로 이동한다.
재사용이 용이한 버전으로 개선
이렇게 작성한 함수를 사용자 정의 명령으로 호출하면 파이썬 파일을 실행하고 그 출력값을 새 버퍼에 표시할 수 있다. 문제는 이 명령이 :new
를 사용하기 때문에 호출할 때마다 새 버퍼를 계속해서 만든다는 점이다. (쉽게 닫기 위해서 Esc키를 맵핑하긴 했지만…)
따라서 재호출시에 이전 실행 결과창이 열려있으면 닫도록 하는 명령을 추가해보도록 하자. 이전에 작성한 코드중에서는 소스 버퍼로 돌아왔을 때, let b:out_bf = l:temp
를 통해서 출력창의 버퍼 번호를 소스 버퍼 범위 변수에 저장했다. 이 값을 검사해서 창을 닫는다.
function! s:run_python_file() abort
" 파이썬이 아니면 실행하지 않는다.
if &l:filetype !~# 'python' | return | endif
" 이전에 만든 결과 버퍼가 있으면 닫기
if exists('b:bf_out') && winbufnr(b:bf_out) != -1
exec b:bf_out . 'wincmd q'
" 새 버퍼를 만들기
....
입력이 가능한 버전
이상의 코드는 출력 결과만을 버퍼로 보여준다. 만약 파이썬 스크립트가 input()
함수를 써서 키보드 입력을 받는다면, 이 방법은 사용할 수 없다. 이 때는 :new
명령대신 :term
명령을 써서 새 터미널 창을 열면 된다. 단 몇 가지 설정에 주의할 것이 있다.
- 터미널 창을 열 때, 인자로 실행될 명령을 주고, 대신
job_start()
함수를 사용하지 않는다. - 터미널 버퍼는 숨겨질 때 제거되어야 하므로
setl bufhidden=delete
로 옵션을 준다. - 터미널에서의 입력모드 (터미널모드)에서 빠져나올 필요가 있을 때
<Esc>
키를 누를 수 있도록 맵핑을 추가해준다. - 터미널이 종료될 때 콜백을 실행하고 싶다면, 터미널 버퍼에서
term_getjob('%')
을 사용해서 해당 터미널의 job id를 얻어서,job_setoptions()
함수에서exit_cb
옵션을 지정해주면 된다.
function! s:run_python_term() abort
" 파이썬 파일일 때만 실행
if &l:filetype !~# 'python' | return | endif
" 이전 실행창이 열려있으면 닫기
if exists('b:buf_output') && bufwinnr(b:buf_output) != -1
exec bufwinnr(b:buf_output) . 'wincmd q'
endif
let b:cmd_args = ['python', expand('%:p')]
" 터미널 실행 및 설정
exec 'belowright term ' . b:cmd_args->join(' ')
" now on out_buffer
let l:temp = bufnr() " 터미널 버퍼 번호
setl nobackup nobuflisted bufhidden=delete
" 터미널 입력 중 Esc를 눌러서 노멀모드로 빠져나오기
tnoremap <buffer><silent> <Esc> <c-\><c-n>
nnoremap <buffer><silent> <Esc> :<c-u>hide<CR>
nnoremap <buffer><silent> q :<c-u>hide<CR>
let l:termjob = term_getjob('%')
" 원본버퍼로 돌아가서 b:buf_output 을 설정해준다.
exec bufwinnr(bufnr('#')) . 'wincmd w'
let b:buf_output = l:temp
call job_setoptions(l:termjob, #{
\exit_cb: function('s:callback_exit')})
" 터미널로 복귀
exec bufwinnr(b:buf_output) . 'wincmd w'
endfunction
명령 및 맵핑을 버퍼 범위로 지정하고, 위 코드를 ftplugin/python.vim 등에 기록해두면 파이썬 파일을 열 때마다 맵핑이 지정되어 호출할 수 있다. 이전에 소개했던 간단한 맵핑과는 달리, 이를 통해서 작성된 코드를 vim을 벗어나지 않고 분할창에서 실행할 수 있는 기능을 사용할 수 있다.
실제로 이 방식은 작성할 코드를 터미널에서 실행할 명령만 구성하면 자바스크립트나 그외 어떤 다른 언어에 대해서도 동일한 기능을 구성할 수 있기에 얼마든지 확장하여 사용할 수도 있을 것이다. 개인적으로는 autoload 폴더 속에 실제 분할 및 터미널 실행 코드를 함수로 작성해두고, 실행할 명령과 콜백들을 ftplugin 폴더에 파일 타입별로 정의하고, 버퍼별 맵핑을 지정하여 사용하고 있다.