last updated: October 16, 2022

5 minute read

Five Interesting Things From my Neovim Config

Although I'm no expert in Neovim, I've found a few interesting solutions for the problems I've encountered when switching from VSCode to Neovim. For those new and old to Neovim, I hope I can provide some value as you experiment with your own config.

1. Absolute Imports in Lua

Importing another module into your Lua code can be a little confusing, to say the least. For example, if you search how to import a module from a parent directory, you might come across this stackoverflow post with the following accepted answer:

package.path = package.path .. ";../?.lua"

For context, Lua keeps a list of semicolon-separated directories to search when resolving a require, and stores it in a global variable called package.path. To import a module from a parent, the solution above suggests updating the package.path to include the parent directory.

This works fine for importing directly from the parent directory, but you can imagine how poorly this scales.

As it turns out, Lua in Neovim helps us out by automatically adding its own runtime path (i.e. ~/.config/nvim/lua) to the package.path! This means that with a filetree like so:

.
└── nvim/
├── lua/
│ ├── settings/
│ │ └── barbar.lua
│ └── shared/
│ └── helpers.lua
└── init.lua

We can import the shared helpers in helpers.lua to our barbar.lua using the following code:

-- barbar.lua
local helpers = require("shared.helpers")

No need to manually update your package.path, and no need for relative imports either - just include the entire path starting from ~/.config/nvim/lua

2. Sending Neovim Keys Though iTerm2

When I first switched from VSCode to Neovim, one of the greatest pain points was relearning all the shortcuts I became so used to: cmd+s, cmd+p, cmd+z etc. Unfortunately, as pointed out in this stackoverflow answer, you can only remap the command key with MacVim, not Neovim. Luckily, there's another alternative: using iTerm2.

With iTerm2, you can set remaps with the command key that send arbitrary key inputs to the terminal. In other words, you can set up a remap like cmd+s to send the key inputs :w! A few gotchas to keep in mind:

  1. The iTerm2 equivalent of <cr> is \n. This is mostly useful when submitting a Neovim command, like :w\n
  2. To remap the control key, say ctrl+x, use the form \<C-x>

You can add remaps by going to iTerm2 -> Preferences -> Keys and clicking the + sign at the bottom left. For the action, select Send Text with "vim" Special Chars, and you should be good to go!

3. Shared Settings For a Barebones Config

When setting up a remote server, it can be a pain to get your Vim config up and running – especially if you use packages with their own installation processes (i.e. treesitter, coc, etc.). For remote systems where I mostly edit server config files, I've come to use a barebones Vim config with sensible defaults and zero external packages. Since nearly all of my options and a good chunk of my remaps can be shared between a barebones and full-blown config, I decided to take advantage of the overlap by sharing files between the two. Take a look at the following file structure:

.
└── nvim/
├── lua/
│ ├── barebones/
│ │ ├── init.lua
│ │ ├── options.lua
│ │ └── remaps.lua
│ ├── settings/
│ │ ├── plugins.lua
│ │ ├── functions.lua
│ │ ├── options.lua
│ │ └── remaps.lua
│ └── shared/
│ ├── helpers.lua
│ ├── options.lua
│ └── remaps.lua
└── init.lua

This file tree may look a bit weird, but it works great. Ignoring the barebones directory, this is just a normal Neovim config - an init.lua at the root, and some settings to import. In other words, by default, I use the feature-complete config's init.lua and settings. In the barebones directory, I added a second init.lua, one which can be sourced with:

nvim -u ~/.config/nvim/lua/barebones/init.lua

Let's take a look at the two init.luas

-- nvim/init.lua
local h = require("shared.helpers")
-- remap leader before importing remaps that use it
h.map("", "<space>", "<nop>")
h.let.mapleader = " "
-- settings unique to the feature-complete config
require("settings.plugins")
require("settings.functions")
require("settings.remaps")
require("settings.options")
require("shared.options")
require("shared.remaps")

and

-- nvim/lua/barebones/init.lua
local h = require("shared.helpers")
-- remap leader before importing remaps that use it
h.map("", "<space>", "<nop>")
h.let.mapleader = " "
-- settings unique to the barbones config
require("barebones.options")
require("barebones.remaps")
require("shared.options")
require("shared.remaps")

Each config imports its own unique settings, along with the shared settings for both to use. It's a setup I've come to really enjoy working with.

4. Remaps For Hard-to-Reach Keys

I've always had trouble hitting the keys on the top row of the keyboard, especially the pinkey-keys to the right of 0: -, _, =, +. To make these keys a bit easier to use, I have two types of solutions: snippets and native Neovim remaps.

For snippets, I use coc-snippets with the following coc-settings.json:

// ~/.config/nvim/coc-settings.json
{
"snippets.textmateSnippetsRoots": ["path/to/.config/nvim/snippets"]
}

This sources custom snippets from the snippets directory in the root of your config. I'm used to writing VSCode-style snippets, so I use the textmate style with the following code:

// ~/.config/nvim/snippets/misc.code-snippets
{
"plus": {
"prefix": "uu",
"body": ["+"]
}
}

This works wonders with using the + key, but snippets don't quite work for the other hard-to-reach keys. Since snippets are triggered by a prefix like hh, the prefix must be separated from any characters before it with a space. Unfortunately, there are many situations where you wouldn't want a space before the character. Say we want to create a snippet for =, we can see that a trigger prefix won't work for cases like prop={prop}. Instead, for -, _, and =, I remap the control key with a native Neovim remap:

inoremap <C-f> =
inoremap <C-g> -
inoremap <C-b> _

Personally, I've had great results with these two types of solutions, and I'd highly recommend trying them out.

5. Search by Whole Word, Case Sensitive, or Both

One of my favorite search-related features in VSCode was the ability to search by whole word, case, or both. Luckily, with the right keymappings, this isn't too hard to recreate in Neovim.

# case sensitive
noremap <leader>/c /\C<left><left>
# whole word
noremap <leader>/w /\<\><left><left>
# case sensitive and whole word
noremap <leader>cw /\<\>\C<left><left><left><left>

To replace multiple results in a single file, highlight your results with / or one of the commands above, type cgn, enter the replacement, and press escape. Go to the next entry with n as normal, and do the same replacement with ..

you might also like:

React Suspense in three different architectures

August 17, 2023

Unpacking React's most versatile API

software eng
nextjs
react
server comps
© elan medoff