Coding With Fun
Home Docker Django Node.js Articles Python pip guide FAQ Policy

Vimscript external command


May 24, 2021 Vim


Table of contents


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.

Compile

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.

Bang!

:! 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 bytecode is displayed

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.

system()

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

Play a draft on the split

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.

Practice

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.

Add questions

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.

Extra bonus questions

Remember when we set the filetype buffer potionbytecode Create syntax/potionbytecode.vim and define syntax highlights for Potion bytecodes, making them easier to read.