May 24, 2021 Vim
Vim follows UNIX's philosophy of "doing one thing and doing it well." Rather than trying to integrate the functionality you might want into the editor itself, it's better to use Vim to invoke external commands when appropriate.
Let's add some commands that interact with the Potion compiler to the plug-in to give you a taste of the way external commands are called inside Vim.
First we'll add a command to compile and execute the current Potion file. There are many ways to do this, but let's do it simply with external commands for the time being.
Create a
potion/ftplugin/potion/running.vim
This will be where we create the mapping of the Compile and Execute Potion files.
if !exists("g:potion_command")
let g:potion_command = "potion"
endif
function! PotionCompileAndRunFile()
silent !clear
execute "!" . g:potion_command . " " . bufname("%")
endfunction
nnoremap <buffer> <localleader>r :call PotionCompileAndRunFile()<cr>
The first part stores the commands used to execute Potion code as global variables, if not set. We've seen similar checks before.
If
potion
not in the
$PATH
this will allow the user to overwrite it, for example, by adding a line like
let g:potion_command = "/Users/sjl/src/potion/potion"
at .vimrc.
~/.vimrc
The last line adds a buffalo-local map to call the function we defined earlier.
Don't forget that since this file
ftdetect/potion
it is executed every time a
filetype
for a file is set to
potion
The place where the functionality
PotionCompileAndRunFile()
Save the
factorial.pn
press
<localleader>r
to perform this mapping to see what happens.
If
potion
under your $PATH and
$PATH
code is executed, you should see the output at the terminal (or at the bottom of the window, if you're using GUI vim).
If you see an error
potion
find the potion command, you'll need to set up g:30 within the
~/.vimrc
g:potion_command
Let's take
PotionCompileAndRunFile()
works.
:!
Commands (read "bang") execute external commands and display their output on the screen.
Try to execute the following command:
:!ls
Vim outputs the
ls
the ls command, with the prompt "Please continue with ENTER or other commands."
When this is performed, Vim does not pass any input to external commands. To verify, perform:
:!cat
Break into some lines and you'll see
cat
command spit them all back, just as you did cat outside
cat
Press Ctrl-D to end.
To execute an external command and avoid
请按 ENTER 或其它命令继续
with the prompt to
:silent !
Execute the following command:
:silent !echo Hello, world.
If you perform under GUI Vim such as MacVim or gVim, you will not
Hello,world.
.
the output of .
If you perform under the terminal, the results you see depend on your configuration. O
nce you have executed a
:silent !
you may need to execute
:redraw!
to refresh the screen again.
Note that this command
:silent !
I
nstead
:silent!
(
See the space?) )
! T
hese are two different commands, we want the former!
Vimscript is wonderful, isn't it?
PotionCompileAndRun()
function! PotionCompileAndRunFile()
silent !clear
execute "!" . g:potion_command . " " . bufname("%")
endfunction
First we execute a
silent !clear
to empty the screen output and avoid prompting.
This will ensure that we only see the output of this command, which you will find useful if you execute the same command over and over again.
On the next line we use old
execute
to dynamically create a command.
The established command looks similar to:
!potion factorial.pn
Note that there is
silent
here, so the user will see the command output and will have to press enter to return to Vim.
That's what we want, so that's it.
The Potion compiler has an option to display the bytecode generated by it. T his will help if you are trying to debug at a very low level. In the shell, execute the following command:
potion -c -V factorial.pn
You'll see a whole bunch of output like this:
-- 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
...
Let's add a map that allows users to see the bytecode generated by the current Potion code under the new Vim split, making it easier for them to browse and test the output.
First, add
ftplugin/potion/running.vim
nnoremap <buffer> <localleader>b :call PotionShowBytecode()<cr>
There's nothing special here -- just a simple mapping. Now sketch out the approximate framework of the function:
function! PotionShowBytecode()
" Get the bytecode.
" Open a new split and set it up.
" Insert the bytecode.
endfunction
Now that a framework has been established, let's make it a reality.
There are many different ways to do this, so I chose the relatively convenient one.
Execute the following command:
:echom system("ls")
You should see the output of the
ls
bottom of the screen. I
f you execute
:message
you can also see them.
The Vim
system()
accepts a string command as an argument and returns the output of that command as a string.
You can pass another string as an argument to
system()
Follow these commands:
:echom system("wc -c", "abcdefg")
Vim will
7
(and some others). I
f you pass the second argument like this, Vim writes it to the temporary file and enters it into the command through the pipeline as standard input.
We don't need this feature at the moment, but it's worth knowing.
Back to our function.
Edit
PotionShowBytecode()
fill the first part of the frame:
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
Save the
factorial.pn
:set ft=potion
reload, and try it out using
<lovalleader>b
V
im displays bytecodes at the bottom of the screen.
Once you see it execute successfully, you can remove
echom
Next we'll open a new split to show the results to the user. This will allow users to browse bytecodes with the full functionality of Vim, rather than just flashing across the screen.
To do this, we'll create a "draft" split: a split that includes a buffer that is never saved and is overwritten every time the mapping is performed.
Change
PotionShowBytecode()
function to this:
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
The new commands should be well understood.
vsplit
__Potion_Bytecode__
split called a file. W
e wrapped the name with an underscore to make the user notice that this is not an ordinary file (it just shows the buffer of the output).
Underscore is not a special use, it's just convention.
Then we use
normal! ggdG
everything in the buffer.
This is not necessary the first time this mapping is performed, but then we
__Potion_Bytecode__
buffer, so we need to empty it.
Next we set two local settings for this buffer. F
irst we set its file type to
potionbytecode
to indicate its purpose.
We also
buftype
nofile
Vim that the buffer is not related to the files on the disk so that it does not write the buffer.
Finally, there is only a
bytecode
we save in the bytecode variable into the buffer.
Complete the function and make it look like this:
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
The Vim
append()
accepts two parameters: a line number that will be attached and a list of strings that will be attached by line.
For example, try the following command:
:call append(3, ["foo", "bar"])
This will attach two lines,
foo
bar
after the third row of your current buffer.
This time we'll add it after line 0 at the beginning of the representing file.
We need a list of strings to attach, but we only have a single string from
system()
call that includes line breaks. W
e use Vim's
split()
function to split this large piece of text into a list of strings.
split()
accepts a string to be split and a regular expression to find the split point.
It's really simple.
Now that the function is complete, try the corresponding mapping. W
hen you
factorial.pn
in the
<localleader>b
Vim opens a new buffer that includes Potion bytecode.
Modify the Potion source code, save the files, and perform a mapping to see what the difference will be.
Read
:help bufname
.
Read
:help buftype
.
Read:
:help append()
Read
:help split()
Read
:help :!
.
Read
:help :read
and
:help :read!
(We didn't talk about these commands, but they work very well.)
Read
:help system()
Read
:help design-not
.
Currently, our plug-ins require users to manually save files before performing mapping to make their changes work. Undoing today has become so easy that you modify written functions to save them automatically.
What happens if you perform this bytecode mapping on a Potion file with syntax errors? Why?
Modify
PotionShowBytecode()
detect if the Potion compiler returned an error and output an error message to the user.
Each time you perform a bytecode mapping, a new vertical split is created, even if the user does not close the last one. If users do not close these windows repeatedly, they will end up trapped by a large number of additional windows.
Modify
PotionShowBytecode()
__Potion_Bytecode__
buffer is open and, if so, switch to it instead of creating a new split.
You probably want
:help bufwinnr()
for help.
Remember when we set the
filetype
buffer
potionbytecode
Create
syntax/potionbytecode.vim
and define syntax highlights for Potion bytecodes, making them easier to read.