Extensibility in Vim and Emacs

Published April 7th, 2009

Emacs and Vim both provide facility for extension. But as they represent divergent philosophies — Vim following the “small is beautiful” and “do one thing well” precepts of Unix, Emacs coming from a belief that the editor is an operational hub — they have different objectives here.

This is a comparison article with a focus on plugin development. The following articles offer more general comparisons of the editors, and are worth reading:

I’ve written previously on writing Vim plugins and using Emacs Lisp.

1. Development resources

For me, programming has never been a sit-down-and-go activity. It’s more like traditional writing: I think about what it is I want to say, do a little fact checking, and then find a way to say it. Programming is perhaps easier than writing, though, because the mechanism and the medium share the same space, so your tools can meet you half way.

i. Integrated help

The :help command in Vim is not just for new users; it’s also a great boon for experienced users. Each section and subsection of Vim’s extensive documentation includes one or more descriptive keywords. These appear in the :help command’s completion list to make it easier to find what you are looking for, even when you don’t know exactly what it is.

Imagine we wish to know the syntax for ignoring character case in a regex. Typing “:help ignore<TAB>” shows:

:help ignore
/ignorecase          filetype-ignore      +wildignore
'ignorecase'         g:netrw_ignorenetrc  'eventignore'
ignore-errors        'foldignore'         'noignorecase'
efm-ignore           'wildignore'

Trial-and-error will prove /ignorecase as the help section we want. “:help case<TAB>” shows a similar list.

The equivalent in Emacs is probably C-h d, apropos-documentation. It’s actually a little handier than :help: because the convention in Emacs Lisp is to supply docstrings for API symbols, apropos-documentation searches a larger space having a higher chance of relevancy.

Relatedly, there are C-h f, describe-function, and C-h v, describe-variable, which present full API documentation and can even jump to the definition of a given symbol. Really convenient.

ii. Reference material

When programming, no matter your skill or experience, it’s imperative to have some kind of reference material. Documentation is okay, but existing code is the real blessing. Here is a place where Emacs shines. The basic core of the editor is written in C (opaquely), but the rest of its functionality is written in Emacs Lisp and is immediately available for viewing.

$ dpkg -L emacs-snapshot-el | grep '\.el\.gz$' | wc -l
1125
$ gunzip --stdout `dpkg -L emacs-snapshot-el | grep '\.el\.gz$'` | wc -l
1243201

Over one million lines-of-code in 1125 files — a treasure trove.

This is harder to measure in Vim. Its base package includes about 1000 .vim files, but the vast majority are filetype or indentation specifiers, or colour themes, none of which are generally of use to a programmer. There is an unofficial vim-scripts package in Debian-based distributions, though it too skews toward themes. Here are the numbers anyway:

$ dpkg -L vim-scripts | grep '\.vim$' | wc -l
172
$ cat `dpkg -L vim-scripts | grep '\.vim$'` | wc -l
47972

2. Provisions for extension

As an environment for editor extensions, Emacs can be fairly called full-featured; Vim can’t. (I’m suitably positioned to make this judgement having created sizable plugins for both editors.)

Rather than write a feature-by-feature comparison, I’ll just list some things Emacs natively supports which Vim does not, or does only poorly:

i. Key capture/filename entry

  • As a convenience for input, Emacs provides simple minibuffer functions such as read-file-name and read-buffer as well as more general read-from-minibuffer and read-string, which offer a lot of customization. Capturing key presses is made easy with read-key-sequence; to respond to general user action, watch functions can be added to hook variables such as post-command-hook.
  • Vim offers input() for using the command-line in a script, but it is limited. Key capture is very difficult; getchar() exists, but is incomplete. Inexplicably, there is no CharacterPress autocmd event (or anything similar). The only certain way to capture keys is to remap all of them to call a user function, then later restore all previous mappings. A bad hack.

ii. Specialized buffers

  • Creating a new temporary buffer or a special-purpose buffer in Emacs can be done using with-temp-buffer and generate-new-buffer, or several other calls.
  • Vim provides no standard means to create a special buffer; the documentation recommends to create a new buffer and to set these options:

    :setlocal buftype=nofile
    :setlocal bufhidden=hide
    :setlocal noswapfile

    The problem is, this new buffer will also inherit many user settings, such as wrap, number, foldcolumn, cursorline, spell, and sidescroll. There is no way — that I know of — to create a fresh, blank buffer. For correctness, all of these settings should be enumerated and explicitly turned off upon buffer creation. To make this even less satisfactory, not all buffer settings can be set per-buffer.

iii. Programmatic window cycling/traversal

Emacs has many useful functions:

  • walk-windows — equivalent to mapping (window-list) through a given function.
  • window-tree — returns a tree representing the window layout in the given frame.
  • save-window-excursion — screw around with the current layout temporarily, without repercussion.
  • Many more. And as a bonus, the minibuffer can be manipulated with standard window functions.

Similar functions in Vim:

  • Like walk-windows: there is :windo, but it only works on commands, not functions, and gives up completely on any minor error.
  • The closest analogue to window-tree may be winnr(), a multi-purpose function. It returns the following, depending on context:
    1. The number of the current window (to be used as an argument in other functions)
    2. The count of open windows
    3. The number of the last accessed window
  • Like save-window-excursion: winrestcmd() generates a sequence of storable window commands that can be called to restore the current window configuration; but it doesn’t always work. There are also winsaveview() and winrestview(), but they also don’t always work.

iv. Buffer ordering

When enumerating buffers for some purpose, the most suitable ordering is often most-recently used (MRU). This is how Emacs acts by default with e.g. buffer-list. Vim seems to order buffers by most recently opened, or maybe it’s more arbitrary. MRU is possible in Vim, but it must be hacked in manually by use of autocmds, watching these events:

  • BufEnter
  • BufDelete
  • BufWipeout

v. Environment variable interpretation

  • Emacs: getenv/setenv, process-environment, various helper functions.
  • Vim:

    :let $VAR = value  " set
    :let var = $VAR    " get

    Sometimes works when setting options:

    :set term=$TERM    " works
    :set history=$NUM  " does not work

vi. Text deletion

A minor problem (or convenience) of Vim is that every scripted delete action will clobber the unnamed register and numbered registers that we use for quick cut+pastes. (Emacs folks: this could be like losing your kill ring every time you run find-file or dired.) I struggled with this for a long time before learning of Vim’s blackhole register. In general, the behaviour of Vim’s delete registers is intricate and unpleasant.

vii. Completion

  • Emacs has heavy-duty completion support, from simple, high-level functions such as read-buffer and read-variable to accept values at the minibuffer, to try-completion and all-completions which can be used programmatically without action from a user.
  • Vim command definitions can be specified with a special argument to include completion support at the ex command line. It’s a little awkward, though. Copy/paste the following block and then type “:Finger <TAB>” in Vim for a demonstration:

    command -complete=custom,ListUsers -nargs=1 Finger !finger <args>
    fun ListUsers(A,L,P)
        return system("cut -d: -f1 /etc/ passwd")
    endfun

    See :help :command-completion for an explanation. I don’t think Vim has built-in support for programmatic completion.

Speaking broadly: Emacs feels engineered, while Vim gives the impression of having grown piecemeal. As a platform it is awkward and missing some useful bits. This can be liberating, as one need not worry about duplicating something already part of the API, but it’s also limiting.

Vim offers bindings into other languages, and you may choose to eschew Vim Script and mitigate some of these issues. But a side effect of doing so is that you’ll need to account for differences in string syntax when directly interfacing with the editor. Check out these Ruby-to-Vim special character escaping functions:

def vim_single_quote_escape(s)
  # Everything in a Vim single-quoted string is literal, except single
  # quotes.  Single quotes are escaped by doubling them.
  s.gsub("'", "''")
end
def vim_filename_escape(s)
  # Escape slashes, open square brackets, spaces, and double quotes
  # using backslashes.
  s.gsub(/[\['" \\]/, '\\\\\0')
end
def vim_regex_escape(s)
  # Escape lots of stuff.
    s.gsub(/[\]\[.~"^$\\*]/,'\\\\\0')
end

It took me a few releases to get these right. Admittedly, this glue is the cost of doing business with an external language.

3. Portability requirements

(To be clear: “portability” here refers to accounting for differences between versions of the same editor, whether running on the same operating system or not.)

When developing for Vim, it’s unlikely that portability will be an issue; Vim Script has changed little in the last few years. Also, since upgrading Vim is painless, users will do so, and therefore it’s common for a plugin writer to target only the most recent major release. There’s even an integrated feature to automatically update plugins since Vim 7.1, which suggests faith there won’t be breaking changes in the future.

As discussed in a previous article, portability must be a greater concern to developers of Emacs packages. It’s probably a good idea to choose a portability target during development instead of trying to hack it in after the fact.

4. Conclusion

I’d feel uncomfortable to mark this as a win for Emacs or a loss for Vim, even given all the evidence. The basic philosophies of the two editors are distinct enough to make the comparison unfair. To have a video editor integrated into Vim would be neither funny nor useful, while for Emacs it’s perhaps both.

But I do think it’s fair to say that Vim Script is an ugly language, a DSL which has been stretched to the breaking point. It’s an intellectual dead-end. Emacs Lisp feels more like a true programming language and has much the elegance of any other Lisp. And to learn it pays dividends, as it overlaps in respectable portion with Common Lisp.

Further discussion on the programming reddit
Further discussion on Hacker News


19 Responses to “Extensibility in Vim and Emacs”

  1. skeptomai Says:

    You had me up to ‘overlaps …. Common Lisp’. Emacs Lisp is hugely useful in its context, but is a poor lisp, missing among other things lexical closures and confusing new users with its dynamic extent. I’m a staunch Emacs advocate, but I can’t roundly defend Emacs Lisp. Yes, I know about the ‘cl’ package, but this is a band-aid over a wound that won’t heal.

  2. Stephen Bach Says:

    I think about it like this: an Emacs Lisp expert will be able to make an easy transition to Common Lisp. A Vim Script expert gains nothing from his experience except the knowledge of how to customize Vim well.

    You’re right that Emacs Lisp makes for a poor general-purpose Lisp.

  3. Brian Says:

    So far as “engineered” vs. “piecemeal”, I actually have the opposite impression. Vim gives me many things I want by default that in Emacs have to be user-written scripts. These scripts are as piecemeal as you could get, written by different authors, often of questionable quality, often out-dated.

    For example how do I display line numbers in a column down the left side of the editor in Emacs? This is built-in for Vim, but in Emacs I need linum.el or similar; I have yet to find a line-numbering script that works correctly 100% of the time, e.g. some of them don’t interact very well with SLIME, and even in a normal buffer often the spacing or formatting of the numbers will become garbled for no reason I can determine. I have had many similar experiences with Emacs plugins that don’t interact well together.

    Vim doesn’t need as many plugins as Emacs to do these sorts of things because the features already exist in the editor. It’s a small sacrifice in extensibility, but you gain consistency. This is a worthy sacrifice assuming the built-in features already do what you want, which they do for me most of the time. (e.g. MRU vs. other orderings, I actually find MRU to be annoying. Overwriting the default register is the behavior I want when deleting text; I never liked the kill ring.)

    I use Emacs and Vim both and Vim always feels more solid to me. They’re both good editors but as you say, they have different goals. Vim focuses on editing text and does it very well. Emacs focuses on letting you do crazy things and script everything under the sun, which is awfully helpful sometimes but it has its own cost.

    (I agree that Emacs lisp is not all that comparable to CL. All variables are dynamically bound? They are both Lisps but they demand vastly different style.)

  4. Stephen Bach Says:

    Brian, thank you for the well-reasoned reply. Lemme respond to each item individually.

    For example how do I display line numbers in a column down the left side of the editor in Emacs? This is built-in for Vim, but in Emacs I need linum.el or similar; I have yet to find a line-numbering script that works correctly 100% of the time, e.g. some of them don’t interact very well with SLIME, and even in a normal buffer often the spacing or formatting of the numbers will become garbled for no reason I can determine. I have had many similar experiences with Emacs plugins that don’t interact well together.

    I have to agree with you re: line numbering, though that may be a function of my relative inexperience with Emacs compared to Vim. The confusion expressed on this page is telling.

    Vim doesn’t need as many plugins as Emacs to do these sorts of things because the features already exist in the editor. It’s a small sacrifice in extensibility, but you gain consistency. This is a worthy sacrifice assuming the built-in features already do what you want, which they do for me most of the time. (e.g. MRU vs. other orderings, I actually find MRU to be annoying. Overwriting the default register is the behavior I want when deleting text; I never liked the kill ring.)

    re: MRU, my real beef with Vim here is that I can’t programmatically reorder the buffers for some purpose without special effort; the information to do so is simply not available.

    re: the unnamed register, I agree; all user-driven deletion should go to that register. But I don’t think it’s good that plugin-driven deletion should go there as well (again, without special effort).

    I use Emacs and Vim both and Vim always feels more solid to me. They’re both good editors but as you say, they have different goals. Vim focuses on editing text and does it very well. Emacs focuses on letting you do crazy things and script everything under the sun, which is awfully helpful sometimes but it has its own cost.

    Yes. 🙂

    (I agree that Emacs lisp is not all that comparable to CL. All variables are dynamically bound? They are both Lisps but they demand vastly different style.)

    I know this is a regular criticism of Emacs Lisp, but in my experience at least dynamic scope is not so terrible a drawback in practice. No, it’s not optimal.

  5. troll Says:

    VIM doesn’t fit the style of editing I’m used to so I’m going to complain about it not fitting my work flow.

  6. DZ Says:

    I’ve always felt that in the area that matters most to me — editing — Vim feels more engineered and logical than Emacs.

    In Vim, commands build on top of each other in a logical and predictable way. Delete a character? “d”. Go to the next word? “w”. Delete until the next word? “dw”.

    Emacs key bindings feel arbitrary and unpredictable to me. To me, things are mapped haphazardly — some actions bound to meta-foo belong more in control-foo.

    No arguments from me that Emacs feels more engineered when talking about extensibility. Vim can give the impression that extending it was an afterthough, whereas Emacs make it appear that extensibility was expected from the beginning.

  7. Stephen Bach Says:

    DZ, I too prefer the Vim edit model.

  8. Krylen Says:

    This was a very enlightening post, thank you very much for providing this somewhat obscure information. I don’t have much to add here, but I will say when it comes to programming languages emacs has it hands down. Lisp is an elegant extendable language that still goes where modern languages fear to tread. That being said, I am a VIM user, the style of interaction fits my desires for day-to-day use much better than emacs, not to claim it is better or worse. Also, it’s handy to be very comfortable when you find yourself on an aging Solaris or AIX box that have nothing but old fashioned VI, which can be somewhat of an irritation compared to VIM.

  9. DZ Says:

    Also, great job on lustyexplorer 🙂 It’s the first thing that goes into my .vim folder whenever I do a fresh install.

  10. Anonymous Says:

    Another problem with vim script is its reliance on user settings. For instance, == relies on the ignorecase setting (see :help expr4). This is completely counter-intuitive, not to mention pointless; most users don’t bother to check this (and rightly so, it’s not the case in _any_ other language), so their scripts randomly break on certain cases (making it not portable, which is terrible for a scripting language). It’s not just these operators, so many functions do this too, like search(), searchpair(), match(), etc…

    Also, a minor detail that has annoyed me on my scripts is that the last inserted text register (:h ".) is read-only. This makes it very hard to not break the “.” command in, for instance, completion plugins.

    I like Vim and use it every day, but I think it would be much better of if it went with a standard (good) scripting language like Python or Lua. Vim is very extensible, it’s just typically a pain to do it, and, as you said, vim script is also only applicable to Vim, whereas learning something like Lisp, Python, etc. has other applications.

  11. Stephen Bach Says:

    Krylen, DZ, thanks!

    Anonymous, this is very informative. I wasn’t aware of that irritating side-effect of ignorecase, though I have been bitten by "..

  12. Anonymous Says:

    (global-linum-mode 1)

  13. mb Says:

    I’m not sure, I follow your argument on completion. Vim has a wide variety
    of completion functions: keyword completion, filename completion, omni
    completion… You can use eg. omni completion programmatically because it
    is specified by a function. Whether this function is called by Vim or by you
    doesn’t really matter…

    I don’t see, where this is less “heavy-duty” than emacs. But of course it depends
    on the quality of the completion function. I think, eg. VimClojure’s omni completion
    beats the one of SLIME.

  14. Stephen Bach Says:

    Meikel, I suspect you know more than I about how completion works in Vim, so I’m willing to concede this point. I do still think it’s unwieldy. The same function call is used to both:

    1. Return a substring for completing — sort-of like try-completion in Emacs
    2. Return the possible completions for that substring — all-completions in Emacs

    I’ve been intending to try VimClojure. At some point I’ll put your assertion to the test. 🙂

  15. Moonie Says:

    I’ve used both Vim and Emacs a lot and can code with both editor’s scripting language. My opinion is that Vim is a great text editor which can be customized easily. By customization I don’t mean extensibility. It’s really difficult to write good, large extensions for Vim so that they work nicely in all modes and interact well with other extensions and the rest of the system. There will never be anything like Gnus, Org-mode or CC-mode in Vim. Perhaps only cheap imitations with a small subset of features. In short, Vim is great unless you want to go beyond customizable text editor.

  16. short and sweet elisp primer « Awhan Patnaik Says:

    […] on the subject of emacs this article is surely a reassuring read for those emacs users who are constantly vexed by vim users. will the […]

  17. Mike Says:

    I’m an Emacs user and I’ve written one mode in for my own use (in very, very pedestrian Elisp). I use VM for email and Magit for accessing git.

    Quite a few of the problems with Emacs that were mentioned have to do with its age: Elisp is the way it is partly because it originated on very limited hardware by today’s standards. The reason that displaying line numbering has appeared as late as it has is likely because of limited integration with the GUI toolkits and the requirement to work also in terminals. It’s obviously much easier to do something like that in, say, Gedit, which only has to work on Gtk.

    I’m somewhat hopeful that Emacs development will pick up some speed. The project finally has a proper bug tracker and will hopefully soon switch to Bazaar for version control. The need for a shorter release cycle is also widely acknowledged, I think. Maybe they’ll even fix some of the fundamental problems in Elisp (concurrency?).

  18. Anonymoose Says:

    I do not think Vim has built-in support for programmatic completion.

    Vim does in fact have support for this, although it is limited (see :h complete() or :h complete-functions). However, the built-in completion for vim seems much harder to script; for instance, it seems nearly impossible to do things like adding items that would appear on the keyword-completion menu (shown via or ) onto an omnicompletion menu (shown via the unfriendly ), check whether the completion menu is visible after pressing or any of the other many keystrokes to display the slightly different versions (no, pumvisible() does not work), among other simple things like binding an item on the menu to a keystroke.

    Speaking broadly: Emacs feels engineered, while Vim gives the impression of having grown piecemeal.

    Although I have not used Emacs enough to have a viable opinion on it, I definitely agree with the latter assessment (despite using vim every day ;).

  19. SirVer Says:

    vim script is a deadend! I really enjoyed your break down. I used vim forever (> 15 years now… wow) and only took a short look to emacs, I am convinced that the two editors are the best on the planet. I agree though that vimscript is awful: limited syntax and hard to debug. I see light on the tunnel though: on the vim site, a top requested feature is the proper support for python scripting and since Bram is a Fan of Python himself (he has written his A-A-P build system in it) I consider it likely that future updates will go into this direction. I shall very much enjoy it, if this is the case.