Vim follows the UNIX philosophy of "do one thing well". Instead of trying to cram all the functionality you could ever want inside the editor itself, the right way to use Vim is to delegate to external commands when appropriate.
Let's add some interaction with the Potion compiler to our plugin to get our feet wet with external commands in Vim.
First we'll add a command to compile and run the current Potion file. There are a number of ways to do this, but we'll simply use an external command for now.
potion/ftplugin/potion/running.vim file in your plugin's repo. This
is where we'll create the mappings for compiling and running 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 chunk stores the command used to execute Potion in a global variable, if that variable isn't already set. We've seen this kind of check before.
This will allow users to override it if
potion isn't in their
putting a line like
let g:potion_command = "/Users/sjl/src/potion/potion" in
The last line adds a buffer-local mapping that calls a function we've defined
above. Remember that because this file is in the
ftdetect/potion directory it
will be run every time a file's
filetype is set to
The real functionality is in the
PotionCompileAndRunFile() function. Go ahead
and save this file, open up
factorial.pn and press
<localleader>r to run the
mapping and see what happens.
potion is in your
$PATH, the file should be run and you should see its
output in your terminal (or at the bottom of the window if you're using a GUI
Vim). If you get an error about the
potion command not being found, you'll
need to set
g:potion_command in your
~/.vimrc file as mentioned above.
Let's take a look at how
PotionCompileAndRunFile() function works.
:! command in Vim runs external commands and displays their output on the
screen. Try it out by running the following command:
Vim should show you the output of the
ls command, as well as a "Press ENTER or
type command to continue" prompt.
Vim doesn't pass any input to the command when run this way. Confirm this by running:
Type a few lines and you'll see that the
cat command spits them back out, just
as it normally would if you ran
cat outside of Vim. Use Ctrl-D to finish.
To run an external command without the "Press ENTER or type command to continue"
:silent !. Run the following command:
:silent !echo Hello, world.
If you run this in a GUI Vim like MacVim or gVim, you won't see the "Hello, world." output of the command.
If you run it in a terminal Vim, your results may vary depending on your
configuration. You may need to run
:redraw! to fix your screen after running
Note that this command is
:silent ! and not
:silent! (see the space?)!
Those are two different commands, and we want the former! Isn't Vimscript
Let's look back at the
function! PotionCompileAndRunFile() silent !clear execute "!" . g:potion_command . " " . bufname("%") endfunction
First we run a
silent !clear command, which should clear the screen without
a "Press ENTER..." prompt. This will make sure we only see the output of this
run, which is helpful when you're running the same commands over and over.
The next line uses our old friend
execute to build a command dynamically. The
command it builds will look something like this:
Notice that there's no
silent here, so the user will see the output of the
command and will have to press enter to go back to Vim. This is what we want
for this particular mapping, so we're all set.
The Potion compiler has an option that will let you view the bytecode it generates as it compiles. This can be handy if you're trying to debug your program at a very low level. Try it out by running the following command at a shell prompt:
potion -c -V factorial.pn
You should see a lot of output that looks 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 mapping that will let the user view the bytecode generated for the current Potion file in a Vim split so they can easily navigate and examine it.
First, add the following line to the bottom of
nnoremap <buffer> <localleader>b :call PotionShowBytecode()<cr>
Nothing special there -- it's just a simple mapping. Now let's sketch out the function that will do the work:
function! PotionShowBytecode() " Get the bytecode. " Open a new split and set it up. " Insert the bytecode. endfunction
Now that we've got a little skeleton set up, let's talk about how to make it happen.
There are a number of ways we could implement this, so I'll choose one that will come in handy later for you.
Run the following command:
You should see the output of the
ls command at the bottom of your screen. If
:messages you'll see it there too. The
system() Vim function takes
a command string as a parameter and returns the output of that command as
You can pass a second string as an argument to
system(). Run the following
:echom system("wc -c", "abcdefg")
Vim will display "7" (with some padding). If you pass a second argument like this, Vim will write it to a temporary file and pipe it into the command on standard input. For our purposes we won't need this, but it's good to know.
Back to our function. Edit
PotionShowBytecode() to fill out the first part of
the skeleton like this:
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
Go ahead and try it out by saving the file, running
:set ft=potion in
factorial.pn to reload it, and using the
<localleader>b mapping. Vim should
display the bytecode at the bottom of the screen. Once you can see it's working
you can remove the
Next we're going to open up a new split window for the user to show the results. This will let the user view and navigate the bytecode with all the power of Vim, instead of just reading it once from the screen.
To do this we're going to create a "scratch" split: a split containing a buffer
that's never going to be saved and will be overwritten each time we run the
mapping. Change the
PotionShowBytecode() function to look like 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
These new command should be pretty easy to follow.
vsplit creates a new vertical split for a buffer named
We surround the name with underscores to make it clearer to the user that this
isn't a normal file -- it's a buffer just to hold the output.
Next we delete everything in this buffer with
normal! ggdG. The first time
the mapping is run this won't do anything, but subsequent times we'll be reusing
__Potion_Bytecode__ buffer, so this clears it.
Next we prepare the buffer by setting two local settings. First we set its
potionbytecode, just to make it clear what it's holding. We also
buftype setting to
nofile, which tells Vim that this buffer isn't
related to a file on disk and so it should never try to write it.
All that's left is to dump the bytecode that we saved into the
variable into this buffer. Finish off the function by making 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
append() Vim function takes two arguments: a line number to append after,
and a list of Strings to append as lines. For example, try running the
:call append(3, ["foo", "bar"])
This will append two lines,
bar, below line 3 in your current
buffer. In this case we're appending below line 0, which means "at the top of
We need a list of Strings to append, but we just have a single string with
newline characters embedded in it from when we used
system(). We use Vim's
split() function to split that giant hunk of text into a list of Strings.
split() takes a String to split and a regular expression to find the split
points. It's pretty simple.
Now that the function is complete, go ahead and try out the mapping. When you
<localleader>b in the
factorial.pn buffer Vim will open a new buffer
containing the Potion bytecode. Play around with it by changing the source,
saving the file, and running the mapping again to see the bytecode change.
:help :read and
:help :read! (we didn't cover these commands, but
they're extremely useful).
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?
PotionShowBytecode() function to detect when the Potion compiler
returns an error, and show an error message to the user.
Each time you run the bytecode mapping a new vertical split will be created, even if the user hasn't closed the previous one. If the user doesn't bother closing them they could end up with many extra windows stacked up.
PotionShowBytecode() to detect with a window is already open for the
__Potion_Bytecode__ buffer, and when that's the case switch to it instead of
creating a new split.
You'll probably want to read
:help bufwinnr() for this one.
More Extra Credit
Remember how we set the
filetype of the temporary buffer to
syntax/potionbytecode.vim file and define syntax highlighting for
Potion bytecode buffers to make them easier to read.