Skip to main content

Neovim Quckstart Guide

Introduction

  In the previous post we talked about how to set up Vim quickly so you could start doing some real work, instead of wasting time configuring it. Now it's time for Neovim. I said before that this is the editor I use the most of them all for a variety of reasons. One of the main ones is that Neovim is compatible with Lua, which allows you to script how you want it to behave more easily. This could also be done in Vim, to an extent with its language Vimscript. Neovim is also compatible with Vimscript, but Lua is still a much more modern approach and more powerful.

 Another major advantage of NeoVim is its focus on performance and extensibility. NeoVim has been designed to be more efficient with memory and faster in execution, which is particularly noticeable when working with large files or multiple plugins. The built-in plugin manager and the ability to asynchronously execute commands mean that your editing experience remains smooth, even when performing complex tasks.

 Additionally, NeoVim boasts better integration with external tools and modern software development practices. It supports embedded terminals, which allow you to run terminal commands directly within the editor, and it has improved support for features like LSP (Language Server Protocol), making it easier to integrate with various programming languages and their tooling. This makes NeoVim a more versatile and powerful tool for developers who want to streamline their workflow.

 Since I work as a DevOps engineer, I'm going to teach you how to set it up for coding with Helm, Terraform and Yaml files, which is not trivial task, but here we go

Directory setup

  Neovim behaves mostly the same when it comes to configuration. It also needs a particular directory hierarchy so it knows where to look for configuration files. And exactly as I did in the previous post, I'm going to attach right below how everything should look like. Bear in mind you don't have to manually create any directories hanging under plugged. Those are automatically generated once you install a new plugin.

~/.config/nvim
├── init.vim
├── lua/
│   ├── highlighting.lua
│   ├── init.lua
│   └── lsp.lua
└── plugged
    ├── cmp-buffer/
    ├── cmp-nvim-lsp/
    ├── cmp-path/
    ├── cmp-vsnip/
    ├── dracula/
    ├── lualine.nvim/
    ├── nerdtree/
    ├── nordic.nvim/
    ├── nvim-cmp/
    ├── nvim-jdtls/
    ├── nvim-lspconfig/
    ├── nvim-treesitter/
    ├── python-syntax/
    ├── vim-go/
    ├── vim-helm/
    ├── vim-terraform/
    ├── vim-vsnip/
    └── vim-yaml/

 As you can see it's pretty much the same as Vim was with the exception that the equivalent to .vimrc is located within the Nvim's configuration directory and it's called init.vim. This name is mandatory. It has to be named like so.

  Here is where the initial configuration for Nvim is going to be located. Also, notice that, in contrast to Vim, this hierarchy is incredibly simple. You have an initial file (init.vim) and then just two sub-directories. One is the plugged directory, which is exactly the same as it was in Vim, it stores the plugins you install, and then there's the new and shiny lua directory, where you can store the lua scritps you code.

  These can have any name you want, just remember to include them in the init.vim file so they can be loaded on startup.

  Here's the command to create all the directories:

mkdir -p ~/.config/nvim/lua

 We only need to create the nvim and lua directories. The plugged is managed automatically by the plugin manager.

Installing vim-plug

  This is my preferred NeoVim plugin manager, but you can choose any other one you like. I'm going to show you how to install this one since it's the one I use. A plugin manager is just an automated way of installing plugins for Nvim. It's the same manager I showcased in the previous post. To install it in NeoVim, follow the instructions in the vim-plug repository or just execute the following command if you don't care much about the README.md and just want this installed ASAP.

sh -c 'curl -fLo "${XDG_DATA_HOME:-$HOME/.local/share}"/nvim/site/autoload/plug.vim --create-dirs \
       https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim'

  If you go to the previously mentioned repository, you will find this exact command.

The configuration file

  If you read the previous post, there's nothing to add here. This is the initial configuration file for Nvim I recommend having. The only difference is the plugins I import, needed for code autocompletion and LSP support, and the Lua file required, which has said LSP configuration scripted. We'll get to it later though.

  For starters, here's the init.vim template I have:

init.vim
" Initialize the plugin manager - vim-plug
call plug#begin('~/.config/nvim/plugged')

  " File explorer - NERDTree or nvim-tree
  Plug 'preservim/nerdtree'
  " or you can use nvim-tree instead:
  " Plug 'kyazdani42/nvim-tree.lua'

  " Dracula theme
  Plug 'dracula/vim', { 'as': 'dracula' }
  Plug 'AlexvZyl/nordic.nvim'

  " LSP configurations
  Plug 'neovim/nvim-lspconfig'

  " Autocompletion engine
  Plug 'hrsh7th/nvim-cmp'      " Autocompletion plugin
  Plug 'hrsh7th/cmp-nvim-lsp'  " LSP completion source
  Plug 'hrsh7th/cmp-buffer'    " Buffer completion source
  Plug 'hrsh7th/cmp-path'      " File path completion source
  Plug 'hrsh7th/cmp-vsnip'     " Snippet completion source
  Plug 'hrsh7th/vim-vsnip'     " Snippet engine

  " Syntax highlighting for Go, Java, Python, and Terraform
  Plug 'fatih/vim-go', { 'do': ':GoUpdateBinaries' }
  Plug 'mfussenegger/nvim-jdtls'  " Java
  Plug 'vim-python/python-syntax'
  Plug 'hashivim/vim-terraform'

  " Treesitter for better syntax highlighting and parsing
  Plug 'nvim-treesitter/nvim-treesitter', {'do': ':TSUpdate'}

  " Statusline (optional)
  Plug 'nvim-lualine/lualine.nvim'

  " LSP plugins
  Plug 'stephpy/vim-yaml'
  Plug 'towolf/vim-helm'
call plug#end()

lua require('init')
lua require('highlighting')
lua require('cmp_setup')

" General settings
set number
set termguicolors
set background=dark
set nocompatible            " disable compatibility to old-time vi
set showmatch               " show matching 
set ignorecase              " case insensitive 
set mouse=v                 " middle-click paste with 
set hlsearch                " highlight search 
set incsearch               " incremental search
set tabstop=2               " number of columns occupied by a tab 
set softtabstop=2           " see multiple spaces as tabstops so <BS> does the right thing
set expandtab               " converts tabs to white space
set shiftwidth=2            " width for autoindents
set autoindent              " indent a new line the same amount as the line just typed
set wildmode=longest,list   " get bash-like tab completions
filetype plugin indent on   "allow auto-indenting depending on file type
set mouse=a                 " enable mouse click
set clipboard=unnamedplus   " using system clipboard
filetype plugin on
set cursorline              " highlight current cursorline
set ttyfast                 " Speed up scrolling in Vim
syntax on
set backupdir=~/.cache/vim " Directory to store backup files
colorscheme nordic

" Enable NERDTree at startup and map it to a sidebar toggle key
autocmd vimenter * NERDTree
nnoremap <C-t> :NERDTreeToggle<CR>
nnoremap <C-n><C-n> :tabnew<CR>
nnoremap <C-n><C-p> :tabprevious<CR>
nnoremap <C-n><C-x> :tabnext<CR>
nnoremap <C-n><C-c> :tabclose<CR>
nnoremap <C-f> :FZF<CR>

  Pay attention to this line: lua require('init'). It indicates to import a Lua script. This directive will automatically look for any scritps within the lua/ directory. Also note that we don't need to add the extension. It will only look for those files with the following format: *.lua. If by any chance you have a file not found error, make sure the lua file is created, with the correct name (matching the one in between brackets) and that it's in the correct directory.

The Lua Scripts

  Alright. We have everyting so far, and it's looking good. We now need to create the Lua scritps that will allow us to autocomplete and suggest code. To do that, copy the following code into a file located in the lua directory.

init.lua
 -- Set up nvim-cmp.
local cmp = require'cmp'

cmp.setup({
  snippet = {
    -- REQUIRED - you must specify a snippet engine
    expand = function(args)
      vim.fn["vsnip#anonymous"](args.body) -- For `vsnip` users.
      -- require('luasnip').lsp_expand(args.body) -- For `luasnip` users.
      -- require('snippy').expand_snippet(args.body) -- For `snippy` users.
      -- vim.fn["UltiSnips#Anon"](args.body) -- For `ultisnips` users.
      -- vim.snippet.expand(args.body) -- For native neovim snippets (Neovim v0.10+)
    end,
  },
  window = {
    -- completion = cmp.config.window.bordered(),
    -- documentation = cmp.config.window.bordered(),
  },
  mapping = cmp.mapping.preset.insert({
    ['<C-b>'] = cmp.mapping.scroll_docs(-4),
    ['<C-f>'] = cmp.mapping.scroll_docs(4),
    ['<C-Space>'] = cmp.mapping.complete(),
    ['<C-e>'] = cmp.mapping.abort(),
    ['<CR>'] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items.
  }),
  sources = cmp.config.sources({
    { name = 'nvim_lsp' },
    { name = 'vsnip' }, -- For vsnip users.
    -- { name = 'luasnip' }, -- For luasnip users.
    -- { name = 'ultisnips' }, -- For ultisnips users.
    -- { name = 'snippy' }, -- For snippy users.
  }, {
    { name = 'buffer' },
  })
})

-- To use git you need to install the plugin petertriho/cmp-git and uncomment lines below
-- Set configuration for specific filetype.
--[[ cmp.setup.filetype('gitcommit', {
  sources = cmp.config.sources({
    { name = 'git' },
  }, {
    { name = 'buffer' },
  })
})
require("cmp_git").setup() ]]-- 

-- Use buffer source for `/` and `?` (if you enabled `native_menu`, this won't work anymore).
cmp.setup.cmdline({ '/', '?' }, {
  mapping = cmp.mapping.preset.cmdline(),
  sources = {
    { name = 'buffer' }
  }
})

-- Use cmdline & path source for ':' (if you enabled `native_menu`, this won't work anymore).
cmp.setup.cmdline(':', {
  mapping = cmp.mapping.preset.cmdline(),
  sources = cmp.config.sources({
    { name = 'path' }
  }, {
    { name = 'cmdline' }
  }),
  matching = { disallow_symbol_nonprefix_matching = false }
})

require('lsp')

 This is not entirely mine, it's just an adaptation of the very basic configuration the vim-cmp repository advices to start with. I just extended a tad the functionality.

  For those of you who don't know, vim-cmp is a plugin that allows code autocompletion among other things. In order to do the work properly, it needs the Lua script either embedded within the init.vim or in a separate file as I have here.

 We have a few more files to configure. If you paid attention and not just copy/pasted my code, you pesky thief, you have seen a require('lsp') at the very bottom of the init.lua file. That is, well, another *.lua file we have to configure. What, you thought this was done by magic? Here you go:

lsp.lua
-- Enable LSP servers for Go, Python, Java, Terraform, Helm and Yamls
local lspconfig = require('lspconfig')
local capabilities = require('cmp_nvim_lsp').default_capabilities()

-- Go
lspconfig.gopls.setup({
  settings = {
    gopls = {
      analyses = {
        unusedparams = true,
      },
      staticcheck = true,
      gofumpt = true,
    },
  },
})

-- Python
lspconfig.pyright.setup{
  capabilities = capabilities
}

-- Java
lspconfig.jdtls.setup{}

-- Terraform
lspconfig.terraformls.setup{}

-- Rust
lspconfig.rust_analyzer.setup{}

-- C/C++
lspconfig.clangd.setup{}

-- Enable Helm LSP
lspconfig.helm_ls.setup {
  settings = {
    ['helm-ls'] = {
      yamlls = {
        path = "yaml-language-server",
      }
    }
  }
}
-- YAML LSP
lspconfig.yamlls.setup({
  on_attach = function(client, bufnr)
    if vim.bo[bufnr].buftype ~= "" or vim.bo[bufnr].filetype == "helm" then
      vim.diagnostic.disable()
    end
  end,
  capabilities = capabilities,
  settings = {
    yaml = {
      schemas = {
        ["https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/configmap.json"] = '*onfigma*.{yml,yaml}',
        ["https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/deployment.json"] = '*eployment*.{yml,yaml}',
        ["https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/service.json"] = '*ervic*.{yml,yaml}',
        ["https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/ingress.json"] = '*ngres*.{yml,yaml}',
        ["https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/secret.json"] = '*ecre*.{yml,yaml}',
        ["https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/statefulset.json"] = '*stateful*.{yml,yaml}',
        ["https://raw.githubusercontent.com/yannh/kubernetes-json-schema/master/master/pod.json"] = '*pod*.{yml,yaml}',
      },
      validate = true,    -- Enable schema validation
      completion = true,    -- Enable completion
    }
  }
})

  The purpose of that file is to configure all the LSPs we need properly and easily, so if you want to add or remove an LSP, you know where to head to. The hardest thing to configure here is the yamlls, because it's mostly a schema.json archive where Nvim connects to in order to find what properties the file.yaml ought to have.

 Also, you may have seen that on_attach section right below the yamlls setup. Try and remove that and edit a template for a Helm Chart, see what happens.

 Anyway, there's one more file to go, and it's called highlight.lua. Its purpose is to serve as a configuration for the TreeSitter plugin and its parsers. Why is it called "highlight" you ask? No idea. I guess I couldn't come up with anything better.

highlighting.lua
-- Treesitter setup for enhanced syntax highlighting
require'nvim-treesitter.configs'.setup {
  ensure_installed = { "go", "python", "java", "hcl", "yaml", "helm" },
  highlight = {
    enable = true,
  },
}

 And with that, we have the basic nvim configuration covered, but we are not done yet. For all this to work, we need to do some extra few, tiny, not-very-complex 20 steps more. Just kidding, but we have to do a few things more.

Installing the LSPs

  Bear in mind that these LSPs are not automatically installed. You have to do it manually, usually it can be done using your distro's package manager however, sometimes, as we'll se soon enough with terraform, you'll need to do some extra steps. In this case, for this specific Lua script to work fine you will need to install an LSP for each of the following languages: Python, Go, Terraform, Helm, Yaml, Rust and C/C++.

sudo pacman -S pyright gopls clang rust-analyzer

  Installing terraform and terraform-ls and helm-ls is a bit more complicated in Arch. First you need to install yay, or any other AUR helper you want. Follow this yay installation guide if you don't have it already installed in your system yet. Once you have yay, you can go and install them with it just like so:

sudo pacman -S terraform
yay -S terraform-ls
yay -S helm-ls-bin

 Again, terraform is not widely available as a package everywhere so if you have any other distro, or are working with MacOS, follow the installation.md guide in the official terraform-ls repository

  The yaml-language-server package should be installed as a dependendency of helm-ls, however, if it's not you can install it with yay, just like the rest

yay -S yaml-language-server

 Alternatively, you can avoid the hassle of installing terraform altogether by removing the corresponding lines in the init.lua script.

  If you need or want an LSP for any other language, you can refer to the server_configurations.md in the official nvim-lspconfig repository.

The final steps

  We have now everything setup. We are missing the final steps. Can you guess what's that? No? Well, installing the plugins of course. Just as we did with Vim in the last post, we need to also install them in NeoVim running the same command. Open NeoVim and run the following: :PlugInstall. That should be enough. Remember how in Vim I told you this could not be enough and you may have to run this command from the terminal vim +PlugInstall +qa? Well, here we don't have that issue (most of the time), so feel free to run the command from within NeoVim.

 Also, remember that TreeSitter helps you install parsers for languages. Since Helm and Yaml parsers are not installed by default, you need to run these commands with a Nvim session open: :TSInstall helm and :TSInstall yaml.

 Now we're all set. With all this steps done properly, we should be good to go. If something went south, you can always remove everything and start al over again, just saying. Anyway here's proof that everything actually works as intendednand the final look and feel for your fresh new Nvim installation:


 Do you like it? No? Well, then go ahead an change it up! It's yours now, take care of it and treat it with the respect it deserves. Well, if you already know how to use Vim/Nvim and were here just for the config, then you can leave now, if not, below I got something pretty interesting for you.

Nvim shortcuts

 In short? They are the same Vim has. Not quite, but yes. I'll refer you to my last post where you can find all the basic shortcuts for operating Vim. These all work with NeoVim too, so don't need to worry about it.

  Also, I'll leave a NeoVim shortcut cheat sheet in case you want to learn more advanced ones, same way I did last post with Vim.

At last

  Now you have NeoVim all up and running for a really good coding experience. Once you get used to all those pesky shortcuts you'll be flying. Keep in mind that this is just the surface, Nvim is more powerful than just this, but in case you don't want to spend time looking up documentation and tinkering with it, this is enough to get started.

 I can tell you I spent more time I'm willing to admit searching, testing and preparing all of this when I first got started with it, so I think it's really nice to have it all in a nice single blog post relatively well explained (I hope). Still, this post was shorter than the previous one because most of the stuff is already there, so refer to that post if you feel like you're missing something.

 Did you enjoy it? I sure did. Anyway, see you next time. Keep coding!

Comments