Skip to main content
  1. Posts/

My Neovim Setup

After using nvim “bundles” like NvChad, LunarVim for some time I decided to go through the list of plugins they use, decide what I need, and configure everything myself. When I use these bundles I am not familiar with the config and it can be hard to discover and tweak plugins. So if I need to go through the plugin documentation to see what it does and how it works I might as well configure everything myself. So in this post I’m going to document my Neovim configuration.

Even though I don’t use vim as my main editor, learning it can be a very useful skill. I can bring vim keybindings to any editor (IDEA, VS Code, etc.) with plugins and other applications like browsers. At work I use Intelij IDEA with vim motions, and my goal was to have the same number basic features in vim. Here is the list.

  • No tabs - When using tabs I have to spend extra time to managing them (closing/reordering).
  • Global fuzzy search.
  • Quick switch between files/buffers.
  • Auto complete.

Nothing extraordinary really.

Plugin list #

Directory structure #

.
├── init.lua
├── lua
│   └── vladmyh
│       ├── lspconfig.lua
│       ├── nvim-tree.lua
│       ├── nvimcmp.lua
│       ├── telescope.lua
│       └── treesitter.lua
├── plugin
│   └── packer_compiled.lua
├── undodir
└── ~

init.lua is the main configuration file. lua/vladmyh directory contains configuration files for plugins. The reason for another directory inside lua directory is to prevent namespace clashes between plugins. For example, if telescope plugin has a file named lua/telescope.lua it will be ambiguous which file to load. You can find more information about namespaces here.

Main configuration #

This is the main init.lua file. Neovim supports both vimscript and lua in configuration I chose lua. Here I have some helper functions, general settings and plugin declarations.

-- Helpers
function map(mode, shortcut, command)
	vim.api.nvim_set_keymap(mode, shortcut, command, { noremap = true, silent = true })
end

function map_func(shortcut, command)
	vim.keymap.set('n', shortcut, command, { noremap = true, silent = true})
end

local o = vim.o
local wo = vim.wo
local bo = vim.bo

-- Settings
vim.g.mapleader = " "
o.ignorecase = true
o.undodir = '~/.config/nvim/undodir'
o.undofile = true
o.smartcase = true
o.splitbelow = true
o.splitright = true
o.scrolloff = 4
o.mouse = 'a'
o.relativenumber = true
wo.number = true
bo.tabstop = 4
bo.softtabstop = 4
bo.expandtab = true
bo.smartindent = true

-- Keys
vim.g.mapleader = ' '
map('n', '<leader>j', ':wincmd j<cr>')
map('n', '<leader>k', ':wincmd k<cr>')
map('n', '<leader>h', ':wincmd h<cr>')
map('n', '<leader>l', ':wincmd l<cr>')

-- Plugins
require('packer').startup(function()
	use {
		'kyazdani42/nvim-tree.lua',
		requires = {
			'kyazdani42/nvim-web-devicons', -- optional, for file icons
		}
	}
	use { 'nvim-treesitter/nvim-treesitter', run = ':TSUpdate' }
    	use 'nvim-treesitter/playground'
	use 'neovim/nvim-lspconfig'
	use {
		'nvim-telescope/telescope.nvim',
		requires = { {'nvim-lua/plenary.nvim'} }
	}
	use 'hrsh7th/nvim-cmp'
	use 'hrsh7th/cmp-nvim-lsp'

	use 'dcampos/nvim-snippy'
	use 'dcampos/cmp-snippy'
	use {
		'rose-pine/neovim',
		as = 'rose-pine',
		config = function()
			vim.cmd('colorscheme rose-pine')
		end
	}

end)
require('vladmyh/telescope')
require('vladmyh/lspconfig')
require('vladmyh/nvimcmp')
require('vladmyh/treesitter')
require('vladmyh/nvim-tree')

-- NvimTree
map('n', '<leader>n', ':NvimTreeToggle<cr>')

-- Telescope
map('n', '<leader>ff', ':Telescope find_files<cr>')
map('n', '<leader>fb', ':Telescope buffers<cr>')
map('n', '<leader>fg', ':Telescope git_files<cr>')
map('n', '<leader>fp', ':Telescope grep_string<cr>')

LSP Config #

Here we define a number of keybindings in the on_attach function, which is called only on buffers with active language server. It means that if I open a file that I do not have an lsp setup for the keybindings won’t be set. I use default keybindings and use map_func from main init.lua.

map_func('<leader>e', vim.diagnostic.open_float)
--map_func('<C-e>', vim.diagnostic.open_float)
map_func('[d', vim.diagnostic.goto_prev, opts)
map_func(']d', vim.diagnostic.goto_next, opts)
map_func('<leader>q', vim.diagnostic.setloclist, opts)

local on_attach = function(client, bufnr)
  -- Enable completion triggered by <c-x><c-o>
  vim.api.nvim_buf_set_option(bufnr, 'omnifunc', 'v:lua.vim.lsp.omnifunc')

  -- See `:help vim.lsp.*` for documentation on any of the below functions
  local bufopts = { noremap=true, silent=true, buffer=bufnr }
  map_func('gD', vim.lsp.buf.declaration, bufopts)
  map_func('gd', vim.lsp.buf.definition, bufopts)
  map_func('K', vim.lsp.buf.hover, bufopts)
  map_func('gi', vim.lsp.buf.implementation, bufopts
  map_func('<C-k>', vim.lsp.buf.signature_help, bufopts)
  map_func('<leader>wa', vim.lsp.buf.add_workspace_folder, bufopts)
  map_func('<leader>wr', vim.lsp.buf.remove_workspace_folder, bufopts)
  map_func('<leader>wl', function()
    print(vim.inspect(vim.lsp.buf.list_workspace_folders()))
  end, bufopts)
  map_func('<leader>D', vim.lsp.buf.type_definition, bufopts)
  map_func('<leader>vrn', vim.lsp.buf.rename, bufopts)
  map_func('<leader>ca', vim.lsp.buf.code_action, bufopts)
  map_func('gr', vim.lsp.buf.references, bufopts)
  map_func('<leader>f', vim.lsp.buf.formatting, bufopts)
end
--
-- Setup lspconfig.
local capabilities = require('cmp_nvim_lsp').default_capabilities(vim.lsp.protocol.make_client_capabilities())

local lsp_flags = {
  -- This is the default in Nvim 0.7+
  debounce_text_changes = 150,
}
require('lspconfig')['gopls'].setup{
    on_attach = on_attach,
    flags = lsp_flags,
    capabilities = capabilities
}
require('lspconfig')['elixirls'].setup{
    on_attach = on_attach,
    flags = lsp_flags,
    capabilities = capabilities,
    cmd = {"/usr/bin/elixir-ls"}
}

Nvim tree #

require('nvim-tree').setup()

Nvimcmp #

local cmp = require('cmp')
cmp.setup{
	snippet = {
		expand = function(args)
			require('snippy').expand_snippet(args.body)
		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 = 'snippy' }, -- For snippy users.
    	}, 
    	{
      		{ name = 'buffer' },
    	})
}

Telescope #

Apart from key mappings I set property sort_mru to true to get the list of buffers from most recently used, and initial_mode to normal in order to set initial mode of buffer menu to normal mode. You can find parameter description here.

require('telescope').setup {
  pickers = {
    buffers = {
      sort_mru = true, --sort buffers by mru
      initial_mode = "normal" --start buffer picker in normal mode
    }
  }
}

Treesitter #

require'nvim-treesitter.configs'.setup {
  ensure_installed = { 'lua', 'go', 'rust', 'elixir', 'erlang' },
  sync_install = false,
  auto_install = true,

  highlight = {
    enable = true,
    additional_vim_regex_highlighting = false,
  },
}

Useful motions #

Here is the list of some useful motions that I try to add to my work.

  • yap copy until newline down
  • dap delete until newline down
  • vap/kvap select down/up until new line
  • va{ select until closing ‘}’ (va(, va’, va", etc.)
  • o insert and edit line below, O line above
  • I move to line start and edit
  • A move to line end and edit
  • VY copy whole line
  • Ctrl + a increment number
  • Ctrl + x decrement number
  • Ctrl + v select multiple lines, I to go into edit mode
  • '{'/'}' to jump between newlines