Neovim Editor Configuration

Installation

Install Neovim using your package manager:

sudo pacman -S neovim

Configuration

Change to the /home/<username> directory:

cd ~

Inside ~/.config, create an nvim directory to store the Neovim configuration files:

mkdir .config/nvim

To keep the configuration clean and easy to maintain, create the following directory structure:

~/.config/nvim
|-- init.lua
|-- lua
|   |-- options.lua
|   |-- mappings.lua
|   |-- package-manager.lua
|   |-- autocmds.lua
|   |-- plugins
|   |   |-- colour-scheme.lua
|   |   |-- language-servers.lua
|   |   |-- completion.lua
|   |-- templates
|   |   |-- tex-temp.tex

Using the following commands:

mkdir .config/nvim/lua
mkdir .config/nvim/lua/plugins

Any files within the lua subdirectory will be available on demand and can be loaded with the require command (this is the Lua equivalent of the Vimscript auto-load mechanism).

Create the base configuration file in this directory (this can be either init.vim or init.lua, however, Lua is recommended):

nvim .config/nvim/init.lua

Leave this file blank for now.

One can force reload the init.lua file at any stage within Neovim:

:luafile %

To check the health of Neovim (e.g. if there are any issues with the init.lua file):

:checkhealth

Setting Options

Create the options.lua file:

nvim .config/nvim/lua/options.lua

Within this file, one can configure many of the built-in Neovim options. Setting options is very similar to Vim:

  • vim.opt behaves like :set.
  • vim.opt_global behaves like :setglobal.
  • vim.opt_local behaves like :setlocal.

Some useful options to set include:

insert some options here...

Within the init.lua file, add the require function call so that Neovim can read the options.lua file (note the absence of a .lua extension for the file name):

require("options")

Defining Keyboard Mappings

Create the mappings.lua file:

nvim .config/nvim/lua/mappings.lua

Within this file, set desired key mappings. Some useful mappings include:

insert some options here...

For the basics on Lua mappings, see the Mappings section of the Neovim Lua guide.

Within the init.lua file, add the require function call so that Neovim can read the mappings.lua file (note the absence of a .lua extension for the file name):

require("mappings")

Enabling System Clipboard Functionality

By default, Neovim utilises internal registers for managing yanked (copied) or deleted (cut) data. These registers are independent from the system clipboard, thus, data yanked or deleted inside a Neovim instance will be isolated to that instance unless specified otherwise. To enable the system clipboard functionality, first has to install a command-line tool for manipulating the system clipboard. For Wayland compositors, install wl-clipboard:

sudo pacman -S wl-clipboard

For X compositors, install xclip:

sudo pacman -S xclip

The system clipboard is accessed through the + register in Neovim:

  • "+y<movement> to yank (copy).
  • "+d<movement> to delete (cut).
  • "+p<movement> to paste, or use the terminal paste command Ctrl+Shift+v.

Setting Up Templates

Create the templates directory to store file templates:

mkdir .config/nvim/lua/templates

Create a template for a specific file type, for example, for LaTeX files:

nvim .config/nvim/lua/templates/tex-temp.tex

Add the desired generic content, then save and close the file:

\documentclass[a4paper]{article}

\begin{document}

\tableofcontents

\section{Template}
This is a \LaTeX template.

\end{document}

Create the autocmds.lua file:

nvim .config/nvim/lua/autocmds.lua

Add the following Lua-based Vim API call:

local autocmd_group = vim.api.nvim_create_augroup(
    "Custom auto-commands",
    {clear = true})

vim.api.nvim_create_autocmd({"BufNewFile"},
    {
        pattern = {"*.tex"},
        desc = ".tex template generation auto-command",
        command = "0r ~/.config/nvim/lua/templates/tex-temp.tex",
        group = autocmd_group
    })

An auto-command group called "Custom auto-commands" is created (nvim_create_augroup) to which the .tex template generation auto-command is later assigned. See Why Use Auto-Command Groups? for an explanation.

The first argument of nvim_create_autocmd are the event(s) for which to register the auto-command. In this case, the command will be run every time the "starting to edit a non-existent file" event happens (i.e. BufNewFile). The command to be executed is one that checks whether the file name matches the pattern (i.e. any file that ends in .tex), and then reads the contents of the template and inserts it at line 0.

For more information on auto-commands, see the Autocommands section of the Lua guide.

Neovim is able to read any file from disk into the current buffer by calling the read command. For example, if one creates a file that doesn't have the .tex extension, then you can still read the contents of the template by running the following command within Neovim:

:read ~/.config/nvim/lua/templates/tex-template.tex

Package Management

To extend the functionality of Neovim easily, one must set-up a Neovim package manager. Many exist and it seems like the go-to manager changes every few years. Currently, lazy.nvim appears to be the latest and greatest, and is therefore preferred, however, Packer is a close second.

Create the package-manager.lua file:

nvim .config/nvim/lua/package-manager.lua

Add the following code to this file to allow lazy.nvim to bootstrap itself (i.e. the first time it runs this code, it will download the repository to ~/.local/share/nvim/lazy):

local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.loop.fs_stat(lazypath) then
    vim.fn.system({
        "git",
        "clone",
        "--filter=blob:none",
        "https://github.com/folke/lazy.nvim.git",
        "--branch=stable", -- latest stable release
        lazypath,
    })
end
vim.opt.rtp:prepend(lazypath)

local plugins = {}

local opts = {}

require(lazy).setup(plugins, opts)

Edit the init.lua file and add the package-manager.lua file as a requirement:

require("package-manager")

Restart Neovim: there will be a slight delay while it downloads the lazy.nvim repository and sets everything up.

Changing the Theme

Find an aesthetic Neovim theme. The Nightfox.nvim package provides a few colour schemes—one of which should suit.

Edit the package-manager.lua file and add the following lines to the plugins variable:

local plugins = {
    { -- Theme
        "EdenEast/nightfox.nvim",
        config = function()
            vim.cmd "colorscheme terafox"
        end
    }
}

In Lua, one can wrap functions within other functions. From the lazy.nvim documentation, lazy.nvim runs the config function whenever a plugin loads (in this case nightfox.nvim). Knowing this one can redefine config to wrap other functions to be executed upon loading the plugin. Normally to set-up the Terafox theme, one would call the Lua function vim.cmd("colorscheme terafox") from either command mode inside Neovim, or by placing the functional call somewhere in init.lua or another file that gets executed. When wrapping functions in Lua, you pass the function's name followed by the parameters, without any brackets. If the above lines were added correctly, then when lazy.nvim loads the nightfox.nvim plugin, it will automatically call the config function, in turn executing the Vim command colorscheme with the argument terafox, subsequently setting the theme.

Setting up the Language Server Protocol

Neovim contains a built-in Language Server Protocol (LSP) client and the nvim-lspconfig plug-in provides common configurations for it. Language servers that can be installed natively using the Arch Linux package manager can be found here.

Install the LSP configurations and Mason server manager by editing the package-manager.lua file and add the following lines to the plugins variable:

local plugins = {
    { -- LSP 
        "neovim/nvim-lspconfig",
        dependencies = {
            "williamboman/mason.nvim",
            "williamboman/mason-lspconfig.nvim"
        }
    }
}

Customising the Status Line

Improve the aesthetics and functionality of the status line (bar) displayed at the bottom of the Neovim window to include the file name, encoding and type, as well as how far you are through the file in both percentage form as well as line number and character (column) number.

Edit the package-manager.lua file and add the following to the plugins variable:

local plugins = {
    { -- Status Line 
        "nvim-lualine/lualine.nvim",
        opts = {
            options = {
                icons_enabled = false,
                component_separators = "|",
                section_separators = ""
            },
            sections = {
                lualine_x = {"encoding", "filetype"}
            }
        }
    }
}

Setting Up Snippets

Setting up snippets requires both a completion engine and a snippet engine. The former is provided by nvim-cmp, and the latter by LuaSnip (along with some other dependencies).

Install the necessary package by editing the editing the package-manager.lua file and adding the following lines to the plugins variable:

local plugins = {
    { -- Auto-completion and snippets
        "hrsh7th/nvim-cmp",
        dependencies = {
            "hrsh7th/cmp-nvim-lsp",
            "L3MON4D3/LuaSnip",
            "saadparwaiz1/cmp_luasnip" -- Snippets engine
        }
    }
}

Create the snippets directory to store snippets:

mkdir .config/nvim/lua/snippets

Create a snippet file for a particular file type, for example, HTML files:

nvim .config/nvim/lua/snippets/html.lua

Add the desired snippets inside this file. Below is a basic example of a snippet that automatically expands the HTML <p> paragraph tag:

return {
    s( -- paragraph
        {
            trig="<p>",
            dscr="Automatically expand '<p>' tag to a HTML paragraph",
            snippetType="autosnippet"
        },
        fmt("<p>{}</p>", { i(1) })
    ),
}

See Elijan J. Mastnak's guide on LuaSnip for LaTeX workflows for a detailed explanation on how to use LuaSnip snippets. That guide, in combination with the documentation available on the LuaSnip GitHub repository, as well as the introductory and advanced tutorials by Timothy J. DeVries, should provide adequate coverage of technical capability of LuaSnip.

Edit the package-manager.lua file and configure the completion and snippet engines.

Automatically Compiling LaTeX Files on Save

Add the following lines to the autocmds.lua file:

local autocmd_group = vim.api.nvim_create_augroup(
    "Custom auto-commands",
    {clear = true})

vim.api.nvim_create_autocmd({"BufWritePost"},
    {
        pattern = {"*.tex"},
        desc = "Auto-compile .tex files on save",
        callback = function()
            local fileName = vim.api.nvim_buf_get_name(0)
            local fileDir = string.match(fileName, ".*/")
            local cwDir = vim.fn.getcwd()
            local returnKey =
                vim.api.nvim_replace_termcodes(
                    '<cr>', true, false, true)
            vim.api.nvim_set_current_dir(fileDir)
            vim.cmd(":!pdflatex " .. fileName)
            vim.cmd(":!biber " .. string.sub(fileName, 1, -5))
            vim.cmd(":!pdflatex " .. fileName)
            vim.cmd(":!pdflatex " .. fileName)
            vim.api.nvim_set_current_dir(cwDir)
            vim.api.nvim_feedkeys(returnKey, 'm', true)
        end,
        group = autocmd_group
    })

This code block runs the necessary LaTeX commands to compile a .tex file automatically when it is saved.

Specifically, it creates a Neovim auto-command (nvim_create_autocmd) that executes after the buffer has been written to a file ({"BufWritePost"}). Writing to a file usually occurs when :w or :x commands are entered, however :x will only initiate a write if changes to the buffer have been made since opening the file. The auto-command only executes for .tex files (pattern = {"*.tex"}), if such a file type is written to file, then the callback function is executed which:

  • gets the absolute path and name (nvim_buf_get_name) of the current buffer (0),
  • extracts the absolute path of the file to compile (string.match),
  • gets the current working directory (getcwd),
  • changes the directory (nvim_set_current_dir) to that of the current file,
  • executes the shell commands to compile a LaTeX document using the Biber back-end for bibliography management (note that the ".tex" extension is stripped before running the biber command,
  • changes back to the previous working directory (nvim_set_current_dir),
  • presses the carriage return key (Enter) to return to the previous window (nvim_feedkeys).

Shortcut to Preview LaTeX PDFs via Zathura

Add the following lines to the mappings.lua file:

previewLaTeXPDF = function()
    local fileName = vim.api.nvim_buf_get_name(0)
    if string.find(fileName, ".tex", -4) then
        local pdfName = string.gsub(fileName, ".tex", ".pdf")
        vim.fn.jobstart("zathura " .. pdfName)
    else
        print(".tex extension not found for file: " .. fileName)
    end
end

vim.keymap.set({'n'}, '<leader>p', '', { desc = "Preview LaTeX PDF", callback = previewLaTeXPDF })

This code block defines a function called previewLaTeXPDF that gets the absolute path and name (nvim_buf_get_name) of the current buffer (0), and then checks if the last four characters of it are equal to ".tex" (string.find). If the .tex extension is found, an asynchronous job is started in the background (jobstart) to launch Zathura with the path and name retrieved prior, else, a relevant error message is printed. The previewLaTeXPDF function is then mapped to the <leader>p sequence (vim.keymap.set).

Why Use Auto-Command Groups?

In most situations, auto-commands should be assigned to a group that clears upon resourcing files within Neovim (i.e. whenever the :so command is executed). This is done to prevent an auto-command being loaded repeatedly every time a file is resourced which may lead to slow-downs or unexpected behaviour. Groups are created using the nvim_create_augroup function which expects the first argument to be a string for the name of the group, and the second argument a table with the clear key set accordingly. Documentation for the autocmd API in general can be found here (which includes the nvim_create_augroup function among others). For more general information on auto-commands, see the Groups section of the autocmd user documentation.