···11+---
22+title: "Writing Vim Plugin"
33+date: 2019-11-04T18:21:18+01:00
44+description: |
55+ Article about writing Vim plugins, but not about writing Vim plugins. It is
66+ how to concieve plugin, how to go from an idea to the full fledged plugin.
77+tags:
88+ - vim
99+ - viml
1010+---
1111+1212+While there are many "tutorials" for writing plugins in Vim, I hope this one
1313+will be a little bit different from what is out there, because it won't be
1414+about writing plugin *per se*. If you want to find information about that then
1515+you should check out [`:h write-plugin`][h-write-plugin]. I want this article to be about how
1616+plugins come to life, using my own experience on writing
1717+[`vim-backscratch`][scratch] as an example.
1818+1919+## Problem
2020+2121+All plugins should start with a problem. If there is no problem, then there
2222+should be no code, as there is no better code than [no code][nocode]. In this
2323+case, my problem was pretty trivial: I wanted a temporary buffer that would let
2424+me perform quick edits and view SQL queries while optimising them (and run them
2525+from there with Tim Pope's [dadbod][dadbod]).
2626+2727+## "Simple obvious solution"
2828+2929+Now that we have defined the problem, we need to try the first possible solution.
3030+In our case, it is opening a new buffer in a new window, edit it, and then close it
3131+when no longer needed. It is simple in Vim:
3232+3333+```vim
3434+:new
3535+" Edits
3636+:bd!
3737+```
3838+3939+Unfortunately this has bunch of problems:
4040+4141+- if we forgot to close that buffer, then it will hang there indefinitely,
4242+- running `:bd!` in the wrong buffer, can have unpleasant consequences,
4343+- this buffer is still listed in `:ls`, which is unneeded (as it is only
4444+ temporary).
4545+4646+## Systematic solution in Vim
4747+4848+Fortunately Vim has solutions for all of our problems:
4949+5050+- the "scratch" section in [`:h special-buffers`][h-special-buffers], which
5151+ solves the first two problems,
5252+- [`:h unlisted-buffer`][h-unlisted-buffer], which solves the third problem.
5353+5454+So now our solution looks like:
5555+5656+```vim
5757+:new
5858+:setlocal nobuflisted buftype=nofile bufhidden=delete noswapfile
5959+" Edits
6060+:bd
6161+```
6262+6363+However that is a long chain of commands to write. Of course we could condense
6464+the first two into a single one:
6565+6666+```vim
6767+:new ++nobuflisted ++buftype=nofile ++bufhidden=delete ++noswapfile
6868+```
6969+7070+But in reality this does not shorten anything.
7171+7272+## Create a command
7373+7474+Fortunately we can create our own commands in Vim, so we can shorten that to a
7575+single, easy to remember command:
7676+7777+```vim
7878+command! Scratch new ++nobuflisted ++buftype=nofile ++bufhidden=delete ++noswap
7979+```
8080+8181+For better flexibility, I prefer it to be:
8282+8383+```vim
8484+command! Scratchify setlocal nobuflisted buftype=nofile bufhidden=delete noswap
8585+command! Scratch new +Scratchify
8686+```
8787+8888+We can also add a bunch of new commands to give us better control over our new
8989+window's location:
9090+9191+```vim
9292+command! VScratch vnew +Scratchify
9393+command! TScratch tabnew +Scratchify
9494+```
9595+9696+Those commands will open a new scratch buffer in a new vertical window, and
9797+a new scratch buffer in a new tab page, respectively.
9898+9999+## Make it a more "vimmy" citizen
100100+101101+While our commands `:Scratch`, `:VScratch`, and `:TScratch` are nice, they are
102102+still not flexible enough. In Vim we can use modifiers like [`:h
103103+:aboveleft`][h-aboveleft] to define exactly where we want new windows to appear
104104+and our current commands do not respect that. To fix this problem, we can
105105+simply squash all the commands into one:
106106+107107+```vim
108108+command! Scratch <mods>new +Scratchify
109109+```
110110+111111+And we can remove `:VScratch` and `:TScratch` as these can be now done via
112112+`:vert Scratch` and `:tab Scratch` (of course you can keep them if you like, I
113113+just wanted the UX to be minimal).
114114+115115+## Make it powerful
116116+117117+This has been in my `$MYVIMRC` for some time in the form described above until
118118+I found out [Romain Lafourcade's snippet][redir] that provided one additional
119119+feature: it allowed to open a scratch buffer with the output of a Vim or shell
120120+command. My first thought was - hey, I know that, but I know I can make it
121121+better! So we can write a simple VimL function (which is mostly copied from
122122+romainl's snippet, with a few improvements):
123123+124124+```vim
125125+function! s:scratch(mods, cmd) abort
126126+ if a:cmd is# ''
127127+ let l:output = []
128128+ elseif a:cmd[0] is# '!'
129129+ let l:cmd = a:cmd =~' %' ? substitute(a:cmd, ' %', ' ' . expand('%:p'), '') : a:cmd
130130+ let l:output = systemlist(matchstr(l:cmd, '^!\zs.*'))
131131+ else
132132+ let l:output = split(execute(a:cmd), "\n")
133133+ endif
134134+135135+ execute a:mods . ' new'
136136+ Scratchify
137137+ call setline(1, l:output)
138138+endfunction
139139+140140+command! Scratchify setlocal nobuflisted noswapfile buftype=nofile bufhidden=delete
141141+command! -nargs=1 -complete=command Scratch call <SID>scratch('<mods>', <q-args>)
142142+```
143143+144144+The main differences are:
145145+146146+- special case for empty command, it will just open an empty buffer,
147147+- use of `is#` instead of `==`,
148148+- use of `:h execute()` instead of `:redir`.
149149+150150+As it is quite self-contained and (let's be honest) too specific for `$MYVIMRC`
151151+we can can extract it to its own location in `plugin/scratch.vim`, but to do so properly we need
152152+one additional thing, a command to prevent the script from being loaded twice:
153153+154154+```vim
155155+if exists('g:loaded_scratch')
156156+ finish
157157+endif
158158+let g:loaded_scratch = 1
159159+160160+function! s:scratch(mods, cmd) abort
161161+ if a:cmd is# ''
162162+ let l:output = []
163163+ elseif a:cmd[0] is# '!'
164164+ let l:cmd = a:cmd =~' %' ? substitute(a:cmd, ' %', ' ' . expand('%:p'), '') : a:cmd
165165+ let l:output = systemlist(matchstr(l:cmd, '^!\zs.*'))
166166+ else
167167+ let l:output = split(execute(a:cmd), "\n")
168168+ endif
169169+170170+ execute a:mods . ' new'
171171+ Scratchify
172172+ call setline(1, l:output)
173173+endfunction
174174+175175+command! Scratchify setlocal nobuflisted noswapfile buftype=nofile bufhidden=delete
176176+command! -nargs=1 -complete=command Scratch call <SID>scratch(<q-mods>, <q-args>)
177177+```
178178+179179+## To boldly go…
180180+181181+Now my idea was, hey, I use Vim macros from time to time, and these are just
182182+simple lists of keystrokes stored in Vim registers. Maybe it would be nice to have
183183+access to that as well in our command. So we will just add a new condition that
184184+checks if `a:cmd` begins with the `@` sign and has a length of two. If so, then
185185+set `l:output` to the spliced content of the register:
186186+187187+```vim
188188+function! s:scratch(mods, cmd) abort
189189+ if a:cmd is# ''
190190+ let l:output = ''
191191+ elseif a:cmd[0] is# '@'
192192+ if strlen(a:cmd) is# 2
193193+ let l:output = getreg(a:cmd[1], 1, v:true)
194194+ else
195195+ throw 'Invalid register'
196196+ endif
197197+ elseif a:cmd[0] is# '!'
198198+ let l:cmd = a:cmd =~' %' ? substitute(a:cmd, ' %', ' ' . expand('%:p'), '') : a:cmd
199199+ let l:output = systemlist(matchstr(l:cmd, '^!\zs.*'))
200200+ else
201201+ let l:output = split(execute(a:cmd), "\n")
202202+ endif
203203+204204+ execute a:mods . ' new'
205205+ Scratchify
206206+ call setline(1, l:output)
207207+endfunction
208208+```
209209+210210+This gives us a pretty powerful solution where we can use `:Scratch @a` to open
211211+a new scratch buffer with the content of register `A`, edit it, and yank it
212212+back via `"ayy`.
213213+214214+## Pluginize
215215+216216+Now, it would be a shame to keep such a useful tool for ourselves so
217217+let's share it with the big world. In this case we need:
218218+219219+- a proper project structure,
220220+- documentation,
221221+- a good catchy name.
222222+223223+You can find help on the two first topics in [`:h
224224+write-plugin`][h-write-plugin] and [`:h write-local-help`][h-write-local-help]
225225+or in any of the bazillion tutorials on the internet.
226226+227227+Finding a good name is something I can't help you with. I have picked
228228+`vim-backscratch`, because I like back scratches (everyone likes them) and, as
229229+a nice coincidence, because it contains the word "scratch".
230230+231231+## Summary
232232+233233+Creating plugins for Vim is easy, but not every functionality needs to be
234234+a plugin from day one. Start easy and small. If something can be done with
235235+a simple command/mapping, then it should be done with a simple command/mapping
236236+at first. If you find your solution really useful, then, and only then, you
237237+should think about turning it into a plugin. The whole process described in this
238238+article didn't happen in a week or two. It took me about a year to reach the step
239239+*Make it a more "vimmy" citizen*, when I heard about romainl's script on IRC.
240240+I didn't need anything more, so take your time.
241241+242242+Additional pro-tips:
243243+244244+- make it small, big plugins will require a lot of maintenance, small plugins
245245+ are much simpler to maintain,
246246+- if something can be done via a command then it should be made as a command,
247247+ do not force your mappings on users.
248248+249249+[scratch]: https://github.com/hauleth/vim-backscratch
250250+[nocode]: https://github.com/kelseyhightower/nocode
251251+[dadbod]: https://github.com/tpope/vim-dadbod
252252+[redir]: https://gist.github.com/romainl/eae0a260ab9c135390c30cd370c20cd7
253253+[h-write-plugin]: https://vimhelp.org/usr_41.txt.html#write-plugin
254254+[h-write-local-help]: https://vimhelp.org/usr_41.txt.html#write-local-help
255255+[h-special-buffers]: https://vimhelp.org/windows.txt.html#special-buffers
256256+[h-unlisted-buffer]: https://vimhelp.org/windows.txt.html#unlisted-buffer
257257+[h-aboveleft]: https://vimhelp.org/windows.txt.html#%3Aaboveleft