···
title: "Writing Vim Plugin"
date: 2019-11-04T18:21:18+01:00
5
+
Article about writing Vim plugins, but not about writing Vim plugins. It is
6
+
how to concieve plugin, how to go from an idea to the full fledged plugin.
7
-
While there is a lot of "tutorials" for writing plugins in Vim, I hope this one
8
-
will be a little different form what is out there, because I will not write
9
-
about writing plugin per se. If you want to find information about that then you
10
-
should check out `:h write-plugin`. In this article I want to provide you a
11
-
tutorial about how plugin becomes a thing using as example my own experience on
12
-
writing [`vim-backscratch`][scratch].
12
+
While there are many "tutorials" for writing plugins in Vim, I hope this one
13
+
will be a little bit different from what is out there, because it won't be
14
+
about writing plugin *per se*. If you want to find information about that then
15
+
you should check out [`:h write-plugin`][h-write-plugin]. I want this article to be about how
16
+
plugins come to life, using my own experience on writing
17
+
[`vim-backscratch`][scratch] as an example.
16
-
All plugins should start with a problem, if there is no problem, then there
17
-
should be no code, there is no better code than [no code][]. In this case
18
-
my problem was pretty trivial - I wanted temporary buffer to be able to provide
19
-
quick edits and view into SQL queries while optimising them (and run them from
20
-
there with [`dadbod`][dadbod]).
21
+
All plugins should start with a problem. If there is no problem, then there
22
+
should be no code, as there is no better code than [no code][nocode]. In this
23
+
case, my problem was pretty trivial: I wanted a temporary buffer that would let
24
+
me perform quick edits and view SQL queries while optimising them (and run them
25
+
from there with Tim Pope's [dadbod][dadbod]).
## "Simple obvious solution"
24
-
When we have defined problem, then we need to check the first possible solution,
25
-
in our case it is opening new buffer in new window, edit it, and then close it
26
-
when no longer needed. It is simple in Vim
29
+
Now that we have defined the problem, we need to try the first possible solution.
30
+
In our case, it is opening a new buffer in a new window, edit it, and then close it
31
+
when no longer needed. It is simple in Vim:
···
Unfortunately this has bunch of problems:
36
-
- If we forgot to close that buffer, then it will hang there indefinitely
37
-
- If we run `:bd!` in wrong buffer, then it can have unpleasant consequences
38
-
- Such buffer is still listed in `:ls`, which is unneeded (as this is only
41
+
- if we forgot to close that buffer, then it will hang there indefinitely,
42
+
- running `:bd!` in the wrong buffer, can have unpleasant consequences,
43
+
- this buffer is still listed in `:ls`, which is unneeded (as it is only
## Systematic solution in Vim
43
-
Fortunately Vim has solution for all of our problems `:h scratch-buffer`, which
44
-
solves first two problems, and `:h unlisted-buffer` which solves third problem.
48
+
Fortunately Vim has solutions for all of our problems:
50
+
- the "scratch" section in [`:h special-buffers`][h-special-buffers], which
51
+
solves the first two problems,
52
+
- [`:h unlisted-buffer`][h-unlisted-buffer], which solves the third problem.
So now our solution looks like:
···
54
-
However that is long chain of commands to write, of course we could shorten
63
+
However that is a long chain of commands to write. Of course we could condense
64
+
the first two into a single one:
:new ++nobuflisted ++buftype=nofile ++bufhidden=delete ++noswapfile
61
-
But in reality that do not shorten nothing.
70
+
But in reality this does not shorten anything.
65
-
Fortunately we can create our own commands in Vim, so we can shorten that to
74
+
Fortunately we can create our own commands in Vim, so we can shorten that to a
single, easy to remember command:
command! Scratch new ++nobuflisted ++buftype=nofile ++bufhidden=delete ++noswap
72
-
However I, for better flexibility prefer it to be:
81
+
For better flexibility, I prefer it to be:
command! Scratchify setlocal nobuflisted buftype=nofile bufhidden=delete noswap
command! Scratch new +Scratchify
79
-
We can also add few new commands to allow us to better control where our new
88
+
We can also add a bunch of new commands to give us better control over our new
command! VScratch vnew +Scratchify
command! TScratch tabnew +Scratchify
87
-
That will open new vertical buffer and buffer in new tab, respectively.
96
+
Those commands will open a new scratch buffer in a new vertical window, and
97
+
a new scratch buffer in a new tab page, respectively.
89
-
## Make it more "vimmy" citizen
99
+
## Make it a more "vimmy" citizen
91
-
While our commands `:Scratch` and `:VScratch` are nice, these are still not
92
-
flexible enough. In Vim we can use modifiers like `:aboveleft` to define exactly
93
-
where we want window to appear and our current commands do not respect that. To
94
-
fix it we can simply squash all commands into one:
101
+
While our commands `:Scratch`, `:VScratch`, and `:TScratch` are nice, they are
102
+
still not flexible enough. In Vim we can use modifiers like [`:h
103
+
:aboveleft`][h-aboveleft] to define exactly where we want new windows to appear
104
+
and our current commands do not respect that. To fix this problem, we can
105
+
simply squash all the commands into one:
command! Scratch <mods>new +Scratchify
···
And we can remove `:VScratch` and `:TScratch` as these can be now done via
`:vert Scratch` and `:tab Scratch` (of course you can keep them if you like, I
102
-
just wanted UX to be minimal).
113
+
just wanted the UX to be minimal).
106
-
In the form I have described it above it have been in `$MYVIMRC` for some time,
107
-
but after that I have found [Romain Lafourcade's snippet][redir] that provided
108
-
one additional feature - it allowed to open our scratch with output of Vim
109
-
command or shell command. My first thought was - hey, I know that, but I know I
110
-
can make it better! So we can crate simple VimL function (which is mostly copied
111
-
from romainl snippet, but few updates):
117
+
This has been in my `$MYVIMRC` for some time in the form described above until
118
+
I found out [Romain Lafourcade's snippet][redir] that provided one additional
119
+
feature: it allowed to open a scratch buffer with the output of a Vim or shell
120
+
command. My first thought was - hey, I know that, but I know I can make it
121
+
better! So we can write a simple VimL function (which is mostly copied from
122
+
romainl's snippet, with a few improvements):
function! s:scratch(mods, cmd) abort
···
The main differences are:
135
-
- Special case for empty command, it will just open empty buffer
136
-
- Use of `is#` instead of `==`
137
-
- Use of `:h execute()` instead of `:redir`
146
+
- special case for empty command, it will just open an empty buffer,
147
+
- use of `is#` instead of `==`,
148
+
- use of `:h execute()` instead of `:redir`.
139
-
As it is quite self-contained and (let's be honest) to specific for `$MYVIMRC`
140
-
now we can can extract it to its own location in `.vim/plugin/scratch.vim` (or
141
-
respectively `./config/nvim/plugin/scratch.vim` for NeoVim), but to do so
142
-
properly we need one additional thing, command to prevent file from being loaded
143
-
twice. So in the end we have file like:
150
+
As it is quite self-contained and (let's be honest) too specific for `$MYVIMRC`
151
+
we can can extract it to its own location in `plugin/scratch.vim`, but to do so properly we need
152
+
one additional thing, a command to prevent the script from being loaded twice:
if exists('g:loaded_scratch')
···
Now my idea was, hey, I use Vim macros from time to time, and these are just
173
-
simply list of actions stored in Vim register. Maybe it would be nice to have
174
-
access to that as well in our command. So just add new branch to our if, that
175
-
checks if `a:cmd` begins with `@` sign and is only 2 letter long, if so, then
176
-
set `l:output` to spliced content of the register:
182
+
simple lists of keystrokes stored in Vim registers. Maybe it would be nice to have
183
+
access to that as well in our command. So we will just add a new condition that
184
+
checks if `a:cmd` begins with the `@` sign and has a length of two. If so, then
185
+
set `l:output` to the spliced content of the register:
function! s:scratch(mods, cmd) abort
···
201
-
This gives us pretty powerful solution where we can use `:Scratch @a` to open
202
-
content of the register `A` in the scratch buffer, edit it, and yank it back via
210
+
This gives us a pretty powerful solution where we can use `:Scratch @a` to open
211
+
a new scratch buffer with the content of register `A`, edit it, and yank it
207
-
Now, when we see how useful it is, it would be shame to keep it for ourselves,
208
-
let's share this with the big world. In this case we need:
216
+
Now, it would be a shame to keep such a useful tool for ourselves so
217
+
let's share it with the big world. In this case we need:
210
-
- Proper project structure
219
+
- a proper project structure,
221
+
- a good catchy name.
214
-
About first two you can read more on `:h write-plugin` and `:h
215
-
write-local-help` or in any of the bazillion tutorials in the internet.
223
+
You can find help on the two first topics in [`:h
224
+
write-plugin`][h-write-plugin] and [`:h write-local-help`][h-write-local-help]
225
+
or in any of the bazillion tutorials on the internet.
217
-
About last one I cannot provide much help. I have picked `vim-backscratch`,
218
-
because I like back scratches (everyone like them) and as a nice coincidence
219
-
it also has "scratch" in the name.
227
+
Finding a good name is something I can't help you with. I have picked
228
+
`vim-backscratch`, because I like back scratches (everyone likes them) and, as
229
+
a nice coincidence, because it contains the word "scratch".
223
-
Creating plugins for Vim is easy, but not every one functionality need to be a
224
-
plugin from the day one. Start easy and small. If something can be done by a
225
-
simple command/mapping, then it should be it at first. If you find it really
226
-
useful then, and only then, you should think about making it into plugin. Whole
227
-
process described in this article wasn't done in week or two. The step *Make it
228
-
more "vimmy" citizen* took about a year before I found romainl script on IRC. I
229
-
didn't need anything more, so take your time.
233
+
Creating plugins for Vim is easy, but not every functionality needs to be
234
+
a plugin from day one. Start easy and small. If something can be done with
235
+
a simple command/mapping, then it should be done with a simple command/mapping
236
+
at first. If you find your solution really useful, then, and only then, you
237
+
should think about turning it into a plugin. The whole process described in this
238
+
article didn't happen in a week or two. It took me about a year to reach the step
239
+
*Make it a more "vimmy" citizen*, when I heard about romainl's script on IRC.
240
+
I didn't need anything more, so take your time.
233
-
- Make it small, big plugins will require a lot of maintenance, small plugins
234
-
are much simpler to maintain
235
-
- If something can be done via command then it should be made as a command, do
236
-
not force your mappings on users
244
+
- make it small, big plugins will require a lot of maintenance, small plugins
245
+
are much simpler to maintain,
246
+
- if something can be done via a command then it should be made as a command,
247
+
do not force your mappings on users.
[scratch]: https://github.com/hauleth/vim-backscratch
239
-
[no code]: https://github.com/kelseyhightower/nocode
250
+
[nocode]: https://github.com/kelseyhightower/nocode
[dadbod]: https://github.com/tpope/vim-dadbod
252
+
[redir]: https://gist.github.com/romainl/eae0a260ab9c135390c30cd370c20cd7
253
+
[h-write-plugin]: https://vimhelp.org/usr_41.txt.html#write-plugin
254
+
[h-write-local-help]: https://vimhelp.org/usr_41.txt.html#write-local-help
255
+
[h-special-buffers]: https://vimhelp.org/windows.txt.html#special-buffers
256
+
[h-unlisted-buffer]: https://vimhelp.org/windows.txt.html#unlisted-buffer
257
+
[h-aboveleft]: https://vimhelp.org/windows.txt.html#%3Aaboveleft