Table of contents
Open Table of contents
Intro
It’s been a while since I looked at vlang, I wanted to touch up on it a bit and see how far the tooling and language has matured. One of my favourite way of learning new languages is to use exercism. Picture LeetCode but with simpler, easier exercises. This is designed to familiarise yourself with the language.
When you are working on an exercise, there’s a few commands you need:
- Download the exercise
- Run the tests
- Submit code
Luckily the exercism
CLI tool handles most of these for you. However, it’s a bit annoying to
context swap to my terminal just to type in exercism submit
in the correct folder, over and over
and over again. Same for running tests. The directory structure is per language per exercise,
something like this:
.
├── hello-world
│ ├── ...
│ ├── run_test.v
│ └── hello-world.v
├── leap
│ ├── ...
│ ├── run_test.v
│ └── leap.v
├── scrabble-score
│ ├── ...
│ ├── run_test.v
│ └── scrabble-score.v
└── .nvim.lua 👀
The workflow would be to “cd correctly”, “run test”, “submit file”. Sometimes the exercise names are similar, sometimes you forget to cd properly and most of the time I just want to go fast!!
So how can we make this easier?
Neovim’s exrc
This is the built-in method for managing project-local configuration. When you navigate to your project and start a Neovim session it will load this file as well. This allows you to create simple project-scoped keybindings for common tasks that you need to do, without leaving your editor of course.
So what do we need to do? The eagled-eye readers might have already noticed that in the file tree earlier
I have a .nvim.lua
file. Think of this as your project-scoped init.lua
. Let’s have a look at what
we have in the file.
local vlang_augroup = vim.api.nvim_create_augroup('exercism/vlang', { clear = true })
vim.api.nvim_create_autocmd('FileType', {
group = vlang_augroup,
pattern = 'v',
callback = function(args)
vim.keymap.set('n', '<leader>er', ':!v run %<CR>', {
desc = 'V: run file',
buffer = args.buf,
})
vim.keymap.set('n', '<leader>et', ':!v -stats test expand("%:h")<CR>', {
desc = 'V: run tests',
buffer = args.buf,
})
vim.keymap.set('n', '<leader>e<CR>', ':!exercism submit %<CR>', {
desc = 'Exercism: Submit current file to Exercism',
buffer = args.buf,
})
vim.notify("Loaded exercism/vlang project keymaps.", vim.log.levels.INFO)
end,
})
Great! Since my <leader>e
is luckily unmapped, I will just take over this key for exercism
based projects. I think the more ideal way would be to have a convention where your key actually
means project-scoped-commands
. I’m sure you can come up with a great key for yourself.
But can we do better?
Introducing fastaction
Plugins minimalist readers can stop here, you’ve seen enough. If you’re still here then I would like to introduce you to the fastaction plugin.
QRD: Popup menu, key = first character that makes sense, customisable of course.
It’s designed to work with LSP code actions (e.g. gopls: organise import
).
In the image shown there I have mapped my custom popup menu to
flash.nvim and multicursor.nvim things.
You can probably see where I’m going with this. Project-scoped entries on top fastaction with custom keys. But before we see the actual code and mapping I’d like to segue into making a case for another great action key we all use but overlook for normal mode!
Glorious <C-Space>
If you’ve ever used VSCode or Intellij before this will most likely be one of the first keybind you learn on the editor. Because why else would you use a smart editor if you don’t use it’s smart features?
The limitations of <C-Space>
in those editors is that we’re always in insert mode
but we actually have so many other modes in Vim!
PSA: map your damn pop-up menu to <C-Space>
in normal mode!
Putting it all together
The last piece of logistics we need to figure out is how
we can override the custom menus we already have with the project based ones
that we want to override? Luckily in Lua not much is sacred, just drop the local
keyword and capitalise your variable name (optional) and you are officially good to go.
Menus = {
items = {
" hop",
" jump",
" remote",
" treesitter",
" treesitter search",
" continue",
" all <cword>",
" all search term",
" restore",
" prev match",
" next match",
" operator in range",
" buffer pick",
},
fns = {
... -- omitted for brevity, assume `function() ... end` index relative entries
},
keys = {
[" hop"] = "<C-Space>",
[" jump"] = "j",
[" all <cword>"] = "m",
[" all search term"] = "s",
[" restore"] = "v",
[" prev match"] = "N",
[" next match"] = "n",
[" operator (iwap)"] = "S",
},
}
Menus.append = function(item, fn, key)
if item ~= nil and fn ~= nil then
table.insert(Menus.items, item)
table.insert(Menus.fns, fn)
end
if key ~= nil then
Menus.keys[item] = key
end
end
There is definitely a better way to do this, as a Lua hobbyist this just werks™️on my machine.
Here is our new glorious .nvim.lua
file:
local term_ops = { auto_close = false }
local entries = {
{
" v:run",
function() Snacks.terminal(("v run %s"):format(vim.fn.expand("%")), term_ops) end,
"v",
},
{
" v:test",
function() Snacks.terminal(("v -stats test %s"):format(vim.fn.expand("%:h")), term_ops) end,
"t",
},
{
" submit",
function() Snacks.terminal(("exercism submit %s"):format(vim.fn.expand("%")), term_ops) end,
"<CR>",
},
}
for _, value in pairs(entries) do
Menus.append(value[1], value[2], value[3])
end
I massively enjoy folke/snacks.nvim even replacing the full telescope workflow with it. Having a small subsection in a blog about snacks won’t do it justice so I’ll leave it up to you to figure out.
Here is our finished product:
Links
Name | URL |
---|---|
vlang | https://vlang.io |
exercism | https://exercism.org |
:help exrc | https://neovim.io/doc/user/starting.html#exrc |
:help ‘exrc’ | https://neovim.io/doc/user/options.html#'exrc' |
fastaction | https://github.com/Chaitanyabsprip/fastaction.nvim... |
flash.nvim | https://github.com/folke/flash.nvim/ |
multicursor.nvim | https://github.com/jake-stewart/multicursor.nvim |
folke/snacks.nvim | https://github.com/folke/snacks.nvim |
telescope | https://github.com/nvim-telescope/telescope.nvim |