Writing a Vim plugin

Published October 11th, 2008

Vim is fairly extensible. Unlike Emacs or Eclipse, it’s just an editor, not a platform. It is, however, quite featureful and includes its own slightly eccentric domain language as well as bindings into a few others. Note: this isn’t a HOWTO, it’s just a few things to consider before jumping in.

1. Vim Script

Here is a neat lineage: the Unix line editor ed evolved into the more advanced ex which was used as the basis for the command mode in vi and extended into the roughly Turing-complete mini-language of Vim. And development continues; in his latest release, Bram Moolenaar has added native support for floating point numbers.

Vim Script supports many regular programming concepts: loops, lists, dictionaries, exceptions, etc. But the language is unconventional. Here’s some code showing the hoops one must jump through to map a bit of functionality to a key:

function! s:doSomething()
  " stuff
endfunction
 
command DoSomething :call <SID>doSomething()
nmap k :DoSomething
  • A trailing ! on function enables redefinition.
  • The s: in the function definition and the <SID> in the command declaration are a thin but tenable form of namespace management. They’ll expand to a unique name at read time so that similarly named functions in other files are not clobbered.
  • function could be replaced equivalently with fu, fun, func, etc. This follows for all Vim commands. As long as a token can uniquely complete into a keyword, it is valid.

As in many other languages, statements can be wrapped using a \ character. Unlike in those languages, in Vim Script it must appear at the beginning of the succeeding line:

if some_exceedingly_long_expression ||
   \ a_second_expression
  echo 'Success'
endif

2. Alternative plugin languages

It isn’t widely known that Vim has interfaces into several popular scripting languages: Python, Ruby, Perl, Scheme, and Tcl. These are more powerful than Vim Script but have certain practicality drawbacks in context.

  1. Debugging is difficult. Foreign code is interpreted by what is essentially one giant eval. If you misplace a close parenthesis or an end keyword, you will have to track it down yourself.
  2. Integration with Vim is slight. The calling interface is in a table below. The bindings just tunnel most editor interactions through Vim::evaluate or Vim::command (or equivalent) as unstructured string arguments.
  3. Many Vim installations don’t include external language support by default. It’s an easy fix for a user running a deb or rpm-based Linux distribution, but will require a recompile on Windows, something a Windows user is not wont to do. OS X is hit-or-miss.

Point (3) is particularly unfortunate if you’re making an extension you intend to distribute. I wrote perhaps the largest and most popular Ruby-based plugin for Vim available, yet the majority of people who contact me about it are only looking for installation help. 🙂

This is what it looks like to interact with Vim from Ruby:

# Setting options inside the editor is pretty
# straightforward.
VIM::set_option "noinsertmode"
VIM::set_option "hlsearch"
 
# ...unless you want to set an option local to a buffer.
# There's no API call for this, so we must ascend one
# layer of abstraction:
VIM::command "setlocal nowrap"
VIM::command "setlocal spell"
VIM::command "setlocal foldcolumn=0"

If we’re going to do an odd call in multiple places, it makes sense to add some glue to confine code acrobatics:

def VIM::has_syntax?
  # All return values from `evaluate` are strings, and
  # "0" evaluates to true in ruby.
  VIM::evaluate('has("syntax")') != "0"
end

See below the partial interfaces for a few languages. Obviously there’s room for improvement in the API:

Editor concept Ruby Python MzScheme
eval VIM::evaluate vim.eval (eval)
command VIM::command vim.command (command)
option VIM::set_option (get-option),
(set-option)
output VIM::message sys.stdout
buffer VIM::Buffer vim.buffers (get-next-buff),
(get-prev-buff)
window VIM::Window vim.windows (get-win-list)
current
buffer
$curbuf vim.current.buffer (curr-buff)
current
window
$curwin vim.current.window (curr-win)
range vim.current.range (range-start),
(range-end)
Manual Manual Manual

(Tcl and Perl not shown.)

For some strange reason, the Scheme interface also offers the highly situational (beep), and the Tcl interface, ::vim::beep. These do what you expect.

Interesting sidenote: these extension languages have access to window handles within Vim, providing equality semantics and therefore deterministic window management. Vim Script doesn’t seem to support this, so using an alternative language appears to offer an (inelegant) superset of functionality.

3. Choosing a language

This can be summarized like so:

Vim Script

Pros:

  • Great integrated :help system.
  • Lots of other plugins you can crib from.

Cons:

  • The language is awkward.

 

Other (Perl, Python, Ruby, Tcl, Scheme)

Pros:

  • Strong languages.
  • Experience carries over into other pursuits.

Cons:

  • Debugging is hard.
  • Interface to Vim is slight.
  • May require the user to install language libraries.
  • Syntax highlighting in a .vim file is easily confused.
  • Neglected by both plugin writers and Vim developers.

 

Using Vim Script means giving up the good stuff: closures, object-orientation, higher-order functions, reflection, and metaprogramming. So despite the extra work, selecting an alternative language is recommended for non-trivial extensions. Perhaps support within Vim will improve as more plugin writers follow this route.

Thanks to Jesse Funaro and Chris Gaal for their comments and suggestions.

Further discussion on the programming reddit


13 Responses to “Writing a Vim plugin”

  1. nf Says:

    I believe if you have Python installed on a windows machine and the interpreter in your path that Python scripting from vim works fine. At least, it’s working for and I’ve never compiled vim on windows. It’s very convenient to have a sane interpreter at your fingertips at all times.

  2. Stephen Bach Says:

    Hey, you’re right! Looks like official Vim binaries since 7.0 include Python. That really suggests Python as the language of choice if you want to trouble users of your plugin as little as possible.

  3. mb Says:

    The funny thing is: you can have kind of “poor man’s closures” in the latest Vim by means of the Dictionaries.

    Eg. something like emacs’ save-excursion becomes possible.

    function WithSavedPosition(closure)
        let pos = getpos(".")
        let r = a:closure.f()
        call setpos(".", pos)
        return r
    endfunction
    
    function ExtractSomeInfo(firstArg, secondArg)
        let closure = {'a': a:firstArg, 'b': a:secondArg}
    
        function closure.f() dict
            call DoMoveAround()
            let x = DoSomethingHereWith(self.a)
            call DoMoreMoving()
            return MoreComputation(x, self.b)
        endfunction
    
        return WithSavedPosition(closure)
    endfunction
    

    Of course these are not real closures, but to allow
    this Lisp style programming is actually very nice to
    tidy up the environment whenever you need to move,
    change a register, etc. etc.

    VimScript may be ugly, but it gets the job done.

  4. Stephen Bach Says:

    I am both scared and impressed. I have to admit I didn’t know that functions could be passed around in Vim Script.

  5. mb Says:

    It can be done via the above mentioned anonymous
    functions inside a Dictionary or a reference to a global
    function might be obtained via function.

    let MyGetpos = function("getpos")
    let pos = MyGetpos(".")
    

    This can also be used to implement object-orientation.

    function MyClassIncreaseBy(x) dict
        let self.x = self.x + a:x
    endfunction
    
    function MyClassNew(init)
        return { 'x': a:init, 'increaseBy': function("MyClassIncreaseBy") }
    endfunction
    
    let myObj = MyClassNew(3)
    " myObj.x == 3
    call myObj.increaseBy(2)
    " myObj.x == 5
    

    (Examples just for idea, don’t expect sense of content…)

    So if you miss object-orientation, you can implement it.
    And indeed, there are plugins doing that…

  6. Weekly linkdump #147 - max - блог разработчиков Says:

    […] Снова о редакторе всех времен и народов, Coming home to Vim и Writing a Vim plugin […]

  7. Adam Katz Says:

    That’s a Ukrainian back-reference, translated at http://tinyurl.com/59tjw5 (the above comment says “Again, the editor of all time and peoples, Coming home to Vim and Writing a Vim plugin” … uh, maybe that’s not a good translation, even if Google and Babelfish agree).

  8. duetsch.info : Vim - Ressourcen im Netz Says:

    […] Writing a Vim plugin […]

  9. items.sjbach.com » Blog Archive » Extensibility in Vim and Emacs Says:

    […] written previously on writing Vim plugins and using Emacs […]

  10. Information Overload » Blog Archive » Vim continued (sortof) Says:

    […] lisp that can actually be used elsewhere.  While you can configure vim using other languages, as this post indicates, it is not without its pitfalls.  Also, I found myself agreeing with posts like […]

  11. 用Vim发新浪微博 - 死拍照的 Says:

    […] Vim插件开发看这里:Writing a Vim plugin OAUTH怎么用看这里:Google OAuth 2.0 Playground […]

  12. Alessandro Molari | My switch from Vim to Emacs Says:

    […] you can already know, there are many ways to bypass the usage of VimL by using Ruby or Python (see here for more details and a Ruby/Python comparison instead of […]

  13. Alessandro Molari | My switch from Vim to Emacs (and back again) Says:

    […] you can already know, there are many ways to bypass the usage of VimL by using Ruby or Python (see here for more details and a Ruby/Python comparison instead of […]