Neovim is a Vim fork which aims to improve on its predecessor much like how Vim improved Vi. It was started by a Brazilian developer, Thiago de Arruda, mostly to improve Vim’s concurrency capabilities. It has been 10 years since and it has gotten very popular so far. Meanwhile, one could argue that it also pushed Vim a little into learning a few new tricks. RIP Bram, by the way, his legacy will be remembered for generations to come. In this post I want to share a few tips on configuring Neovim for a nice Linux Kernel development experience.

Introduction

As many of you know, to configure and customize Vim you need to learn Vimscript. From my experience, it isn’t a friendly language. I’m sure I’m not alone in this take because Neovim developers chose Lua for its embedded language. Lua is a very straight forward language, also originated in Brazil. It has some interesting users, such as World of Warcraft user interface and Ginga, a Brazilian middleware for digital TV which introduced me to Lua when I was in college. If you have a vimrc and is not sure of making the leap to a Lua based configuration, you should try out kickstart. For a full introduction, it’s creator — TJ DeVries, a core Neovim maintainer — made a great video about it. Some other great tips are also shared by ThePrimeagen here.

When I first got into Vim, in 2015, I was fascinated and my configuration files grew larger and quicker than they should have. Everything was so new that most of the things I wasn’t even using and eventually I trimmed it all down into a minimalistic setup with as few plugins as possible, so that I could focus on the important thing: learning Vim the right way, i.e. leveraging as much of its core features as I could. A problem with Neovim, in my opinion, is that its plugin ecosystem is so big and Lua makes it so easy to configure things that the urge to install another one to cover that little corner of a workflow is almost irresistible. That may trick some newcomers into acquiring some bad habits, but that’s all part of the game, I guess. What can I say, my dotfiles are as big as they ever were.

Treesitter & LSP

Specifically for Linux kernel development, though, there is this great post. It leverages cscope and ctags for source indexing, which is still very useful. However, there are two things that Neovim supports which VSCode users will find familiar: Treesitter and LSP. Treesitter enables more advanced syntax highlighting and LSP, the Language Server Protocol, is the Microsoft open standard that VSCode uses in the back-end for things like auto completion and finding references.

Since late 2018, as far as I know, it is possible to build Linux using clang. If you do it, there is a helper script that will generate a compile_commands.json file which can be used by clangd LSP within Neovim. What I usually do is to run a allmodconfig build in Linus’ tree and use its compile_commands.json in the root of all the other worktrees I have around. That way I only have to index once, but it comes with a few caveats:

  • Since my host is x86_64, messing around with arch-specific files will show many missing symbol errors, since they weren’t included in the build and therefore the compile_commands.json file.
  • It is painfully slow to setup and index everything, but once it’s done it’s very useful.
  • As you change files in a worktree, errors will inevitably appear since the index reference is pinned to Linus'.
  • When jumping to references, if they’re not within the same file, it takes me to Linus' tree and sometimes I’ll edit that tree instead of the one I’m in. That can be distracting.

LSP completions with nvim-cmp.

LSP references within Telescope.

If, however, you can’t build with clang for some reason or you’re doing too much building that re-indexing all the time is simply not worth it, or perhaps LSP is just not your cup of tea, then fear not: you can still use cscope with Neovim. Official support for it has been dropped, but there’s a plugin that does it. I even added a small Telescope picker for it. Telescope is a powerful plugin that by itself is, in my humble opinion, capable of making even Vim’s most loyal users at least consider Neovim. It is a highly extendable fuzzy finder that works with anything. You can pick buffers, help tags, man pages, LSP symbols, quickfix items, registers, git files, you name it.

Available builtin LSP pickers.

Linters

A great script used by many, especially by newcomers like myself, is checkpatch.pl. Some maintainers dislike it, but anyone not used to the kernel coding style is highly encouraged to use it. During my mentorship in the Linux Foundation, the tip was to use it as a git post-commit hook:

exec git show --format=email HEAD | ./scripts/checkpatch.pl --strict --codespell

That is very useful, but what if you had it as a linter in your editor? This is very straight forward to setup using nvim-lint.

Viewing checkpatch.pl rules applied inline.

There is also klint for the rusty side of the kernel, but I still haven’t found a way of integrating it into Neovim.

get_maintainer.nvim

In the past few weeks I have been working on a few cleanup patches suggested by Greg Kroah-Hartman. The challenge was not so much the patch themselves, but submitting them correctly into their respective trees. Initially I was going through each commit, calling get_maintainer.pl by hand and checking in MAINTAINERS which tree (T:) I should base them off of. Then I realized I would need to send a new patch to the same maintainer again, whereas I could’ve squashed or assembled a series instead. Then I finally discovered that get_maintainer.pl has a --scm option that also gives you the relevant scm (source code management) trees.

So, with that, I decided to customize Neovim by writing a small plugin that would integrate nicely with the legendary vim-fugitive. I needed a wrapper that would call get_maintainer.pl for the current file, ref or list of refs. That way I could better organize which patches should be sent to whom and properly cherry-pick them on b4 branches for sending.

Demo of the basic features of the get_maintainer.nvim plugin.

Snippets

For the same task, instead of manually typing or pasting very similar commit messages and risk introducing errors, I went ahead and defined a few snippets in Lua using LuaSnip. Providing a snippet engine is a prerequisite for setting up LSP completions, so I just chose the one where I could define them with Lua. It’s very powerful once you get the hang of it, so no more writing tags such as Cc:, Tested-by: and Suggested-by: or frequently used email addresses by hand for me. Since you can define snippets based on file types, you could even setup some things you always type like mail signatures, if you use something like neomutt.

Debugger

Many people argue that Vim is just a text editor and a full IDE is always to be preferred and a key point to that is because using a debugger is easier. It’s true that learning Lua and spending more time configuring the editor is not for everyone, but it can be quite satisfying and rewarding. In any case, there is also another Microsoft creation apart from LSP that Neovim supports: the Debug Adapter Protocol. For integration, you need nvim-dap and I highly recommend the nvim-dap-ui as well so you get a nice default user interface from the get go. Now, you only need an adapter and for C/C++ that would be cpptools. To make your life easier you can use Mason to install it and many others. Make sure to disable RANDOMIZE_BASE, then you would need something like this:

local dap = require("dap") -- https://github.com/mfussenegger/nvim-dap
-- dap.set_log_level("TRACE")

dap.adapters.cppdbg = {
  id = "cppdbg",
  type = "executable",
  command = vim.fn.stdpath("data") .. "/mason/bin/OpenDebugAD7", -- if you use mason
}

dap.configurations.c = {
	{
		name = "Attach to gdbserver :1234",
		type = "cppdbg",
		request = "launch",
		MIMode = "gdb",
		miDebuggerServerAddress = "localhost:1234",
		miDebuggerPath = "/usr/bin/gdb",
		cwd = "${workspaceFolder}",
		stopAtEntry = false,
		args = { "-ex", "continue" },
		program = "/mnt/md0/linux/ktest/build/sid/vmlinux",
		-- program = function()
		--   return vim.fn.input("Path to vmlinux: ", vim.fn.getcwd() .. "/", "file")
		-- end,
	},
}

Remote session targeting a QEMU machine.

It’s certainly not perfect, especially for such a complex project. I’ve used it extensively for Python debugging in the past and it’s very good for that. I hope this article made you curious about Neovim, or helped in any way if you’re already a user. Let me know if something should be in here that I’ve missed! Happy hacking.