Neovim: The Future of Vim
Neovim is a modern fork of Vim that focuses on extensibility, usability, and performance. Created in 2014, it maintains Vim compatibility while adding powerful new features like built-in LSP support, Lua scripting, and better plugin architecture.
Why Choose Neovim?
- Modern architecture: Asynchronous job control and better plugin API
- Built-in LSP: Native Language Server Protocol support for Linux development
- Lua scripting: More powerful and faster than Vimscript
- Better defaults: Sensible configuration out of the box
- Active development: Regular updates and new features
- Tree-sitter: Advanced syntax highlighting and code parsing
- Embedded terminal: Built-in terminal emulator perfect for Linux workflows
- Linux-first design: Optimized for Linux development environments
Installation
Package Managers
# Ubuntu/Debian (latest stable)
sudo apt update
sudo apt install neovim
# Ubuntu/Debian (latest unstable)
sudo add-apt-repository ppa:neovim-ppa/unstable
sudo apt update
sudo apt install neovim
# CentOS/RHEL/Fedora
sudo dnf install neovim
# Arch Linux
sudo pacman -S neovim
# SUSE/openSUSE
sudo zypper install neovim
# Gentoo
sudo emerge app-editors/neovim
From Source
# Install dependencies (Ubuntu/Debian)
sudo apt install ninja-build gettext libtool libtool-bin autoconf automake cmake g++ pkg-config unzip curl doxygen
# Clone and build
git clone https://github.com/neovim/neovim
cd neovim
make CMAKE_BUILD_TYPE=RelWithDebInfo
sudo make install
AppImage (Portable)
# Download AppImage
curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim.appimage
chmod u+x nvim.appimage
sudo mv nvim.appimage /usr/local/bin/nvim
Configuration Structure
Neovim uses a different configuration structure than Vim:
# Configuration directory
~/.config/nvim/
# Main configuration file
~/.config/nvim/init.lua # Lua configuration (recommended)
# or
~/.config/nvim/init.vim # Vimscript configuration
# Plugin directory
~/.local/share/nvim/site/pack/
Basic Lua Configuration
init.lua Structure
-- ~/.config/nvim/init.lua
-- Basic settings
vim.opt.number = true
vim.opt.relativenumber = true
vim.opt.expandtab = true
vim.opt.shiftwidth = 4
vim.opt.tabstop = 4
vim.opt.softtabstop = 4
vim.opt.smartindent = true
vim.opt.wrap = false
vim.opt.swapfile = false
vim.opt.backup = false
vim.opt.undodir = os.getenv("HOME") .. "/.vim/undodir"
vim.opt.undofile = true
vim.opt.hlsearch = false
vim.opt.incsearch = true
vim.opt.termguicolors = true
vim.opt.scrolloff = 8
vim.opt.signcolumn = "yes"
vim.opt.isfname:append("@-@")
vim.opt.updatetime = 50
vim.opt.colorcolumn = "80"
-- Leader key
vim.g.mapleader = " "
-- Key mappings
vim.keymap.set("n", "<leader>pv", vim.cmd.Ex)
vim.keymap.set("v", "J", ":m '>+1<CR>gv=gv")
vim.keymap.set("v", "K", ":m '<-2<CR>gv=gv")
vim.keymap.set("n", "J", "mzJ`z")
vim.keymap.set("n", "<C-d>", "<C-d>zz")
vim.keymap.set("n", "<C-u>", "<C-u>zz")
vim.keymap.set("n", "n", "nzzzv")
vim.keymap.set("n", "N", "Nzzzv")
-- System clipboard
vim.keymap.set({"n", "v"}, "<leader>y", [["+y]])
vim.keymap.set("n", "<leader>Y", [["+Y]])
vim.keymap.set({"n", "v"}, "<leader>d", [["_d]])
-- Replace word under cursor
vim.keymap.set("n", "<leader>s", [[:%s/\<<C-r><C-w>\>/<C-r><C-w>/gI<Left><Left><Left>]])
-- Make file executable
vim.keymap.set("n", "<leader>x", "<cmd>!chmod +x %<CR>", { silent = true })
-- Auto commands
local augroup = vim.api.nvim_create_augroup
local autocmd = vim.api.nvim_create_autocmd
local yank_group = augroup('HighlightYank', {})
('TextYankPost', {
autocmdgroup = yank_group,
pattern = '*',
callback = function()
vim.highlight.on_yank({
higroup = 'IncSearch',
timeout = 40,
})
end,
})
-- Remove trailing whitespace on save
local trim_group = augroup('TrimWhitespace', {})
('BufWritePre', {
autocmdgroup = trim_group,
pattern = '*',
command = '%s/\\s\\+$//e',
})
Plugin Management with Packer
Installing Packer
git clone --depth 1 https://github.com/wbthomason/packer.nvim\
~/.local/share/nvim/site/pack/packer/start/packer.nvim
Packer Configuration
-- ~/.config/nvim/lua/plugins.lua
-- This file can be loaded by calling `lua require('plugins')` from your init.lua
-- Only required if you have packer configured as `opt`
vim.cmd [[packadd packer.nvim]]
return require('packer').startup(function(use)
-- Packer can manage itself
'wbthomason/packer.nvim'
use
-- Fuzzy finder
{
use 'nvim-telescope/telescope.nvim', tag = '0.1.4',
requires = { {'nvim-lua/plenary.nvim'} }
}
-- Color schemes
({
use'rose-pine/neovim',
as = 'rose-pine',
config = function()
vim.cmd('colorscheme rose-pine')
end
})
('nvim-treesitter/nvim-treesitter', {run = ':TSUpdate'})
use('nvim-treesitter/playground')
use('theprimeagen/harpoon')
use('mbbill/undotree')
use('tpope/vim-fugitive')
use
-- LSP Support
{
use 'VonHeikemen/lsp-zero.nvim',
branch = 'v3.x',
requires = {
--- Uncomment these if you want to manage LSP servers from neovim
{'williamboman/mason.nvim'},
{'williamboman/mason-lspconfig.nvim'},
-- LSP Support
{'neovim/nvim-lspconfig'},
-- Autocompletion
{'hrsh7th/nvim-cmp'},
{'hrsh7th/cmp-nvim-lsp'},
{'L3MON4D3/LuaSnip'},
}
}
-- File explorer
{
use 'nvim-tree/nvim-tree.lua',
requires = {
'nvim-tree/nvim-web-devicons', -- optional, for file icons
},
tag = 'nightly' -- optional, updated every week
}
-- Status line
{
use 'nvim-lualine/lualine.nvim',
requires = { 'nvim-tree/nvim-web-devicons', opt = true }
}
-- Git integration
{
use 'lewis6991/gitsigns.nvim',
config = function()
require('gitsigns').setup()
end
}
-- Auto pairs
{
use "windwp/nvim-autopairs",
config = function() require("nvim-autopairs").setup {} end
}
-- Comments
{
use 'numToStr/Comment.nvim',
config = function()
require('Comment').setup()
end
}
-- Terminal integration
{"akinsho/toggleterm.nvim", tag = '*', config = function()
use require("toggleterm").setup()
end}
-- Which key
{
use "folke/which-key.nvim",
config = function()
vim.o.timeout = true
vim.o.timeoutlen = 300
require("which-key").setup {}
end
}
-- Indent guides
"lukas-reineke/indent-blankline.nvim"
use
-- Buffer line
{'akinsho/bufferline.nvim', tag = "*", requires = 'nvim-tree/nvim-web-devicons'}
use
end)
Loading Plugins in init.lua
-- ~/.config/nvim/init.lua
require('plugins')
-- Plugin configurations
require('telescope').setup{}
require('nvim-tree').setup{}
require('lualine').setup{}
require('bufferline').setup{}
Language Server Protocol (LSP) Setup
LSP Configuration
-- ~/.config/nvim/lua/lsp-config.lua
local lsp_zero = require('lsp-zero')
lsp_zero.on_attach(function(client, bufnr)
local opts = {buffer = bufnr, remap = false}
vim.keymap.set("n", "gd", function() vim.lsp.buf.definition() end, opts)
vim.keymap.set("n", "K", function() vim.lsp.buf.hover() end, opts)
vim.keymap.set("n", "<leader>vws", function() vim.lsp.buf.workspace_symbol() end, opts)
vim.keymap.set("n", "<leader>vd", function() vim.diagnostic.open_float() end, opts)
vim.keymap.set("n", "[d", function() vim.diagnostic.goto_next() end, opts)
vim.keymap.set("n", "]d", function() vim.diagnostic.goto_prev() end, opts)
vim.keymap.set("n", "<leader>vca", function() vim.lsp.buf.code_action() end, opts)
vim.keymap.set("n", "<leader>vrr", function() vim.lsp.buf.references() end, opts)
vim.keymap.set("n", "<leader>vrn", function() vim.lsp.buf.rename() end, opts)
vim.keymap.set("i", "<C-h>", function() vim.lsp.buf.signature_help() end, opts)
end)
-- Mason setup for LSP server management
require('mason').setup({})
require('mason-lspconfig').setup({
ensure_installed = {
'tsserver',
'rust_analyzer',
'gopls',
'pyright',
'lua_ls',
'clangd',
},
handlers = {
lsp_zero.default_setup,
lua_ls = function()
local lua_opts = lsp_zero.nvim_lua_ls()
require('lspconfig').lua_ls.setup(lua_opts)
end,
}
})
-- Completion setup
local cmp = require('cmp')
local cmp_select = {behavior = cmp.SelectBehavior.Select}
cmp.setup({
sources = {
{name = 'path'},
{name = 'nvim_lsp'},
{name = 'nvim_lua'},
{name = 'luasnip', keyword_length = 2},
{name = 'buffer', keyword_length = 3},
},
formatting = lsp_zero.cmp_format(),
mapping = cmp.mapping.preset.insert({
['<C-p>'] = cmp.mapping.select_prev_item(cmp_select),
['<C-n>'] = cmp.mapping.select_next_item(cmp_select),
['<C-y>'] = cmp.mapping.confirm({ select = true }),
['<C-Space>'] = cmp.mapping.complete(),
}),
})
Installing Language Servers
# Using Mason (recommended)
:Mason
# Then install servers interactively
# Or install manually
npm install -g typescript-language-server
pip install python-lsp-server
go install golang.org/x/tools/gopls@latest
Tree-sitter Configuration
Tree-sitter Setup
-- ~/.config/nvim/lua/treesitter-config.lua
require'nvim-treesitter.configs'.setup {
-- A list of parser names, or "all"
ensure_installed = {
"c", "lua", "vim", "vimdoc", "query", "python", "javascript",
"typescript", "rust", "go", "html", "css", "json", "yaml", "markdown"
},
-- Install parsers synchronously (only applied to `ensure_installed`)
sync_install = false,
-- Automatically install missing parsers when entering buffer
auto_install = true,
highlight = {
enable = true,
-- Setting this to true will run `:h syntax` and tree-sitter at the same time.
additional_vim_regex_highlighting = false,
},
indent = {
enable = true
},
incremental_selection = {
enable = true,
keymaps = {
init_selection = "gnn",
node_incremental = "grn",
scope_incremental = "grc",
node_decremental = "grm",
},
},
textobjects = {
select = {
enable = true,
lookahead = true,
keymaps = {
["af"] = "@function.outer",
["if"] = "@function.inner",
["ac"] = "@class.outer",
["ic"] = "@class.inner",
},
},
},
}
Advanced Neovim Features
Telescope Configuration
-- ~/.config/nvim/lua/telescope-config.lua
local builtin = require('telescope.builtin')
vim.keymap.set('n', '<leader>pf', builtin.find_files, {})
vim.keymap.set('n', '<C-p>', builtin.git_files, {})
vim.keymap.set('n', '<leader>ps', function()
builtin.grep_string({ search = vim.fn.input("Grep > ") })
end)
vim.keymap.set('n', '<leader>vh', builtin.help_tags, {})
require('telescope').setup{
defaults = {
file_ignore_patterns = {
"node_modules",
".git",
"dist",
"build"
},
mappings = {
i = {
["<C-h>"] = "which_key"
}
}
},
pickers = {
find_files = {
theme = "dropdown",
}
},
extensions = {
fzf = {
fuzzy = true,
override_generic_sorter = true,
override_file_sorter = true,
case_mode = "smart_case",
}
}
}
-- Load extensions
require('telescope').load_extension('fzf')
Terminal Integration
-- ~/.config/nvim/lua/terminal-config.lua
require("toggleterm").setup{
size = 20,
open_mapping = [[<c-\>]],
hide_numbers = true,
shade_filetypes = {},
shade_terminals = true,
shading_factor = 2,
start_in_insert = true,
insert_mappings = true,
persist_size = true,
direction = "float",
close_on_exit = true,
shell = vim.o.shell,
float_opts = {
border = "curved",
winblend = 0,
highlights = {
border = "Normal",
background = "Normal",
},
},
}
-- Terminal keymaps
function _G.set_terminal_keymaps()
local opts = {buffer = 0}
vim.keymap.set('t', '<esc>', [[<C-\><C-n>]], opts)
vim.keymap.set('t', 'jk', [[<C-\><C-n>]], opts)
vim.keymap.set('t', '<C-h>', [[<Cmd>wincmd h<CR>]], opts)
vim.keymap.set('t', '<C-j>', [[<Cmd>wincmd j<CR>]], opts)
vim.keymap.set('t', '<C-k>', [[<Cmd>wincmd k<CR>]], opts)
vim.keymap.set('t', '<C-l>', [[<Cmd>wincmd l<CR>]], opts)
end
vim.cmd('autocmd! TermOpen term://* lua set_terminal_keymaps()')
Real-World Development Setups
Python Development
-- ~/.config/nvim/lua/python-config.lua
-- Python-specific settings
vim.api.nvim_create_autocmd("FileType", {
pattern = "python",
callback = function()
vim.opt_local.expandtab = true
vim.opt_local.shiftwidth = 4
vim.opt_local.tabstop = 4
vim.opt_local.softtabstop = 4
vim.opt_local.colorcolumn = "88"
end,
})
-- Python debugging with DAP
local dap = require('dap')
dap.adapters.python = {
type = 'executable',
command = 'python',
args = { '-m', 'debugpy.adapter' },
}
dap.configurations.python = {
{
type = 'python',
request = 'launch',
name = "Launch file",
program = "${file}",
pythonPath = function()
return '/usr/bin/python'
end,
},
}
-- Python testing
vim.keymap.set('n', '<leader>pt', ':!python -m pytest %<CR>')
vim.keymap.set('n', '<leader>pr', ':!python %<CR>')
JavaScript/TypeScript Development
-- ~/.config/nvim/lua/js-config.lua
-- JavaScript/TypeScript settings
vim.api.nvim_create_autocmd("FileType", {
pattern = {"javascript", "typescript", "javascriptreact", "typescriptreact"},
callback = function()
vim.opt_local.expandtab = true
vim.opt_local.shiftwidth = 2
vim.opt_local.tabstop = 2
vim.opt_local.softtabstop = 2
end,
})
-- ESLint and Prettier integration
local null_ls = require("null-ls")
null_ls.setup({
sources = {
null_ls.builtins.formatting.prettier,
null_ls.builtins.diagnostics.eslint,
null_ls.builtins.code_actions.eslint,
},
})
-- Auto-format on save
vim.api.nvim_create_autocmd("BufWritePre", {
pattern = {"*.js", "*.jsx", "*.ts", "*.tsx"},
callback = function()
vim.lsp.buf.format()
end,
})
Go Development
-- ~/.config/nvim/lua/go-config.lua
-- Go settings
vim.api.nvim_create_autocmd("FileType", {
pattern = "go",
callback = function()
vim.opt_local.expandtab = false
vim.opt_local.tabstop = 4
vim.opt_local.shiftwidth = 4
end,
})
-- Go-specific keymaps
vim.api.nvim_create_autocmd("FileType", {
pattern = "go",
callback = function()
local opts = { buffer = true }
vim.keymap.set('n', '<leader>gr', ':!go run %<CR>', opts)
vim.keymap.set('n', '<leader>gt', ':!go test<CR>', opts)
vim.keymap.set('n', '<leader>gb', ':!go build<CR>', opts)
vim.keymap.set('n', '<leader>gf', ':!gofmt -w %<CR>', opts)
end,
})
Debugging with DAP
DAP Configuration
-- ~/.config/nvim/lua/dap-config.lua
local dap = require('dap')
local dapui = require('dapui')
-- DAP UI setup
dapui.setup()
-- Auto-open/close DAP UI
dap.listeners.after.event_initialized["dapui_config"] = function()
dapui.open()
end
dap.listeners.before.event_terminated["dapui_config"] = function()
dapui.close()
end
dap.listeners.before.event_exited["dapui_config"] = function()
dapui.close()
end
-- Keymaps
vim.keymap.set('n', '<F5>', dap.continue)
vim.keymap.set('n', '<F10>', dap.step_over)
vim.keymap.set('n', '<F11>', dap.step_into)
vim.keymap.set('n', '<F12>', dap.step_out)
vim.keymap.set('n', '<leader>b', dap.toggle_breakpoint)
vim.keymap.set('n', '<leader>B', function()
dap.set_breakpoint(vim.fn.input('Breakpoint condition: '))
end)
vim.keymap.set('n', '<leader>lp', function()
dap.set_breakpoint(nil, nil, vim.fn.input('Log point message: '))
end)
vim.keymap.set('n', '<leader>dr', dap.repl.open)
vim.keymap.set('n', '<leader>dl', dap.run_last)
Performance Optimization
Startup Optimization
-- ~/.config/nvim/lua/performance.lua
-- Disable unused providers
vim.g.loaded_python_provider = 0
vim.g.loaded_ruby_provider = 0
vim.g.loaded_perl_provider = 0
vim.g.loaded_node_provider = 0
-- Lazy load plugins
vim.opt.runtimepath:prepend(vim.fn.stdpath("data") .. "/lazy/lazy.nvim")
-- Optimize for large files
vim.api.nvim_create_autocmd("BufReadPre", {
callback = function()
local file_size = vim.fn.getfsize(vim.fn.expand("%"))
if file_size > 1024 * 1024 then -- 1MB
vim.opt_local.syntax = "off"
vim.opt_local.filetype = ""
vim.opt_local.undolevels = -1
vim.opt_local.swapfile = false
vim.opt_local.loadplugins = false
end
end,
})
Migration from Vim
Compatibility Layer
-- ~/.config/nvim/lua/vim-compat.lua
-- Vim compatibility settings
vim.opt.compatible = false
vim.opt.encoding = 'utf-8'
vim.opt.fileencoding = 'utf-8'
-- Source existing vimrc if needed
local vimrc = vim.fn.expand("~/.vimrc")
if vim.fn.filereadable(vimrc) == 1 then
vim.cmd("source " .. vimrc)
end
-- Convert common vim settings to lua
local function convert_vim_settings()
-- Add your vim-to-lua conversions here
if vim.g.loaded_vimrc then
-- Convert specific vim settings
vim.opt.number = vim.g.vim_number or false
vim.opt.relativenumber = vim.g.vim_relativenumber or false
end
end
() convert_vim_settings
Troubleshooting
Common Issues
-- ~/.config/nvim/lua/troubleshooting.lua
-- Health check
vim.keymap.set('n', '<leader>h', ':checkhealth<CR>')
-- Plugin debugging
vim.keymap.set('n', '<leader>pi', ':PackerInstall<CR>')
vim.keymap.set('n', '<leader>ps', ':PackerSync<CR>')
vim.keymap.set('n', '<leader>pc', ':PackerClean<CR>')
-- LSP debugging
vim.keymap.set('n', '<leader>li', ':LspInfo<CR>')
vim.keymap.set('n', '<leader>lr', ':LspRestart<CR>')
-- Log files
-- ~/.local/share/nvim/lsp.log
-- ~/.cache/nvim/packer.nvim.log
Neovim represents the future of modal editing, combining Vim’s efficiency with modern development tools and practices. Its built-in LSP support, Lua scripting, and active plugin ecosystem make it an excellent choice for developers seeking a powerful, customizable editor.