ovelny

Switching from joplin to vimwiki

I've been using Joplin for a while now, and have been mostly happy with it. It features a simple but sufficient Vim keyboard mode, notes are encrypted, and images are easily pasted in Markdown.

All in all this is great, but the following pain points made me reconsider this solution:

And last but certainly not least: Electron. The only apps built with it that didn't end up eating a frightening amount of RAM on my systems are VSCode and Discord, no other exceptions. I'm not too eager to sacrifice this just to simply type text.

Vimwiki has been around for a while and has always intrigued me. As the name implies, this plugin allows you to maintain your own wiki via linked text files, with Markdown support. But I would also need the following features to truly make it similar to Joplin:

Seems like some kind of deranged dream to expect all of these features from vim, but this is very doable with the following plugins:

Note encryption will be done via some vim scripting. Let's roll and set up everything!

Plugins installation

First things first, let's install all the plugins with your .vimrc:

Plug 'plasticboy/vim-markdown', { 'for': 'markdown' }
Plug 'ferrine/md-img-paste.vim', { 'for': 'markdown' }
Plug 'vimwiki/vimwiki'
Plug 'iamcco/markdown-preview.nvim', { 'do': { -> mkdp#util#install() }, 'for': ['markdown', 'vim-plug']}
Plug 'junegunn/goyo.vim'

Goyo is optional but provides an amazing, minimalist interface for notes—I highly recommend it. Note that the following snippet is based on vim-plug: adapt it accordingly if you use another plugin manager, and run the appropriate install command (:PlugInstall in my case).

Setting up encryption with Markdown

Let's populate our .vimrc with a few more settings to get seamless encryption. First we're going to assign the .md.gpg extension to the markdown filetype, so vim can apply highlighting and other features like it normally would:

au BufEnter *.md.gpg setlocal filetype=markdown

The seamless editing of gpg files will be handled by an edited mix of the following scripts:

Be aware that nothing is foolproof here, as vim uses temporary files when dealing with data from external programs. While those scripts harden everything as much as possible, you still might be able to retrieve some of your data with forensics—which is an accepted risk in my case.

One issue with those scripts used in conjunction with vimwiki is the management of buffers: vimwiki will open tons of them, and closing / saving them individually can prevent remaining files to remain tightly encrypted at rest.

The most practical solution I found is to always quit the wiki entirely, trust its autosaving feature and treat the bunch of buffers as a unique group this way. As such and to enforce this, I opted to remap :wq, :q, and disable :w and :q for the .md.gpg filetype exclusively:

au VimEnter *.md.gpg execute ":cabbrev wq wqa"
au VimEnter *.md.gpg execute ":cabbrev q qa"
au VimEnter *.md.gpg execute ":cabbrev w <Nop>"
au VimEnter *.md.gpg execute ":cabbrev wa <Nop>"

Some people will find this to be cursed, and if you hate autosaving I feel you. On my end that solution works perfectly but I'm always open to suggestions!

Next comes the automatic handling and editing of gpg files:

" First make sure nothing is written to ~/.viminfo or backups while editing
" an encrypted file.
set backupskip+=*.gpg
set viminfo=

augroup encrypted
  au!
  " Disable swap file, undo file and backups, and set binary file format
  " before reading the file
  autocmd BufReadPre,FileReadPre *.gpg
    \ setlocal noswapfile noundofile nobackup bin
  " Decrypt contents after reading the file, reset binary file format
  " and run any BufReadPost autocmds matching the filename without .gpg
  autocmd BufReadPost,FileReadPost *.gpg
    \ execute "%!gpg --decrypt --default-recipient-self 2>/dev/null" |
    \ setlocal nobin |
    \ execute "redraw!" |
    \ execute "doautocmd BufReadPost " . expand("%:r")
  " Set binary file format and encrypt contents when leaving vim
  autocmd Vimleave *.gpg
    \ setlocal bin |
    \ bufdo execute "%!gpg --encrypt --armor --default-recipient-self"
  " Contrary to the original scripts, we are not handling an undo command
  " to revert encryption in buffer: vimwiki auto-saves contents when quitting,
  " so we rather leave it at that and disable :w and :wa for .md.gpg files.
  " If you don't like auto-saving, sorry!
augroup END

You will need to have a GPG keypair present on your system for this to work. If you don't already have one, here's how to do it:

If you'd rather go crazy with ECC and Ed25519 (which I encourage you to do), you can do the following instead:

This part should be working now! Create a .md.gpg file, open it with vim, insert something and save it. If you cat the file, this should return the encrypted, ASCII-armored content which roughly looks like the following:

-----BEGIN PGP MESSAGE-----

hQIMA4RrtUt4dcSyAQ/8DAdDoYkJE8d4kq+dsZykX9qR2cEVjy99RLaaeT22HffP
SKc3zKXMz6IDVEx1+KBD0zsDR1EZA6tiOoXwqjZnU/CXwuGpE6yYrIjQZiyVsDhV
zeYupkji0z8JTg8QfAfgW2TvEpctuX7DaXyn26qmg6x1KVhmxDAaaaqQzJIu2dXg
mM8gnU8Qgx0/hPUf9Axm4lTU/iqZLEFOr+ysRn7BX4OSnkfWUIKGOASLqX7l42/+
s8YxKRSsJEAf/vdUxgEABTRKQ7Z/ciMn7Lp8gqz4hhLvgwG7gZz6dYgO5f3F0L+h
oMJnTKOHxkcYPRJODA7jclYqTapSkHXTs1Gl0/d2vjuzYOS9izs6p+iG5eXaZ93a
83gc4FgVAsxnLPx+M8KmmgQBM2f91NUbYdcA/8sGmzSpHBm7vVhCqm9iZLmqFgtW
C2Ygwek5KTNuQ0kNYaGz88047oaQn6jYTR146YWxZBkTOO/9xsEQxlrwBOVLW9vX
qpnRI6Bj0c7Gnb5eh3TBBKJyfKB1r41PBzAoMCr4LBFNjJhtFQmrcV/h+6kqYpE9
xSju2Ux72H3Nq1UBmGO8u2Or8BgQXRnWmTjaGFKetUac1KuHVdSl6gkTkK2TVSJh
dwMiPavB4hf6onJqGlW9746glNTRdfmHU9Bvsz6iBJ3HV6EOTCG2XLyU2fr6vvXS
UQEWWmozZNWc75PUQBSwXDeLUTZU7n3QG0O2dx6brjxbx0hrv3oYYAersvX3NrPK
mgkSHV0XVcSVvG4uLma1ilDMWKb9+DjiZDt2FvqVFV7E3A==
=zjQv
-----END PGP MESSAGE-----

Open it again with vim, and you should see the plaintext message. Perfect, moving on!

Setting up vimwiki

By default, vimwiki uses its own syntax. Markdown is however supported with a few option changes.

The plugin also needs some specific settings to be set, so let's do both:

set nocompatible
filetypelugin on
syntax on

let g:vimwiki_list = [{'path': '~/wiki/',
                      \ 'syntax': 'markdown', 'ext': '.md.gpg'}]

Now vimwiki will identify .md.gpg as its default filetype, and the directory for your entire wiki will be located at ~/wiki/. Easy enough.

Learning everything about vimwiki is beyond the scope of this post, but we can still mention a few things. First, you need to create an index by running vim and press <Leader>ww.

Edit the document as you like, and press <enter> with your cursor placed on a word of your choosing: you just made a link! If you press <enter> again, you'll open a new file which you can edit to your linking. Press backspace, and you're back to your index.

This hierarchical way of handling files makes vimwiki very suitable for personal notes and even a Zettelkasten system (there is an additional plugin for it). You can learn more about it in the repo's README and vimwiki's wiki.

We're done with setting the plugin, but these are some options I like to tweak:

" Automatically insert a header when creating a new link
let g:vimwiki_auto_header = 1
" Use underscores to replace spaces in the file names
let g:vimwiki_links_space_char = '_'
" Disable all concealing
let g:vimwiki_conceal_onechar_markers = 0
" Disable URL shortening
let g:vimwiki_url_maxsave = 0 
" Don't load vimwiki for markdown files located somewhere else
let g:vimwiki_global_ext = 0
" Bold headers
hi VimwikiHeader1 cterm=bold gui=bold 
hi VimwikiHeader2 cterm=bold gui=bold 
hi VimwikiHeader3 cterm=bold gui=bold 
hi VimwikiHeader4 cterm=bold gui=bold 
hi VimwikiHeader5 cterm=bold gui=bold 
hi VimwikiHeader6 cterm=bold gui=bold

Autocapitalisation

This feature will be quick to implement. The following function will handle autocapitalisation wherever we want to:

func! AutoCapitalisation() 
    augroup SENTENCES 
        au! 
        autocmd InsertCharPre * if search('\v(%^|[.!?]\_s+|\_^\-\s|\_^\*\s|\_^#+\s|\n\n)%#', 'bcnw') != 0 | let v:char = toupper(v:char) | endif 
    augroup END 
endfu

It has been tweaked from the following source and does a really nice job:

(Thanks David Moody for sharing it!)

To enable the function with vimwiki, all we need to do is the following:

autocmd FileType markdown call AutoCapitalisation()

Easily pasting images in Markdown files

The md-img-paste plugin uses xclip to work, so let's install it first:

sudo apt install xclip

The following options can be tweaked to your liking. By default, the plugin will create your image folder at the same location of the currently edited file, which works great for us:

autocmd FileType markdown nmap <buffer><silent> <leader>, :call mdip#MarkdownClipboardImage()<CR>
" there are some defaults for image directory and image name, you can change them
let g:mdip_imgdir = 'images'
let g:mdip_imgname = 'image'

Markdown preview

This plugin works splendidly well out of the box. Just use :MarkdownPreview for a live preview and :MarkdownPreviewStop to stop the service. I only added this shortcut for easy toggling:

autocmd FileType markdown nmap <leader>n :MarkdownPreviewToggle<CR>

It also supports preview of diagrams written with mermaid, katex, and others, really an amazing plugin.

Optional (but amazing): Goyo

Goyo is a vim plugin that provides a clutter-free interface for writing in vim: I really enjoyed using it for a long time now, and nothing else quite matched the experience. I recommend checking out the GitHub repo to get a general feel of the interface.

If you want to use it automatically with vimwiki, add the following line in your .vimrc:

au VimEnter *.md.gpg execute ":Goyo"

Caveats

Switching to vimwiki has a few caveats, at least with my current settings:

But in my use case, I can live with that.

I'm always open to suggestions and my vim scripting is not the best, but so far the experience is really pleasant. Being able to quickly write notes from the CLI with the full power of vim available is way better than any alternative I could find!

TL;DR: just give me the script!

Sure thing: https://gist.github.com/ovelny/72659e841c1dbcee173eb244c8609252

~ Want to leave a comment about this post? You can send me a message on CuriousCat without an account, or reply on Twitter if you like!