In the tutorial Basic Vim Configuration, I showed you the basics of configuring a Vim editor. Now it is time to learn more. VimScript is a full programming language with which you can create any type of automation program that you might need. This tutorial will take your knowledge to the next level with a project directory structure creator, exploring Autocommands, and a look at the Maximum-Awesome extension for MacVim.
Autocommands are commands to have Vim perform an action when a particular condition applies: new file creation, writing to a file, and so forth.
It is a great way to automate some of the work you need to do in a project. For example, when you save a SASS file, an autocommand can compile it to normal CSS for you.
The format for an autocommand is:
autocmd <action> <regexp> <VimScript Commands>
<action> is the editor action that the command should wait for. You can also specify more than one action at a time by separating them with a comma.
With 86 possible
<actions> to hook onto, I’ll not describe them all here. To see the full list, you can type
:help autocmd-events-abc. This opens a different buffer with the full list. For the demonstration program, I will use the
BufNewFile action to run the function to check for and create a project directory folder structure.
<regexp> is a regular expression for the file name to action upon.
<VimScript Command> is the actual command in VimScript to execute when the
<action> happens that matches the
<regexp>. It needs to start with a colon, :, such as you would see in the status line of the editor.
Since I do a lot of website development, the helper function will create the needed project structure for a new website. Therefore, the autocommand needs to trigger on HTML file creation. The needed format is:
function! CreateProject( file ) echo "Saving " a:file endfunc autocmd BufNewFile *.html :call CreateProject(expand('%:p'))
The function for the project is just the skeleton. As the tutorial progresses, I’ll add more to this function.
With this in the
.vimrc file, you will see the file name echoed to the status bar when you open a new HTML file in a buffer. You can do this by using the
:new <name> command to create a new buffer with the given file name, or when you open Vim with a file name on the command line.
expand('%:p') macro gives the full path to the new file when the autocommand triggers. That way, the function
CreateProject will get the name of the file, and it’s directory.
This works fine for simply opening and closing Vim without ever reloading the configuration file. Sometimes, however, you’ll want to tweak the configuration file and reload it without exiting. Vim will then perform the
autocmd multiple times per new buffer. That could cause a mess.
You need, therefore, to put the
autocmd in to an
augroup for Autocomand Group. Then clear out the group before adding a new command. The
autocmd line becomes:
augroup CreateProject autocmd! autocmd BufNewFile *.html :call CreateProject(expand('%:p')) augroup END
autocmd! tells Vim to forget what was in this group before. Use this block instead of the autocmd line in the last listing. This is good practice when writing autocmds.
With the script so far created loaded in to Vim, re-open Vim with this commandline:
vim test.html. The picture above is what you will get. It will give the path to the new file. If you do the same thing, but with a file that already exists, you will not get this prompt in the bottom line.
VimScript has more than just variables. You can also use lists. A list is a grouping of object to one variable. List are great for holding related information. In the case of creating a file structure, there will be two lists: list of directories to create and a list of files to create.
You specify a list with the
. Therfore, create two lists like this:
let dirs=["/js", "/js/final", "/css", "/css/final", "/doc"] let files=["/js/base.js", "/css/base.css", "/doc/help.html"]
This creates two lists: one called
dirs and the other ‘files’. If you want to inspect what is in a list, you can reference it with an index. Remember, VimScript has zero based indexing. To get the first item, you have to use the index of zero. Once this is in your
.vimrc file and you reload it, you can see them with this:
This will echo “/js/base.js” to the status line. You can also reference the values starting from the right most value with a negative index. Therefore,
will echo “/css/base.css” to the status line. You can even get a sub-group of elements like this:
This will cause the string
['/css/base.css', '/doc/help.html'] to show in the status line. A sub-set of a set is still a set!
I had the two list definitions outside the function just for demonstration purpose. It is best not to create variables in the global scope (ie: outside of a function). Having a lot of global variable makes the global scope cluttered. It can also clash with other scripts down the road.
Program Flow Control
In order to process each element in the lists, I will use a
for-loop structure. A
for-loop repeatedly executes the enclosed code until a condition happens. To loop over each item in the list, the code becomes this:
function! CreateProject( file ) let dirs=["/js", "/js/final", "/css", "/css/final", "/doc"] let files=["/js/base.js", "/css/base.css", "/doc/help.html"] for dir in dirs endfor for name in files endfor endfunc autocmd BufNewFile *.html :call CreateProject(expand('%:p'))
The two lists are now inside the function and two
for loops iterate through each list. Note, there is a new variable before the
in statement and the list after the
in statement. The new variable acquires each element in the array sequentually.
There is another looping structure in VimScript called the
while-loop. It’s basic form is:
let index = 0 while index < len(dirs) echo l:dirs[index] l:index += 1 endwhile
This code loops over each item in the
dirs list and echos it to the status line. Notice the new variable
index. This variable used to track the number of times gone thourgh the loop and to sequentially get each item in the list. For working with lists, the
for-loop is more effecient.
Finishing the Function
With the data structures and loops in place, I will now finish out the code. The rest of the code looks like this:
function! CreateProject( file ) let dirs=["/js", "/js/final", "/css", "/css/final", "/doc"] let files=["/js/base.js", "/css/base.css", "/doc/help.html"] let parent = fnamemodify(a:file,":p:h") for dir in dirs let newdir = l:parent . dir if !isdirectory(l:newdir) call mkdir( l:newdir ) endif endfor for name in files let newfile = l:parent . name if !filereadable(newfile) call system("touch " . l:newfile) endif endfor endfunc autocmd BufNewFile *.html :call CreateProject(expand('%:p'))
I added a new variable
parent that is the parent directory structure of the file given to the function. The command
fnamemodify allows you to extract different parts of a path across platforms. You can read more about it using the Vim help system:
:help fnamemodify(). Notice, to reference the
file variable passed to the function I had to scope it with the
a: scope refers to the arguments.
In each loop, I created a variable that concats the parent directory with the new directory or file name. I then check for their existence and create them if they don’t exist. To check for existence, the
isDirectory() function checks for the directory, and
filereadable() checks for file existance. I use a
! to negate the return value from the functions. For creating directories, you call the
mkdir() command. To create blank files, you have to use the system command
Also, notice inside each loop and if statement I had to scope the variables that are in the upper scopes with the
l: scope. The
l: scope refers to anywhere in the function definition. Lower scopes do not automatically inherit the upper scopes! This catches me all the time.
Now that you are getting in to heavy use with Vim, Maximum-Awesome is a setup package that loads many great defaults and packages for using Vim on the Mac. It is Ruby on Rails centric, but is useful for any type of programming.
Open the termial program to a directory you want to install the package. Then type these commands:
git clone https://github.com/square/maximum-awesome.git cd maximum-awesome rake
When it completes, then open Vim again and you will see a nice working environment.
There are a lot of interesting packages in this setup. Have fun exploring!
Now that you know how to create automation scripts to help your workflow, go ahead and experiment with other helpful functions.
When you personalize a Vim environment, you will fill more in control and able to do more. Remember, practicing what you learn is the only way to improve and remember it. So, go ahead and practice making your own automation scripts in VimScript!