Local Linking in Vim

Most of my writing is done using markdown.


I write notes and journals and blog posts and documentation and ideas and lots of other content and prose in markdown.


Markdown is the platonic ideal format for note taking. The one thing it's missing when compared to the more marketed note taking products is local linking. The ability to point to a file sitting in the filesystem and be able to open that file using the link is a pretty common feature in most note taking software.


The reason local linking isn't available in markdown is because it's a markup syntax only. It has nothing to do with sharing or connecting data. This keeps the environment lightweight and extensible.


Now, there are numerous ways I can think of to implement linking and probably just as many that I haven't thought of. This is the one I wanted to try.


In short, the format for a local link is as follows:


Double square brackets indicate a link [[somefile.md]]


The path is relative to the file the link is in. ie. to link to a file in a sibling directory, one would use [[../sibling/file.md]]


To render the links in a cleaner way, I use vim concealments to hide the square brackets, the relative path, and the file extension. So [[../somefile.md]] would be rendered as somefile


To avoid confusing a link as plain text, custom vim highlight groups apply syntax styles to differentiate them.


~~


First, write and source the conceal syntax file.


Create a concealments.vim file next to your init.lua or init.vim and paste the following:


" match [[ if followed by one or more characters and ]]

syn match Normal '\v(\[\[)(.+\]\])@=' conceal

" match ]] if preceded by one or more characters and [[

syn match Normal '\v(\[\[.+)@<=\]\]' conceal

" match [[../ if followed by filename.md

syn match Normal '\v(\[\[.*\/)(.+\.md)@=' conceal

" match .md if surrounded by [[]]

syn match Normal '\v(\[\[.*)@<=(\.md)(\]\])@=' conceal


This handles concealing the brackets and extraneous parts of the file path. Lookup vim regex if you want to understand these patterns better.


Next, source the concealments file.


I found it best to only source this file when opening a markdown file. To do that, paste the following in your init.lua


vim.api.nvim_create_autocmd('BufEnter', {pattern = {".md", ".mdx"}, command = ":source path-to-config/concealments.vim"})


This creates an auto-command to source the syntax file that triggers when a new markdown buffer is created.


Now you should be able to create a new markdown file, enter a path to a markdown file surrounded in square brackets and it should conceal everything except the file name.


I won't go over custom highlight groups here because that's a messy topic, but I may write about them sometime in the future.


The final step is opening the linked files. Again, there are many ways to do this and if you have a preferred method, use it. I simply have a keymapping for gf that is mapped to :edit <cfile><cr>. This will open whatever link is under my cursor when I press gf in normal mode.


This is all that is needed to have a somewhat nice looking linking system for markdown in vim.


A last tip I will include is how to insert the exact path of a file without having to memorize it.


Vim has a sub-mode for insert mode called "autocompletion mode". This is triggered by pressing <C-x> (Control-X) while in insert mode. To autocomplete a file path, also press <C-f> (Control-F) immediately after. Make sure the cursor is on a new line or has a space before it. This will give a dropdown of all files in the current working directory. <C-n> or <tab> will cycle through the options and <CR> (Enter) will insert that file name where your cursor is. To link to a file from a parent directory, start by typing ../ then <C-x><C-f>. This will show files up one level and can of course be extended to any directory level like so ../../../


This interface isn't ideal. I would really like fzf with ripgrep to find files to insert, but I haven't yet implemented it.


Now, this isn't some revolutionary vim magic. It's just concealments and some cool key commands. If you can think of a better way to do this, reach out to me and let me know!


Here is where I will leave you.