Skip to content
← Go back

Did you know about Neovim's exrc?

Published: >

Table of contents

Open Table of contents

Intro

It’s been a while since I looked at External Link Globe 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 External Link Globe 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:

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.

External Link Globe :help exrc External Link Globe :help ‘exrc’

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 External Link Globe fastaction plugin.

image of my fastaction menu

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 External Link Globe flash.nvim and External Link Globe 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 External Link Globe folke/snacks.nvim even replacing the full External Link Globe 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

NameURL
vlang External Link Globe https://vlang.io
exercism External Link Globe https://exercism.org
:help exrc External Link Globe https://neovim.io/doc/user/starting.html#exrc
:help ‘exrc’ External Link Globe https://neovim.io/doc/user/options.html#'exrc'
fastaction External Link Globe https://github.com/Chaitanyabsprip/fastaction.nvim...
flash.nvim External Link Globe https://github.com/folke/flash.nvim/
multicursor.nvim External Link Globe https://github.com/jake-stewart/multicursor.nvim
folke/snacks.nvim External Link Globe https://github.com/folke/snacks.nvim
telescope External Link Globe https://github.com/nvim-telescope/telescope.nvim


Next Post
My first experience with Naya Create