The Complete NeoVim Configuration Guide for Developers - Part 2

The Complete NeoVim Configuration Guide for Developers - Part 2

Before starting, I recommend reviewing the first part, where I demonstrated how to set up Neovim from scratch, starting with installation and configuring the console, and finishing with a range of essential plugins for coding:

The Complete NeoVim Configuration Guide for Developers - Part 1
Neovim (nvim) is essentially Vim, but improved, rewritten, and has attracted the majority of the original editor’s community. This project has significantly surpassed the original Vim in GitHub statistics (34k vs 75k stars as of 2024). So, if you decide to start programming in the console in 2024, Neovim is

The material below is best read after going through and following the steps described in the first part. However, if you already have a configured setup and need to add any of the plugins described here, you’ll be able to do so without any issues: I’ve made an effort to describe each plugin independently in separate sections.

Before starting with the editor configuration, I suggest we fine-tune our console—WezTerm, which we set up and configured in the first part.

I’m demonstrating the setup for both the editor and console on Mac, so the following console adjustments are intended for macOS users. If you’re on Linux, you likely won’t encounter the console issues addressed below, and you can go straight to plugin configuration.

Mapping the Command key for use with WezTerm and Neovim

While working, I realized that mapping various combinations to the Command key is much more convenient for me. For example, by default in Vim/Neovim, you need to press Control + w to move to an adjacent window, which, to put it mildly, isn’t very comfortable. It’s much more convenient to remap all Control combinations to Command. Setting up combinations in Neovim is easy; this is typically done with the command:

vim.api.nvim_set_keymap(mode, combination, command, options...)

The peculiarity of WezTerm, like most other macOS consoles, is that they don’t transmit combinations to the editor in a readable format (if you know an easier way to do this, feel free to comment). The method I use works like this: we bind all the necessary combinations as UTF-8 characters in the WezTerm config, which will be sent to Neovim. Then, in the Neovim config, we bind these combinations to the transmitted UTF-8 characters.

Configuration in WezTerm:

--- cmd+keys that we want to send to neovim.
local super_vim_keys_map = {
    -- Command + s: saving file
	s = utf8.char(0xAA),
    -- Command + e: scroll downwords (ctrl + e analog)
	e = utf8.char(0xAB),
    -- Command + y: scroll upwords (ctrl + y analog)
	y = utf8.char(0xAC),
    -- Command + h: go to prev tab
	h = utf8.char(0xAD),
    -- Command + l: go to next tab
	l = utf8.char(0xAE),
    -- Command + w: close current tab
	w = utf8.char(0xAF),
    -- Command + t: find file toogle (NvimTree)
	t = utf8.char(0xA1),
}

local function bind_super_key_to_vim(key)
	return {
		key = key,
		mods = 'CMD',
		action = wezterm.action_callback(function(win, pane)
			local char = super_vim_keys_map[key]
			if char and is_vim(pane) then
				-- pass the keys through to vim/nvim
				win:perform_action({
					SendKey = { key = char, mods = nil },
				}, pane)
			else
				win:perform_action({
					SendKey = {
						key = key,
						mods = 'CMD'
					}
				}, pane)
			end
		end)
	}
end

local keys = {
    ...,
    
    bind_super_key_to_vim('s'),
    bind_super_key_to_vim('h'),
    bind_super_key_to_vim('l'),
    bind_super_key_to_vim('w'),
    bind_super_key_to_vim('e'),
    bind_super_key_to_vim('y'),
    bind_super_key_to_vim('t'),
    bind_super_key_to_vim('['),
    bind_super_key_to_vim(']'),
    bind_super_key_to_vim('n'),
    bind_super_key_to_vim('m')
}

config.keys = keys

return config

The full configuration code can be found here.

Then we map these UTF-8 symbols on the Neovim side. Create a Lua file where we’ll define all the mappings—combinations.lua:

local keymap = vim.api.nvim_set_keymap
local default_opts = {noremap = true, silent = true}

-- cmd+s: save file
keymap("n", "<Char-0xAA>", "<cmd>write<cr>", default_opts)
-- cmd+e: scroll downwords
keymap("n", "<Char-0xAB>", "<C-e>", default_opts)
-- cmd+e: scroll upwords
keymap("n", "<Char-0xAC>", "<C-y>", default_opts)
-- cmd+h: next tab
keymap("n", "<Char-0xAD>", "<cmd>BufferPrevious<cr>", default_opts)
-- cmd+l: last tab
keymap("n", "<Char-0xAE>", "<cmd>BufferNext<cr>", default_opts)
-- cmd+w: close current tab
keymap("n", "<Char-0xAF>", "<cmd>BufferClose<cr>", default_opts)
-- cmd+t: find file toogle 
keymap("n", "<Char-0xA1>", "<cmd>NvimTreeFindFile<cr>", default_opts)

-- cmd+1: open left bar  
keymap('n', '<Char-0xA4>', '<cmd>NvimTreeToggle<cr>', default_opts)

Now, include this file in the require section of init.lua:

...
require"combinations"

Now, restart Neovim and check: if you open any file and press Command + t, Neovim will automatically navigate you to that file in the file tree.

Now, if you go back to the file and press Command + e, Neovim will start scrolling down; Command + y will scroll up. Command + s will save the file, and Command + h / Command + l will navigate between tabs within Neovim. Now we can move on to plugin configuration.

Telescope - search by files and content

The first plugin I use constantly is Telescope. Currently, it’s the most popular plugin for searching files and content. I’ve been using it for a couple of years now, and I can say that in most cases, the plugin performs its tasks excellently.

For searching within file content, the plugin uses the FZF command-line search utility. Before moving on to plugins, we need to install it:

brew install fzf

And after installation, load it into the current terminal session:

source <(fzf --zsh)

Then, in the same session, open Neovim and create the file telescope_config.lua:

-- Use vim.keymap.set instead of vim.api.nvim_set_keymap
local keymap = vim.keymap.set

-- Map <leader>f to fzf-lua's file search
keymap("n", "<leader>f", require("fzf-lua").files, { desc = "Fzf files" })

-- Map <leader>g to fzf-lua's live grep
keymap("n", "<leader>g", require("fzf-lua").live_grep, { desc = "Fzf live grep" })

Add the necessary plugins and configuration in init.lua:

...

-- Telescope
Plug('nvim-lua/plenary.nvim') --for fzf
Plug('nvim-telescope/telescope.nvim', { [ 'tag' ] = '0.1.4' })
Plug('ibhagwan/fzf-lua', {['branch'] = 'main'})


...
require"telescope_config"

Restart Neovim, skip the error by pressing Enter, and run :PlugInstall. After installation, restart again. Now, press the \ + f combination, and you’ll see the file search window:

Then, press the \ + g combination, and you’ll see the content search window:

Autosession

Autosession is a plugin that adds a familiar feature—it remembers which files were open before closing Neovim and automatically opens them the next time Neovim is launched.

Create the file autosession.lua:

require'auto-session'.setup {
  suppressed_dirs = { "~/", "~/Projects", "~/Downloads", "/"},
  auto_restore_last_session = true
}

Then, in init.lua, include the file and its configuration:

Plug('rmagatti/auto-session')
...
require"autosession"

Install, restart, and check:

Comment - code commenting

Now let’s add another basic feature—the ability to comment and uncomment code. For this, I use the Comment plugin. It works right out of the box and supports Treesitter, giving us compatibility with most programming languages.

Create the file comment_config.lua:

require('Comment').setup()

Then, include the plugin and its configuration in init.lua:

...
Plug('numToStr/Comment.nvim')
...
require("comment_config")

Install, restart, and then, by selecting any code section in visual mode (Shift + V + ↑/↓) and using the gc combination, you can comment and uncomment the selected code section:

Trouble

Neovim has its own built-in tools for displaying code errors, but accessing them is, to put it mildly, not straightforward.

Trouble is a plugin that adds an interface for displaying all errors in the open project. Trouble supports the LSP server, allowing us to see all errors in a familiar format with clear comments.

Create the configuration file trouble_config.lua:

require('trouble').setup()

vim.keymap.set("n", "<leader>xx", function() require("trouble").toggle() end)
vim.keymap.set("n", "<leader>xw", function() require("trouble").toggle("workspace_diagnostics") end)
vim.keymap.set("n", "<leader>xd", function() require("trouble").toggle("document_diagnostics") end)
vim.keymap.set("n", "<leader>xq", function() require("trouble").toggle("quickfix") end)
vim.keymap.set("n", "<leader>xl", function() require("trouble").toggle("loclist") end)
vim.keymap.set("n", "gR", function() require("trouble").toggle("lsp_references") end)

init.lua:

...
Plug('folke/trouble.nvim')

...
require"trouble_config"
...

The plugin comes with a set of key combinations you can use out of the box, but I’ve remapped the interface opening to Command + [.

To do the same, go back to the WezTerm config and add another combination to open Trouble with Command.

wezterm.lua:

    -- Command + [: open trouble window
	['['] = utf8.char(0xA2),
    -- Command + ]: close trouble window 
	[']'] = utf8.char(0xA3),

    ...

    bind_super_key_to_vim('['),
    bind_super_key_to_vim(']'),
    ...

Then, add these combinations in the Neovim config

combinations.lua:

...
-- cmd+[: get troubles
keymap("n", "<Char-0xA2>", "<cmd>Trouble diagnostics toggle<cr>", default_opts)
-- cmd+]: close troubles
keymap("n", "<Char-0xA3>", "<cmd>TroubleClose<cr>", default_opts)
...

Restart and check:

git-blame - interactive commit history

git-blame is a minimalist plugin that does the same thing we’re used to seeing in an IDE when we open annotations in the code: it displays the author and commit message.

The plugin requires no configuration, and to enable it, we just need to add the plugin in init.lua:

Plug 'f-person/git-blame.nvim'

And install it. Then, hover over any line in a project with Git, and you’ll see:

Conclusion

With this, the basic configuration setup is complete. I’ve covered the most essential plugins that provide the fundamental features we’re used to in various IDEs.

My configuration is somewhat broader than what I showed above. For example, it includes plugins for working with tests, advanced Git handling, Git conflict resolution, more convenient search, and code debugging. However, to keep this concise, I’ll describe these plugins and their setup in separate articles.

You can find my full, constantly updated configuration here:

GitHub - itxor/go-rust-nvim-config at poltora.dev
Contribute to itxor/go-rust-nvim-config development by creating an account on GitHub.

WezTerm configuration:

wezterm_config/wezterm.lua at main · itxor/wezterm_config
Contribute to itxor/wezterm_config development by creating an account on GitHub.

I also run the telegram channel (en) with a digest of materials on Go:

https://t.me/digest_golang

And a personal Telegram channel (ru), where I share various notes, tools, and my experiences:

https://t.me/junsenior

Be sure to subscribe for new materials on this site as well; updates will be coming more frequently soon! 😄

Subscribe to vpoltora

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe