Open Source Software Technical Articles

Want the Best of the Wazi Blogs Delivered Directly to your Inbox?

Subscribe to Wazi by Email

Your email:

Connect with Us!

Current Articles | RSS Feed RSS Feed

More Fun with Vimscript

  
  
  

In my last article, I looked at some of the ways in which you can use Vimscript, Vim's built-in scripting language, to set up that text editor to do exactly what you want it to. Apparently you liked what you saw and asked for more, so here are some additional tips and tricks to help you get Vim to jump through the hoops of your choice, including techniques for specifying ranges to work on, accepting user input, and debugging.



In the last article, we looked at functions that operate across the whole buffer, and functions that operate on a single line. At times you might want to have a function operate on a range of lines instead. There are two ways you can specify the range of lines over which a function will be applied when you call it. The first is simply to specify the range before the call to the function. This is what you do when using built-in functions like s///:



:1,.s/ping/pong/


This function replaces "ping" with "pong" for every line in the file between line 1 and the line the cursor is currently on. If I wrote a function Capitalize to capitalize the first letter of every word, I could call it with a range in the same way:



:1,.call Capitalize()


What this method actually does is call the function once for every line. Depending on what the function does, the overhead associated with this approach can be fairly high. An alternative is to call the function only once, but deal with the range handling within the function itself. To do this, use the range modifier.



Here is the single-line word count function from the previous article, adapted to apply to a range:



fu! WordsThisLine() range
let words = 0
for linenum in range(a:firstline, a:lastline)
let words += len(split(getline(linenum)))
endfor
echo words
return words
endfu


The range modifier in the first line tells the function to expect a range – though the function will also work fine if called on a single line. The for loop uses two built-in variables, a:firstline and a:lastline, which correspond to the line numbers of the first and last lines of the range. The function loops over each line in the range and adds up the total words, before echoing them and returning them.



You can use this function either by specifying a range on the command line:



:6,9call WordsThisLine()


or by highlighting a range visually and then calling the function with :call WordsThisLine().




It's also possible to write a function that calculates its own range of lines to operate on from context. You can see how to make that work in this example, which shows a function that lines up assignment operators in program code.



Interacting With the Word Under the Cursor



Another neat trick is to do something with the word currently under the cursor. For example, you could look it up in dict:



fu! Dictionary()
let dict = "dict"
let wordUnderCursor = expand("<cword>")
let command = "!" . dict . " " . wordUnderCursor
execute command
endfu


expand("<cword>") is a useful Vimscript phrase that gets the word currently under the cursor. The rest of the function just sets up a command to call !dict word; it's good practice to use variables for the commands you want to run.



You could also look up documentation – for example perldoc – for the word by substituting this line:



let dict = "perldoc -f"


Or you could call a browser. That takes a few more lines to set up properly:



fu! Dictionary()
let browser = "/path/to/browser"
let urlFormat = "http://dictionary.reference.com/browse/WORD"
let wordUnderCursor = expand("")
let url = substitute(urlFormat, "WORD", wordUnderCursor, "g")
let command = "!" . browser . " " . url
execute command
endfu


The most obvious difference here is the need to set up a particular URL format. The substitute command is Vimscript's way of running s/WORD/wordUnderCursor/g on the string
urlFormat, which gives us the correct URL to pull up in the browser.



A few notes if you want to do this:




    1. To minimize debugging, check that the command you're using works on the command line before you try to set it up in Vim.


    1. If the path to the browser contains any spaces, you need to escape them with \\, as in "/path/to/app\\ with\\ space/".


    1. On a Mac, you can simplify the first line by just using let browser = "open", which will automatically call your default browser.
19a98812-f823-48dc-841e-bf029c63c6d7



User Input



You might sometimes want user input to a script. For example, perhaps you regularly have to add a label to the start of several text lines, but the label varies. Try this script out:



fu! Label() range
call inputsave()
let label = input("Enter label: ")
call inputrestore()
for linenum in range(a:firstline, a:lastline)
let currentline = getline(linenum)
call setline(linenum, label . " " . currentline)
endfor
endfu


inputsave and inputrestore first save, then restore, the Vim typeahead buffer; this avoids any possible confusion when getting the user input. The rest of the code uses the range modifier; without it, if you called this function on a range, it would ask you to input the label for each line. getline returns a string with the contents of the given line, and setline sets the contents of the given line; here, with the input label, a space, and the existing line contents.



Debugging



Vimscript has its own integrated debugger, which may come in handy if you're writing complex functions. To debug a script, call it like this:



:debug call WordsThisLine()


At the first line, Vim will pause, and you'll see a > symbol. At this point, you can quit altogether (q), continue without stopping until the next breakpoint (c), step through the next line of code (s), execute the next command (n), or finish running the script without breakpoints (f). Note that the difference between step and next is that next skips over function calls, while step goes into the function called.



Try stepping through a function to see the output you get; broadly, if nothing highlighted red shows up, you have no errors. If you do have errors, you'll get some information about them which should help you to identify the problem.



You can put breakpoints into your code to simplify debugging:



:breakadd func 5 WordsThisBuffer


This adds a breakpoint on line 5 in the given function (note that you don't use the parentheses at the end of the function name). If you now run :debug call WordsThisBuffer() and hit c, the runthrough will stop before executing line 5.



You can also add a breakpoint in a particular file that Vim sources, with:



:breakadd file 239 myfile.txt


Note that this will only work for a command executed while sourcing that file, but not for a function defined in that file. It's more useful when debugging your ~/.vimrc (for example when defining maps or shortcuts to call functions) than it is for debugging functions themselves.



Use breakdel func 5 WordsThisBuffer to delete a breakpoint, or :breakdel * to remove all breakpoints.



Using Other Languages



Finally, if you want to do something more complicated, or if you just have a strongly preferred other language, Vim also has interfaces to other scripting languages, including Perl, Python, and Ruby. These languages, obviously, are more powerful than Vimscript, but they also have problems when it comes to using them within Vim. Possibly the biggest one is that for debugging, you're on your own; Vim will call out once to evaluate your script, rather than being able to step through it line by line if need be. Check out some notes on debugging the Perl interpreter. Along with this goes the fact that there's very little integration between Vim and the external language, so you'll need to do a lot of work by hand. You may also have to fix your Vim installation to allow external language interfaces (the Debian default package includes the Perl interpreter by default; you can also check out notes on Ruby, and a list of Vim compile options). Despite these drawbacks, if you have very complex requirements, interfacing with a scripting language is something you can experiment with.



For most purposes, however, Vimscript, tailored as it is to work with Vim, is a great tool. It gives you huge scope for extending Vim to fit your needs.




This work is licensed under a Creative Commons Attribution 3.0 Unported License
Creative Commons License.


This work is licensed under a Creative Commons Attribution 3.0 Unported License
Creative Commons License.

Comments

Currently, there are no comments. Be the first to post one!
Post Comment
Name
 *
Email
 *
Website (optional)
Comment
 *

Allowed tags: <a> link, <b> bold, <i> italics