Setup Neovim with kickstart.nvim

Posted by Fabrice on Monday, December 25, 2023 Updated on Tuesday, February 06, 2024

Introduction

How I managed my configuration

When I started using Vim, I started editing my .vimrc bit by bit and incrementally before it starts getting too big for me to find anything inside it and not using even half of the plugins I installed. That goes without saying, there were quite a bit on conflicting keymaps as well as I'm using bépo as my keyboard layout with partial remaps (fr).

Obviously, it slowly became quite a mess. To address this issue, I decided to reorganise my $HOME/.config/vim directory using the vim directory structure and did some cleanup at this point of time. I think it was also around this period that I discovered that Vim8 added a native package manager that I started to use. Thus, at this point, I started organising my configuration with semantic files, such as $VIMHOME/plugin/spelling.vim to manage my spelling configuration for instance. This approach makes debugging easier, and also checking custom keyboard shortcuts easier, as I just have to check $VIMHOME/plugin/omnicomplete.vim for instance to know which shortcuts I set up when I'm still getting the habits of using them.

At some point of time, I moved to Neovim, and I simply moved my configuration from Vim to Neovim and continue on adding more and more plugins on top of each other depending on my hype, especially because the world of Neovim plugins opened up to me. Needless to say that less than half of these plugins were put into good use. Which leads to my first configuration big cleanup.

Six months ago, I wiped my frankenconfig, and started back from scratch in lua, with the same structural approach as previously, but now wondering if the plugin would be useful or not. Since my first time using Vim, there were some big changes in the vim ecosystem, especially in language management with tree-sitter and lsp. These two bring into the environment a unified way to manage languages without having to depend on language-specific plugins, henceforth I didn't need specific plugins to have nice syntax coloration for obscure languages anymore, or get frustrated with omnicomplete which decided not to work for some languages… While it's not an absolute rule (for instance, I'm using vimtex for latex, which includes a more accurate syntax coloring than tree-sitter). I also moved from the native vim way of managing plugins to use Lazy as a proper plugin manager, which helped me synchronize my configuration between my different computers. It was working nice and well, with some weird bugs (see below) on first install, but as it was punctual… I just ignored it.

E5113: Error while calling lua chunk:
$VIMHOME/nvim/init.lua:13: E21: Cannot make changes, 'modifiable' is off

However, I was unhappy with some of my configurations, and if I managed to have something functional, there were many details that annoy me that stemmed for some configuration I wrote some times ago and of course didn't document. This leads us to today, where I just decided to use kickstart.nvim, which is a well-documented vim starting configuration (it's not a distribution, it still requires your input to obtain something that fits your needs), which was exactly what I needed to start anew… but not fully from scratch.

The migration

To move my configuration from kickstart.nvim, I wanted to get the best of both world. For instance, I didn't want to have an init.lua that is over 600 lines long. I thus decided to split it into short files that manages a specific part of the configuration: completion, lsps, treesitter, mappings after reading the different configuration default from nvim-kickstart and changing what I disliked. To do that, I started with using the NVIM_APPNAME environment option in order to make the move in a non-destructive way. After installing the bare minimum to make it usable for me (as a bépo user), I exported the NVIM_APPNAME variable to start using my configuration to help me debug it on the fly. I also decided to write this blog post to remember the process and maybe helped some people who want to configure their text editor.

Note that in the following, we will assume that the reader is already familiar with Neovim and lua.

Configuring Neovim

My (in use) configuration for Neovim is available here.

I started using git from the start in order to remember what I did by using its history. As it's not the easier thing to read however, here follows my rationals, and thought during this process in a chronological order.

kickstart.nvim

kickstart.nvim is a starting Neovim configuration file which was created by TJ DeVries, a core developer of Neovim, author of telescope.nvim and content creator about Neovim. You can see a quick presentation on his YouTube channel [here].

To summarise, it is a starting configuration including a minimal set of plugins that helps to have a modern editor, that has a working and customizable LSP configuration with Mason to help install LSP servers, git helpers, completion, telescope, and a choice of shortcuts that are quite natural to learn (unlike what I used previously because I was simply adding shortcuts one after another without thinking of the compatibility between them).

Note that it is made for educational purpose, and thus is not modularised as is (hence the single self-contained init.lua file). Which leads us to our first step after simply pasting the content of init.lua into the $VIMHOME/init.lua file.

Modular configuration

When in doubt about some shortcuts, I'm under the habit of going to read the corresponding configuration file in my $VIMHOME/plugin directory. kickstart.nvim includes which-key, that is a plugin that pops a helper when waiting for a command as shown hereunder.

which-key plugin
illustration

Thanks to that, I start getting rid of this habit. However, having a modular configuration helps debugging it. Usually, when an error spawns, the filename and location of what has triggered it appears in the stack trace, and it's easier to search in a short file than a thousand-line long one.

After a first read of the configuration file, I decided to split it into smaller files. Note that if you want to start directly from there a project that does exactly that exists:

However, I find it easier for educational purpose to have everything in one place to linearly read it first.

The process was quite simple, as the file was already divided logically into components that make sense, I just had to take the content of those sections and move them into the $VIMHOME/lua folder before including them in init.lua. I hesitated to continue using the $VIMHOME/plugin directory for that, but I then realised that having it inside init.lua allows having a structure that allows using init.lua as an index, and I can start jumping from there to access my specific configurations.

To illustrate it, let's take the example of completion. In kickstart.nvim, there is a section called "Configure nvim-cmp", that deleted and pasted into a file $VIMHOME/lua/complete.lua before adding the line require('complete') to load it. You can see the result in this commit.

Survival

Now that it's done, I need my basic keymaps for using Vim/Neovim in bépo. I added the file $VIMHOME/lua/bepo.lua and simply load it with a line require('bepo') in my $VIMHOME/init.lua file.

I also merged my personal remap inside the $VIMHOME/lua/mappings.lua file which already contained the ones imported from kickstart.nvim from the previous step. These mappings are convenient ones such as the following one to easily open folds to a given level.

local keymap = vim.keymap.set

-- z0…z9 to open folds to a certain level
for i=0,9 do
  keymap('n', 'z' .. i , ':set fdl=' .. i .. '<CR>', {noremap = true, silent = false})
end

I also merged and cleaned redundant general options in the $VIMHOME/lua/general-options.lua file. For instance to ignore folded content when jumping between paragraphs for instance, there is the following line in the aforementioned file:

-- folds
vim.g.ip_skipfold=true

Once all of that is done, at this point of time, I started moving to use the configuration by exporting NVIM_APPNAME=new_nvim inside my .zshrc. The idea is that now I bootstrapped my Neovim config to proficiently edit the configuration, which in lua also uses LSP and many of kickstart.nvim features. During this process I notice what I liked and disliked to know how to edit the configuration while editing the configuration.

Importing my former configuration

After having the minimal setup top edit a configuration, I now need to make things work for other things as well.

That is dependent of your workflow. For reference, I personally use Neovim to:

  • Write text in markdown (such as what I'm currently doing) for different purposes;
    • blog post, emails or more generally text blocks that will be exported in html.
  • Read text written in Markdown;
  • Try to organise my life using neorg;
  • LaTeX documents and slideshows;
  • Write code in a variety of languages.

To do that I went to my previous configuration (which fortunately was already using Lazy as a plugin manager). To do that, I picked my specific plugins, for instance Neorg, to import the configuration. For the example of Neorg it has multiple steps as I had a $VIMHOME/plugin/neorg.lua that contained my general configuration and $VIMHOME/ftplugin/norg.lua which has specific configuration when editing a note in Neorg.

After asking Lazy to install Neorg, I first imported the general configuration from my previous plugin folder in my new lua folder, as depicted by this commit.

After wondering if I should create a ftplugin directory, I finally decided to move to autocommands to manage different filetypes. The reason behind that was that I had a limited amount of lines in total in my former ftplugin directory, and the subdivision with augroup and autocmd in Vim makes it reasonably readable. Which brought me to this commit.

Then some sanity and cleanup have been made. For example, adding a description field to my key maps so that which-key nicely prints them. See this commit.

Now, rinse and repeat for each plugin/specific configuration set: vimtex, vim-pandoc-syntax (which I mainly use for the concealer), spelling configuration, etc.

All the while keeping a critical eye on what I'm moving. For instance, I have a mapping on the following command to fix typo on a misspelled word under the cursor when writing text: mz[s1z=`z. However, the key was bound at anytime, even when spelling was not enabled. To fix that, we changed the way the binding was called: instead of being called everytime, we embedded it inside an autocmd which triggers when the option spell is set. To see the resulting configuration, see these two files: autocommands.lua for the autocommand setting, and spelling.lua for the utils module.

Just for the record, this is what it does:

  1. Store the current cursor location: mz sets a mark z on current position;
  2. Go to the previous spelling error: [s;
  3. Pick the first spelling suggestion: 1z=;
  4. Go back to the stored location: `z jumps to mark z at the right column.

Adjusting the configuration

While testing the default behaviour of kickstart.nvim, which changes quite a few things for me, I realised that some of them were quite smart, such as as the leader key, which was on , before because of its accessibility in bépo, disabling quite a useful motion binding, some are more personal tastes, such as the default register to be the system one or not.

I thus tried some of these options and decided while editing the configuration and writing this blog post which one where working nicely for me (with the help of which-key to help me during this whole process), and which one were not quite working (unnamedplus as default clipboard, I really use both separately, and I don't want to have my system clipboard polluted)

Moreover, :healthcheck which-keys was really helpful to debug some colliding key bindings, especially because of bépo, which has been incrementally fixed while editing the configuration. I already wrote a blog post about how I handle those in vimtex.

My former configuration also featured LSP, but Mason was not used, which made me install my LSP server from my system package manager. However, I still prefer using the system one for some languages that features some changes between versions, which I also configured in the $VIMHOME/lua/lsp-configure.lua file.

Final thoughts

To conclude, I would like to say thanks to the French tuppervim community, which regularly organises meetings where we can show our latest configuration file, or just exchange nice tips. I discovered both TJ DeVries and kickstart.nvim there.

I still have to get rid of some habits (such as the comma as a leader key) and get used to it, but I'm happy with the change so far, beside knowing exactly what is in my configuration, it also helped me fix some weird key conflicts while editing markdown, making writing this blog post quite pleasant.

While trying the Git-related key bindings utilities bundled with kickstart.nvim, while being quite minimal, it still filled my needs (especially adding a hunk from visual selection, which is helpful to split commits into sub-blocks when git add -p would have been quite tedious to use).

The status bar was also a pleasant surprise for me. It is exactly the kind of things I find useful but a pain to configure. I may tweak it a bit in the future, but so far it's fine as it is.

The configuration will continue to evolve from this point, as my use of computer and Neovim will change as well. And to finish, I think what TJ Devries said in this video about text editor is quite on point: you don't have to spend time in your configuration if it is not fun for you, just take something that works for you. I actually took the time to do it because I find it interesting and fun 🙂

If you also find it fun, and want to try it, I strongly encourage you to take a cup of hot cocoa, put on some relaxing music, and just dive head first!

tags: vim, neovim, setup