In this post, I would like to present vim-mail-refs, a new Vim plugin that I wrote. It automatically inserts URL references into emails.
Introduction
Since I started to use Vim as the sole text editor, I have found myself pressing Vim shortcuts outside Vim all the time. Especially when writing emails. Of course, it did nothing useful since I was writing them directly in Thunderbird. Thanks to the External Editor plugin that integrates Vim into Thunderbird, writing emails become easier. However, one thing was still missing. Creating emails with references was tedious.
Imagine all the work that had to be done to create an email with references. I had to write a reference to a URL in the email body, then move to the end of the email and write the reference with the URL on one line separated by a space and, finally, move to the original position. Admittedly, this sequence of actions can be done relatively quickly in Vim. Nevertheless, I still had to pay attention not to include one URL twice in the reference list. Also, after rewriting the email, references needed to be renumbered by their order in the email body or completely removed when they were no longer used.
To simplify writing of emails with references, I implemented a Vim plugin that does all of this automatically. In the remainder of this post, I would like to introduce this plugin to you.
Usage
The plugin provides three features. The first one is implemented by the AddMailRef
command. After executing :AddMailRef
, you will be asked to enter a URL. Then, a reference to this URL will be added into the current cursor position in the email body, including adding the URL to the end of the email body before the signature (if any). If the current cursor position is inside a word (anything with letters, numbers, underscores and hyphens), the reference will be added after this word and separated by a single space.
The second feature is represented by the AddMailRefFromMenu
command. It is useful if you want to reuse an already existing reference. After executing :AddMailRefFromMenu
, you will be asked to select a reference from the shown menu. To select a reference, either write just the reference number (e.g. 1
) or put it inside square brackets (e.g. [1]
).
The last command, called FixMailRefs
, normalizes all references used in the email. It performs the following actions:
- unused references are removed,
- references are renumbered by their order of appearance in the buffer (
[1]
,[2]
, …).
To simplify the use of the plugin, it is recommended to create mappings for these commands. For example:
au FileType mail nnoremap <buffer> <Leader>ar :AddMailRef<CR> au FileType mail nnoremap <buffer> <Leader>aR :AddMailRefFromMenu<CR> au FileType mail nnoremap <buffer> <Leader>fr :FixMailRefs<CR>
The first autocommand allows using ,ar
(assuming that your Leader
is ,
) as an abbreviation for typing :AddMailRef
. The other autocommands create similar mappings.
Implementation
The plugin consists of two parts. The first one is written in Vimscript and serves as an interconnection between Vim and the second part. The second part is written in Python and contains the core of the plugin. The Python part is covered by unit tests. Since this plugin is implemented in Python 3, Vim has to be compiled with Python 3 support. To check it, run :echo has("python3")
.
During the implementation of this plugin, I stumbled across a few problems. The most subtle one was calculating cursor position. Initially, I used vim.current.windows.cursor
to get the cursor position. It is a tuple containing two numbers: row and column. At least so I thought. At first, everything seemed to work all right and my friend started to test the plugin. After a short time, he reported a bug. Sometimes, references were inserted at wrong position. It turned out that the problem happened only for emails written in Czech. Since Czech uses accents, some letters need more than one byte to be stored. From this stackoverflow post, I grasped that column from vim.current.windows.cursor
is byte offset on the current line.
For correct functioning of the plugin, I need to use the virtcol
function. However, using virtcol
has its own drawbacks. One of them is that the result of virtcol
depends on the tabstop
and linebreak
settings. Before calling virtcol
, these settings have to be remembered and set to appropriate values and after the call to virtcol
they have to be set to their original values. For a more thorough explanation, look at this blog post.
For an introduction to writing of Vim plugins in Python, you can take a look at this video. However, be aware that it does not include proper handling of cursor positions when writing non-ASCII text. For other tips how to write Vim plugins, I highly recommend this article.
Download
The plugin can be downloaded here. If you find a bug in the plugin or have some ideas for enhancing or extending plugin, please let me know. I will also be happy to receive any opinions and comments on this plugin.