this repo has no description

chore: update article with metadata

Changed files
+96 -79
content
+96 -79
content/post/writing-vim-plugin.md
···
---
title: "Writing Vim Plugin"
date: 2019-11-04T18:21:18+01:00
-
draft: true
+
description: |
+
Article about writing Vim plugins, but not about writing Vim plugins. It is
+
how to concieve plugin, how to go from an idea to the full fledged plugin.
+
tags:
+
- vim
+
- viml
---
-
While there is a lot of "tutorials" for writing plugins in Vim, I hope this one
-
will be a little different form what is out there, because I will not write
-
about writing plugin per se. If you want to find information about that then you
-
should check out `:h write-plugin`. In this article I want to provide you a
-
tutorial about how plugin becomes a thing using as example my own experience on
-
writing [`vim-backscratch`][scratch].
+
While there are many "tutorials" for writing plugins in Vim, I hope this one
+
will be a little bit different from what is out there, because it won't be
+
about writing plugin *per se*. If you want to find information about that then
+
you should check out [`:h write-plugin`][h-write-plugin]. I want this article to be about how
+
plugins come to life, using my own experience on writing
+
[`vim-backscratch`][scratch] as an example.
## Problem
-
All plugins should start with a problem, if there is no problem, then there
-
should be no code, there is no better code than [no code][]. In this case
-
my problem was pretty trivial - I wanted temporary buffer to be able to provide
-
quick edits and view into SQL queries while optimising them (and run them from
-
there with [`dadbod`][dadbod]).
+
All plugins should start with a problem. If there is no problem, then there
+
should be no code, as there is no better code than [no code][nocode]. In this
+
case, my problem was pretty trivial: I wanted a temporary buffer that would let
+
me perform quick edits and view SQL queries while optimising them (and run them
+
from there with Tim Pope's [dadbod][dadbod]).
## "Simple obvious solution"
-
When we have defined problem, then we need to check the first possible solution,
-
in our case it is opening new buffer in new window, edit it, and then close it
-
when no longer needed. It is simple in Vim
+
Now that we have defined the problem, we need to try the first possible solution.
+
In our case, it is opening a new buffer in a new window, edit it, and then close it
+
when no longer needed. It is simple in Vim:
```vim
:new
···
Unfortunately this has bunch of problems:
-
- If we forgot to close that buffer, then it will hang there indefinitely
-
- If we run `:bd!` in wrong buffer, then it can have unpleasant consequences
-
- Such buffer is still listed in `:ls`, which is unneeded (as this is only
-
temporary)
+
- if we forgot to close that buffer, then it will hang there indefinitely,
+
- running `:bd!` in the wrong buffer, can have unpleasant consequences,
+
- this buffer is still listed in `:ls`, which is unneeded (as it is only
+
temporary).
## Systematic solution in Vim
-
Fortunately Vim has solution for all of our problems `:h scratch-buffer`, which
-
solves first two problems, and `:h unlisted-buffer` which solves third problem.
+
Fortunately Vim has solutions for all of our problems:
+
+
- the "scratch" section in [`:h special-buffers`][h-special-buffers], which
+
solves the first two problems,
+
- [`:h unlisted-buffer`][h-unlisted-buffer], which solves the third problem.
+
So now our solution looks like:
```vim
···
:bd
```
-
However that is long chain of commands to write, of course we could shorten
-
first two to one:
+
However that is a long chain of commands to write. Of course we could condense
+
the first two into a single one:
```vim
:new ++nobuflisted ++buftype=nofile ++bufhidden=delete ++noswapfile
```
-
But in reality that do not shorten nothing.
+
But in reality this does not shorten anything.
-
## Create command
+
## Create a command
-
Fortunately we can create our own commands in Vim, so we can shorten that to
+
Fortunately we can create our own commands in Vim, so we can shorten that to a
single, easy to remember command:
```vim
command! Scratch new ++nobuflisted ++buftype=nofile ++bufhidden=delete ++noswap
```
-
However I, for better flexibility prefer it to be:
+
For better flexibility, I prefer it to be:
```vim
command! Scratchify setlocal nobuflisted buftype=nofile bufhidden=delete noswap
command! Scratch new +Scratchify
```
-
We can also add few new commands to allow us to better control where our new
-
window will appear:
+
We can also add a bunch of new commands to give us better control over our new
+
window's location:
```vim
command! VScratch vnew +Scratchify
command! TScratch tabnew +Scratchify
```
-
That will open new vertical buffer and buffer in new tab, respectively.
+
Those commands will open a new scratch buffer in a new vertical window, and
+
a new scratch buffer in a new tab page, respectively.
-
## Make it more "vimmy" citizen
+
## Make it a more "vimmy" citizen
-
While our commands `:Scratch` and `:VScratch` are nice, these are still not
-
flexible enough. In Vim we can use modifiers like `:aboveleft` to define exactly
-
where we want window to appear and our current commands do not respect that. To
-
fix it we can simply squash all commands into one:
+
While our commands `:Scratch`, `:VScratch`, and `:TScratch` are nice, they are
+
still not flexible enough. In Vim we can use modifiers like [`:h
+
:aboveleft`][h-aboveleft] to define exactly where we want new windows to appear
+
and our current commands do not respect that. To fix this problem, we can
+
simply squash all the commands into one:
```vim
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
-
just wanted UX to be minimal).
+
just wanted the UX to be minimal).
## Make it powerful
-
In the form I have described it above it have been in `$MYVIMRC` for some time,
-
but after that I have found [Romain Lafourcade's snippet][redir] that provided
-
one additional feature - it allowed to open our scratch with output of Vim
-
command or shell command. My first thought was - hey, I know that, but I know I
-
can make it better! So we can crate simple VimL function (which is mostly copied
-
from romainl snippet, but few updates):
+
This has been in my `$MYVIMRC` for some time in the form described above until
+
I found out [Romain Lafourcade's snippet][redir] that provided one additional
+
feature: it allowed to open a scratch buffer with the output of a Vim or shell
+
command. My first thought was - hey, I know that, but I know I can make it
+
better! So we can write a simple VimL function (which is mostly copied from
+
romainl's snippet, with a few improvements):
```vim
function! s:scratch(mods, cmd) abort
···
The main differences are:
-
- Special case for empty command, it will just open empty buffer
-
- Use of `is#` instead of `==`
-
- Use of `:h execute()` instead of `:redir`
+
- special case for empty command, it will just open an empty buffer,
+
- use of `is#` instead of `==`,
+
- use of `:h execute()` instead of `:redir`.
-
As it is quite self-contained and (let's be honest) to specific for `$MYVIMRC`
-
now we can can extract it to its own location in `.vim/plugin/scratch.vim` (or
-
respectively `./config/nvim/plugin/scratch.vim` for NeoVim), but to do so
-
properly we need one additional thing, command to prevent file from being loaded
-
twice. So in the end we have file like:
+
As it is quite self-contained and (let's be honest) too specific for `$MYVIMRC`
+
we can can extract it to its own location in `plugin/scratch.vim`, but to do so properly we need
+
one additional thing, a command to prevent the script from being loaded twice:
```vim
if exists('g:loaded_scratch')
···
## To boldly go…
Now my idea was, hey, I use Vim macros from time to time, and these are just
-
simply list of actions stored in Vim register. Maybe it would be nice to have
-
access to that as well in our command. So just add new branch to our if, that
-
checks if `a:cmd` begins with `@` sign and is only 2 letter long, if so, then
-
set `l:output` to spliced content of the register:
+
simple lists of keystrokes stored in Vim registers. Maybe it would be nice to have
+
access to that as well in our command. So we will just add a new condition that
+
checks if `a:cmd` begins with the `@` sign and has a length of two. If so, then
+
set `l:output` to the spliced content of the register:
```vim
function! s:scratch(mods, cmd) abort
···
endfunction
```
-
This gives us pretty powerful solution where we can use `:Scratch @a` to open
-
content of the register `A` in the scratch buffer, edit it, and yank it back via
-
`"ayy`.
+
This gives us a pretty powerful solution where we can use `:Scratch @a` to open
+
a new scratch buffer with the content of register `A`, edit it, and yank it
+
back via `"ayy`.
## Pluginize
-
Now, when we see how useful it is, it would be shame to keep it for ourselves,
-
let's share this with the big world. In this case we need:
+
Now, it would be a shame to keep such a useful tool for ourselves so
+
let's share it with the big world. In this case we need:
-
- Proper project structure
-
- Documentation
-
- Good catchy name
+
- a proper project structure,
+
- documentation,
+
- a good catchy name.
-
About first two you can read more on `:h write-plugin` and `:h
-
write-local-help` or in any of the bazillion tutorials in the internet.
+
You can find help on the two first topics in [`:h
+
write-plugin`][h-write-plugin] and [`:h write-local-help`][h-write-local-help]
+
or in any of the bazillion tutorials on the internet.
-
About last one I cannot provide much help. I have picked `vim-backscratch`,
-
because I like back scratches (everyone like them) and as a nice coincidence
-
it also has "scratch" in the name.
+
Finding a good name is something I can't help you with. I have picked
+
`vim-backscratch`, because I like back scratches (everyone likes them) and, as
+
a nice coincidence, because it contains the word "scratch".
## Summary
-
Creating plugins for Vim is easy, but not every one functionality need to be a
-
plugin from the day one. Start easy and small. If something can be done by a
-
simple command/mapping, then it should be it at first. If you find it really
-
useful then, and only then, you should think about making it into plugin. Whole
-
process described in this article wasn't done in week or two. The step *Make it
-
more "vimmy" citizen* took about a year before I found romainl script on IRC. I
-
didn't need anything more, so take your time.
+
Creating plugins for Vim is easy, but not every functionality needs to be
+
a plugin from day one. Start easy and small. If something can be done with
+
a simple command/mapping, then it should be done with a simple command/mapping
+
at first. If you find your solution really useful, then, and only then, you
+
should think about turning it into a plugin. The whole process described in this
+
article didn't happen in a week or two. It took me about a year to reach the step
+
*Make it a more "vimmy" citizen*, when I heard about romainl's script on IRC.
+
I didn't need anything more, so take your time.
Additional pro-tips:
-
- Make it small, big plugins will require a lot of maintenance, small plugins
-
are much simpler to maintain
-
- If something can be done via command then it should be made as a command, do
-
not force your mappings on users
+
- make it small, big plugins will require a lot of maintenance, small plugins
+
are much simpler to maintain,
+
- if something can be done via a command then it should be made as a command,
+
do not force your mappings on users.
[scratch]: https://github.com/hauleth/vim-backscratch
-
[no code]: https://github.com/kelseyhightower/nocode
+
[nocode]: https://github.com/kelseyhightower/nocode
[dadbod]: https://github.com/tpope/vim-dadbod
+
[redir]: https://gist.github.com/romainl/eae0a260ab9c135390c30cd370c20cd7
+
[h-write-plugin]: https://vimhelp.org/usr_41.txt.html#write-plugin
+
[h-write-local-help]: https://vimhelp.org/usr_41.txt.html#write-local-help
+
[h-special-buffers]: https://vimhelp.org/windows.txt.html#special-buffers
+
[h-unlisted-buffer]: https://vimhelp.org/windows.txt.html#unlisted-buffer
+
[h-aboveleft]: https://vimhelp.org/windows.txt.html#%3Aaboveleft