Extensibility in Vim and Emacs
Posted in Uncategorized on April 7th, 2009- Table of contents
- 1. Development resources
- 2. Provisions for extension
- 3. Portability requirements
- 4. Conclusion
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:
- Thoughts on editors (Emacs in particular) by François Pinard
- Emacs and Vim by Vincent Foley
- Emacs and Vi by John Dierdorf
I have 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 is 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 do not 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 is actually 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 is 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 cannot. (I am in a good position to make this statement having written sizable plugins for both editors.)
Rather than write a feature-by-feature comparison, I will 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-nameandread-bufferas well as more generalread-from-minibufferandread-string, which offer a lot of customization. Capturing key presses is made easy withread-key-sequence; to respond to general user action, watch functions can be added to hook variables such aspost-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 noCharacterPressautocmdevent (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-bufferandgenerate-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, andsidescroll. 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.- Much 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-treemay bewinnr(), a multi-purpose function. It returns the following, depending on context:- The number of the current window (to be used as an argument in other functions)
- The count of open windows
- 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 does not always work. There are alsowinsaveview()andwinrestview(), but they also do not 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 is more arbitrary. MRU is possible in Vim, but it must be tracked manually within a plugin by use of autocmds, watching these events:
BufEnterBufDeleteBufWipeout
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 are intricate and unlikeable.
vii. Completion
- Emacs has heavy-duty completion support, from simple, high-level functions such as
read-bufferandread-variableto accept values at the minibuffer, totry-completionandall-completionswhich 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 is 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-completionfor an explanation. I do not 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 is 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 will 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 is 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 is common for a plugin writer to target only the most recent major release. There is even an integrated feature to automatically update plugins since Vim 7.1, which suggests faith that there will not be breaking changes in the future.
As discussed in a previous article, portability must be a greater concern to developers of Emacs packages. A developer should make a conscious decision during development, not after, about the level of portability they wish to provide.
4. Conclusion
I would 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 is perhaps both.
But I do think it is fair to say that Vim Script is an ugly language, a DSL which has been stretched to the breaking point. It is also 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.