Scroll to top

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

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:

1
2
autocmd <action> <regexp> <VimScript Commands>

where <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.

The <regexp> is a regular expression for the file name to action upon.

The <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:

1
2
function! CreateProject( file )
3
	echo "Saving " a:file
4
endfunc
5
6
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.

The 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:

1
2
augroup CreateProject
3
	autocmd!
4
	autocmd BufNewFile *.html :call CreateProject(expand('%:p'))
5
augroup END

The 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.

Autocmd TriggeredAutocmd TriggeredAutocmd Triggered

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.

Data Structures

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:

1
2
let dirs=["/js", "/js/final", "/css", "/css/final", "/doc"]
3
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:

1
2
:echo files[0]

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,

1
2
:echo files[-2]

will echo “/css/base.css” to the status line. You can even get a sub-group of elements like this:

1
2
:echo files[1:2]

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:

1
2
function! CreateProject( file )
3
	let dirs=["/js", "/js/final", "/css", "/css/final", "/doc"]
4
	let files=["/js/base.js", "/css/base.css", "/doc/help.html"]
5
	
6
	for dir in dirs
7
8
	endfor
9
	
10
	for name in files
11
12
	endfor
13
endfunc
14
15
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:

1
2
let index = 0
3
while index < len(dirs)
4
	echo l:dirs[index]
5
	l:index += 1
6
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:

1
2
function! CreateProject( file )
3
	let dirs=["/js", "/js/final", "/css", "/css/final", "/doc"]
4
	let files=["/js/base.js", "/css/base.css", "/doc/help.html"]
5
	let parent = fnamemodify(a:file,":p:h")
6
7
	for dir in dirs
8
		let newdir = l:parent . dir
9
		if !isdirectory(l:newdir)
10
			call mkdir(  l:newdir )
11
		endif
12
	endfor
13
14
	for name in files
15
		let newfile = l:parent . name
16
		if !filereadable(newfile)
17
			call system("touch  " . l:newfile)
18
		endif
19
	endfor
20
endfunc
21
22
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:. 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 touch.

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.

Maximum-Awesome

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:

1
2
git clone https://github.com/square/maximum-awesome.git
3
cd maximum-awesome
4
rake

When it completes, then open Vim again and you will see a nice working environment.

MacVim with Maximum-AwesomeMacVim with Maximum-AwesomeMacVim with Maximum-Awesome

There are a lot of interesting packages in this setup. Have fun exploring!

Conclusion

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!</name></regexp></action></vimscript></regexp></actions></action>

Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.