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///:
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:
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:
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.
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 commandendfu
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 commandendfu
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 stringurlFormat, which gives us the correct URL to pull up in the browser.
A few notes if you want to do this:
let browser = "open"
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) endforendfu
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.
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.
:debug call WordsThisBuffer()
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.
breakdel func 5 WordsThisBuffer
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.
Allowed tags: <a> link, <b> bold, <i> italics