External Commands - part two
해당 장도 두 포스트로 나누어서 작성하였다. 1부를 읽고 읽기를 권장한다.
Displaying Bytecode
Potion 컴파일러는 컴파일 되었을 때 생성되는 bytecode를 볼 수 있게 해주는 옵션을 제공한다. 이는 아주 low-level에서 디버깅을 하기 쉬워진다. 쉘에서 다음 명령어를 실행해보자.
$ potion -c -V factorial.pn
-- parsed --
code ...
-- compiled --
; function definition: 0x109d6e9c8 ; 108 bytes
; () 3 registers
.local factorial ; 0
.local print_line ; 1
.local print_factorial ; 2
...
[ 2] move 1 0
[ 3] loadk 0 0 ; string
[ 4] bind 0 1
[ 5] loadpn 2 0 ; nil
[ 6] call 0 2
...
쉽게 볼 수 있게 현재 vim에 스플릿된 창에 현재 potion 파일이 생성하는 bytecode를 띄워주는 매핑을 추가해보자.
먼저, ftplugin/potion/running.vim
의 가장 아래에 다음을 추가하자.
nnoremap <buffer> <localleader>b :call PotionShowBytecode()<cr>
특별할 것 없는 그냥 매핑이다. 이제 함수를 스케치해보자.
function! PotionShowBytecode()
" Get the bytecode.
" Open a new split and set it up.
" Insert the bytecode.
endfunction
이제 기본적인 구조는 갖춰졌다. 어떻게 실행할지에 대해 이야기해보자.
system()
아주 많은 방법들이 있지만, 가장 간단한 방법을 사용할 것이다.
다음 명령어를 실행시켜보자.
:echom system("ls")
스크린의 아래에 ls
명령어의 결과물이 나올 것이다. :messages
명령어를 실행시켜도 결과물을 볼 수 있다. system()
함수는 명령어 스트링을 인자로 받아서 해당 결과물을 string으로 반환해주는 함수이다.
system()
함수의 두 번째 인자를 넘길 수 있다. 다음을 실행시켜보자.
:echom system("wc -c", "abcdefg")
vim은 (패딩이 추가된) 7
을 반환할 것이다. 만약 두 번째 인자가 추가되면 vim은 temporary 파일을 만들어서 이를 standard input으로 넘겨준다. 우리의 목적에는 별로 맞지 않지만, 알아두면 좋을 것이다.
다시 우리 함수로 돌아와 PotionShowBytecode()
를 수정해보자.
function! PotionShowBytecode()
" Get the bytecode.
let bytecode = system(g:potion_command . " -c -V " . bufname("%"))
echom bytecode
" Open a new split and set it up.
" Insert the bytecode.
endfunction
파일을 저장하고 factorial.pn
에서 :set ft=potion
을 실행시킨 후 <localleader>b
매핑을 실행시켜보자. 스크린 아래에 bytecode를 출력하는 것을 볼 수 있을 것이다.
Scratch Splits
이제 새 창을 열어서 해당 결과를 유저에게 보여줄 것이다. 이는 사용자가 bytecode를 그냥 스크린으로 읽는 것이 아니라 강력한 vim으로 navigate 할 수 있게 해 줄 것이다.
이를 위해 우리는 "scratch" split을 만들 것이다: 이는 절대 저장되지 않고 매번 매핑이 실행될 때마다 덮어써지는 버퍼를 가진 split을 말한다. PotionShowBytecode()
를 다음과 같이 수정해보자.
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.
endfunction
vsplit
은 __Potion_Bytecode__
라는 이름을 가진 버퍼를 vertical split에 띄워준다. 우리는 이 이름에 던더(__
)를 넣어서 보통 파일이 아니라는 것을 알기 쉽게 하였다. 이는 그냥 컨벤션이다.
다음으로, normal! ggdG
를 사용해서 버퍼에 있는 모든 내용을 지웠다. 처음 매핑이 실행되면 아무것도 없을테지만, __Potion_Bytecode__
를 다시 사용하면 이를 모두 지워야 한다.
그리고 두 로컬 세팅을 추가하였다. 첫 번째는 filetype을 potionbytecode
로 하는 것이다. 또한 buftype
을 nofile
로 하여 vim이 해당 버퍼가 disk의 파일과는 전혀 상관이 없고, 저장하지 않게 해 주었다.
이제 bytecode
변수로 저장한 bytecode를 버퍼에 쓰는 것만 남았다. 함수를 다음과 같이 완성시켜보자.
function! PotionShowBytecode()
" Get the bytecode.
let bytecode = system(g:potion_command . " -c -V " . bufname("%") . " 2>&1")
" 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
append()
함수는 두가지 인자를 받는다: 추가할 줄 넘버와, 추가할 String의 리스트이다. 이해를 위해서 다음 명령어를 실행시켜보자.
:call append(3, ["foo", "bar"])
3번째줄 이후에 foo
, bar
가 각각 4번째, 5번째 줄로 추가된 것을 볼 수 있다. 0을 인자로 사용하면 "파일의 가장 맨 위"에 추가된다.
우리는 추가할 스트링의 리스트가 필요하기 때문에 vim의 split()
함수를 사용하여 텍스트를 리스트로 쪼갰다. split()
은 텍스트와 정규표현식을 인자로 받는다.
함수가 완성되었으니 이를 저장하고 매핑을 사용해보자. <localleader>b
를 factorial.pn
에서 사용하면 새 버퍼를 열어서 Potion bytecode를 반환할 것이다.
Exercises
Read
:help bufname
.Read
:help buftype
.Read
:help append()
.Read
:help split()
.Read
:help :!
.Read
:help :read
and:help :read!
(we didn't cover these commands, but they're extremely useful).Read
:help system()
.Read
:help design-not
.Currently our mappings require that the user save the file themselves before running the mapping in order for their changes to take effect. Undo is cheap these days, so edit the functions we wrote to save the current file for them.
What happens when you run the bytecode mapping on a Potion file with a syntax error? Why does that happen?
Change the PotionShowBytecode() function to detect when the Potion compiler returns an error, and show an error message to the user.
Extra Credit
이전 창이 열려 있음에도 불구하고 매번 bytecode mapping을 실행할 때마다 새로운 vertical split이 생성된다. 창을 닫지 않는다면 아주 많은 윈도우가 쌓일 것이다.
PotionShowBytecode()
를 변경하여 이미 __Potion_Bytecode__
버퍼가 윈도우에 열려있 새로운 split을 생성하는 대신 기존 윈도우를 사용하게 해보자.
:help bufwinnr()
를 읽을 필요가 있을것이다.
More Extra Credit
우리의 임시 버퍼의 filtype
을 potionbytecode
로 설정한 것을 기억할 것이다. 이를 더 읽기 쉽게 하기 위해서 syntax/potionbytecode.vim
파일을 만들고 몇몇 syntax highlighting을 추가해보자.
External Commands - part two
해당 장도 두 포스트로 나누어서 작성하였다. 1부를 읽고 읽기를 권장한다.
Displaying Bytecode
Potion 컴파일러는 컴파일 되었을 때 생성되는 bytecode를 볼 수 있게 해주는 옵션을 제공한다. 이는 아주 low-level에서 디버깅을 하기 쉬워진다. 쉘에서 다음 명령어를 실행해보자.
$ potion -c -V factorial.pn
-- parsed --
code ...
-- compiled --
; function definition: 0x109d6e9c8 ; 108 bytes
; () 3 registers
.local factorial ; 0
.local print_line ; 1
.local print_factorial ; 2
...
[ 2] move 1 0
[ 3] loadk 0 0 ; string
[ 4] bind 0 1
[ 5] loadpn 2 0 ; nil
[ 6] call 0 2
...
쉽게 볼 수 있게 현재 vim에 스플릿된 창에 현재 potion 파일이 생성하는 bytecode를 띄워주는 매핑을 추가해보자.
먼저, ftplugin/potion/running.vim
의 가장 아래에 다음을 추가하자.
nnoremap <buffer> <localleader>b :call PotionShowBytecode()<cr>
특별할 것 없는 그냥 매핑이다. 이제 함수를 스케치해보자.
function! PotionShowBytecode()
" Get the bytecode.
" Open a new split and set it up.
" Insert the bytecode.
endfunction
이제 기본적인 구조는 갖춰졌다. 어떻게 실행할지에 대해 이야기해보자.
system()
아주 많은 방법들이 있지만, 가장 간단한 방법을 사용할 것이다.
다음 명령어를 실행시켜보자.
:echom system("ls")
스크린의 아래에 ls
명령어의 결과물이 나올 것이다. :messages
명령어를 실행시켜도 결과물을 볼 수 있다. system()
함수는 명령어 스트링을 인자로 받아서 해당 결과물을 string으로 반환해주는 함수이다.
system()
함수의 두 번째 인자를 넘길 수 있다. 다음을 실행시켜보자.
:echom system("wc -c", "abcdefg")
vim은 (패딩이 추가된) 7
을 반환할 것이다. 만약 두 번째 인자가 추가되면 vim은 temporary 파일을 만들어서 이를 standard input으로 넘겨준다. 우리의 목적에는 별로 맞지 않지만, 알아두면 좋을 것이다.
다시 우리 함수로 돌아와 PotionShowBytecode()
를 수정해보자.
function! PotionShowBytecode()
" Get the bytecode.
let bytecode = system(g:potion_command . " -c -V " . bufname("%"))
echom bytecode
" Open a new split and set it up.
" Insert the bytecode.
endfunction
파일을 저장하고 factorial.pn
에서 :set ft=potion
을 실행시킨 후 <localleader>b
매핑을 실행시켜보자. 스크린 아래에 bytecode를 출력하는 것을 볼 수 있을 것이다.
Scratch Splits
이제 새 창을 열어서 해당 결과를 유저에게 보여줄 것이다. 이는 사용자가 bytecode를 그냥 스크린으로 읽는 것이 아니라 강력한 vim으로 navigate 할 수 있게 해 줄 것이다.
이를 위해 우리는 "scratch" split을 만들 것이다: 이는 절대 저장되지 않고 매번 매핑이 실행될 때마다 덮어써지는 버퍼를 가진 split을 말한다. PotionShowBytecode()
를 다음과 같이 수정해보자.
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.
endfunction
vsplit
은 __Potion_Bytecode__
라는 이름을 가진 버퍼를 vertical split에 띄워준다. 우리는 이 이름에 던더(__
)를 넣어서 보통 파일이 아니라는 것을 알기 쉽게 하였다. 이는 그냥 컨벤션이다.
다음으로, normal! ggdG
를 사용해서 버퍼에 있는 모든 내용을 지웠다. 처음 매핑이 실행되면 아무것도 없을테지만, __Potion_Bytecode__
를 다시 사용하면 이를 모두 지워야 한다.
그리고 두 로컬 세팅을 추가하였다. 첫 번째는 filetype을 potionbytecode
로 하는 것이다. 또한 buftype
을 nofile
로 하여 vim이 해당 버퍼가 disk의 파일과는 전혀 상관이 없고, 저장하지 않게 해 주었다.
이제 bytecode
변수로 저장한 bytecode를 버퍼에 쓰는 것만 남았다. 함수를 다음과 같이 완성시켜보자.
function! PotionShowBytecode()
" Get the bytecode.
let bytecode = system(g:potion_command . " -c -V " . bufname("%") . " 2>&1")
" 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
append()
함수는 두가지 인자를 받는다: 추가할 줄 넘버와, 추가할 String의 리스트이다. 이해를 위해서 다음 명령어를 실행시켜보자.
:call append(3, ["foo", "bar"])
3번째줄 이후에 foo
, bar
가 각각 4번째, 5번째 줄로 추가된 것을 볼 수 있다. 0을 인자로 사용하면 "파일의 가장 맨 위"에 추가된다.
우리는 추가할 스트링의 리스트가 필요하기 때문에 vim의 split()
함수를 사용하여 텍스트를 리스트로 쪼갰다. split()
은 텍스트와 정규표현식을 인자로 받는다.
함수가 완성되었으니 이를 저장하고 매핑을 사용해보자. <localleader>b
를 factorial.pn
에서 사용하면 새 버퍼를 열어서 Potion bytecode를 반환할 것이다.
Exercises
Read
:help bufname
.Read
:help buftype
.Read
:help append()
.Read
:help split()
.Read
:help :!
.Read
:help :read
and:help :read!
(we didn't cover these commands, but they're extremely useful).Read
:help system()
.Read
:help design-not
.Currently our mappings require that the user save the file themselves before running the mapping in order for their changes to take effect. Undo is cheap these days, so edit the functions we wrote to save the current file for them.
normal! :w
를 함수에 추가하였다.What happens when you run the bytecode mapping on a Potion file with a syntax error? Why does that happen?
** Syntax error before text "factorial = (n):"
에러 메세지가 반환된다.Change the
PotionShowBytecode()
function to detect when the Potion compiler returns an error, and show an error message to the user.
이미 에러 메세지가 반환되는데 왜 수정하라고 하는건지 잘 모르겠지만..
Extra Credit
이전 창이 열려 있음에도 불구하고 매번 bytecode mapping을 실행할 때마다 새로운 vertical split이 생성된다. 창을 닫지 않는다면 아주 많은 윈도우가 쌓일 것이다.
PotionShowBytecode()
를 변경하여 이미 __Potion_Bytecode__
버퍼가 윈도우에 열려있 새로운 split을 생성하는 대신 기존 윈도우를 사용하게 해보자.
:help bufwinnr()
를 읽을 필요가 있을것이다.
다음 줄을 추가해주었다.
" Open a new split and set it up
let bytecode_window_number = bufwinnr("__Potion_Bytecode__")
if bytecode_window_number != -1
execute bytecode_window_number . "wincmd w"
else
vsplit __Potion_Bytecode__
endif
More Extra Credit
우리의 임시 버퍼의 filtype
을 potionbytecode
로 설정한 것을 기억할 것이다. 이를 더 읽기 쉽게 하기 위해서 syntax/potionbytecode.vim
파일을 만들고 몇몇 syntax highlighting을 추가해보자.
'tools > vim' 카테고리의 다른 글
Learn Vimscript The Hard Way - 54. Documentation (0) | 2020.05.10 |
---|---|
Learn Vimscript The Hard Way - 53. Autoloading (0) | 2020.05.09 |
Learn Vimscript The Hard Way - 52. External Commands - part one (0) | 2020.05.07 |
Learn Vimscript The Hard Way - 51. Potion Section Movement - part two (0) | 2020.05.06 |
Learn Vimscript The Hard Way - 51. Potion Section Movement part 1 (0) | 2020.05.05 |