Inserting References Into Emails In Vim

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.

AddMailRef Command In Use

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]).

AddMailRefFromMenu Command In Use

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], …).

FixMailRefs Command In Use

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.

Leave a Comment.