Neovim
GitHub Overview
neovim/neovim
Vim-fork focused on extensibility and usability
Topics
Star History
Development Tool
Neovim
Overview
Neovim is a text editor developed as a modern fork of Vim. While inheriting Vim's philosophy, it adds modern features like Lua scripting, built-in LSP, and Tree-sitter, improving extensibility and maintainability.
Details
Neovim was started in 2014 by Thiago de Arruda as a fork of Vim. Compared to Vim being maintained nearly 100% by Bram Moolenaar alone, Neovim has a much wider contributor base and is supported by an active development community.
Neovim's greatest innovation is the introduction of Lua as a first-class scripting language. In addition to traditional Vimscript, configuration and plugin development with Lua is now possible, enabling faster and more maintainable extensions. Key features include built-in LSP (Language Server Protocol) support, advanced syntax highlighting with Tree-sitter, and improved asynchronous processing.
The "headless" core editor design makes integration with other programs easier, enabling connections with IDEs and browsers. While maintaining backward compatibility with Vim, it provides features adapted to modern development environments. It also supports terminal and remote server work via SSH, achieving efficient workflows through modal editing.
Advantages and Disadvantages
Advantages
- Modern Architecture: Lua scripting, built-in LSP, Tree-sitter support
- Active Development Community: Continuous improvements by wider contributor base than Vim
- High Performance: Fast and efficient operation through optimized lightweight design
- Excellent Extensibility: Flexible customization through both Lua and Vimscript
- Headless Design: Easy integration with other programs and IDEs
- Vim Compatibility: Can directly utilize knowledge learned from Vim
- Remote Work Ready: Optimal for terminal-based SSH environments
Disadvantages
- Steep Learning Curve: Difficult to master modal editing
- Plugin Dependency: Advanced features require many plugins
- Confusing Documentation: Information scattered due to mixing of Lua and Vimscript
- Configuration Complexity: Deep knowledge required for optimal environment setup
- Legacy Features: Inherits many old features from Vim
- Terminal Limitations: Limited GUI functionality
- Initial Setup: Significant configuration required even for basic use
Key Links
Code Examples
Basic init.lua Configuration
-- ~/.config/nvim/init.lua - Lua Configuration
-- Basic settings
vim.opt.number = true -- Show line numbers
vim.opt.relativenumber = true -- Show relative line numbers
vim.opt.mouse = 'a' -- Enable mouse
vim.opt.ignorecase = true -- Ignore case
vim.opt.smartcase = true -- Case sensitive when uppercase included
vim.opt.hlsearch = false -- Disable search highlight
vim.opt.wrap = false -- Disable line wrapping
vim.opt.breakindent = true -- Continue indent
vim.opt.tabstop = 4 -- Tab width
vim.opt.shiftwidth = 4 -- Shift width
vim.opt.expandtab = true -- Convert tabs to spaces
-- Appearance settings
vim.opt.termguicolors = true -- True color support
vim.opt.signcolumn = 'yes' -- Always show sign column
vim.opt.cmdheight = 1 -- Command line height
vim.opt.scrolloff = 8 -- Scroll offset
vim.opt.sidescrolloff = 8 -- Horizontal scroll offset
-- File settings
vim.opt.backup = false -- Disable backup
vim.opt.writebackup = false -- Disable write backup
vim.opt.undofile = true -- Enable undo file
vim.opt.swapfile = false -- Disable swap file
-- Key mapping settings
vim.g.mapleader = ' ' -- Set leader key to space
-- Basic key mappings
local keymap = vim.keymap.set
keymap('n', '<leader>w', ':w<CR>', { desc = 'Save file' })
keymap('n', '<leader>q', ':q<CR>', { desc = 'Quit' })
keymap('n', '<leader>h', ':nohlsearch<CR>', { desc = 'Clear search highlight' })
keymap('n', '<C-h>', '<C-w>h', { desc = 'Move to left window' })
keymap('n', '<C-j>', '<C-w>j', { desc = 'Move to bottom window' })
keymap('n', '<C-k>', '<C-w>k', { desc = 'Move to top window' })
keymap('n', '<C-l>', '<C-w>l', { desc = 'Move to right window' })
-- Visual mode movement
keymap('v', 'J', ":m '>+1<CR>gv=gv", { desc = 'Move selected lines down' })
keymap('v', 'K', ":m '<-2<CR>gv=gv", { desc = 'Move selected lines up' })
-- Center screen during search
keymap('n', 'n', 'nzzzv')
keymap('n', 'N', 'Nzzzv')
-- System clipboard
keymap('n', '<leader>y', '"+y', { desc = 'Yank to system clipboard' })
keymap('v', '<leader>y', '"+y', { desc = 'Yank to system clipboard' })
keymap('n', '<leader>p', '"+p', { desc = 'Paste from system clipboard' })
Plugin Management (lazy.nvim)
-- ~/.config/nvim/lua/plugins/init.lua
return {
-- Color scheme
{
'folke/tokyonight.nvim',
lazy = false,
priority = 1000,
config = function()
vim.cmd([[colorscheme tokyonight]])
end,
},
-- File explorer
{
'nvim-tree/nvim-tree.lua',
dependencies = { 'nvim-tree/nvim-web-devicons' },
config = function()
require('nvim-tree').setup{}
vim.keymap.set('n', '<leader>e', ':NvimTreeToggle<CR>', { desc = 'Toggle file explorer' })
end,
},
-- Fuzzy finder
{
'nvim-telescope/telescope.nvim',
dependencies = { 'nvim-lua/plenary.nvim' },
config = function()
local telescope = require('telescope')
telescope.setup{}
local builtin = require('telescope.builtin')
vim.keymap.set('n', '<leader>ff', builtin.find_files, { desc = 'Find files' })
vim.keymap.set('n', '<leader>fg', builtin.live_grep, { desc = 'Live grep' })
vim.keymap.set('n', '<leader>fb', builtin.buffers, { desc = 'Find buffers' })
vim.keymap.set('n', '<leader>fh', builtin.help_tags, { desc = 'Help tags' })
end,
},
-- LSP configuration
{
'neovim/nvim-lspconfig',
dependencies = {
'williamboman/mason.nvim',
'williamboman/mason-lspconfig.nvim',
},
config = function()
require('mason').setup()
require('mason-lspconfig').setup{
ensure_installed = { 'lua_ls', 'pyright', 'tsserver' }
}
local lspconfig = require('lspconfig')
-- Lua LSP
lspconfig.lua_ls.setup{
settings = {
Lua = {
diagnostics = {
globals = {'vim'}
}
}
}
}
-- Python LSP
lspconfig.pyright.setup{}
-- TypeScript LSP
lspconfig.tsserver.setup{}
-- LSP key mappings
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, { desc = 'Go to definition' })
vim.keymap.set('n', 'K', vim.lsp.buf.hover, { desc = 'Hover documentation' })
vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, { desc = 'Go to implementation' })
vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, { desc = 'Rename' })
vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action, { desc = 'Code action' })
end,
},
-- Completion engine
{
'hrsh7th/nvim-cmp',
dependencies = {
'hrsh7th/cmp-nvim-lsp',
'hrsh7th/cmp-buffer',
'hrsh7th/cmp-path',
'hrsh7th/cmp-cmdline',
'L3MON4D3/LuaSnip',
'saadparwaiz1/cmp_luasnip',
},
config = function()
local cmp = require('cmp')
local luasnip = require('luasnip')
cmp.setup{
snippet = {
expand = function(args)
luasnip.lsp_expand(args.body)
end,
},
mapping = cmp.mapping.preset.insert({
['<C-d>'] = 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 }),
['<Tab>'] = cmp.mapping(function(fallback)
if cmp.visible() then
cmp.select_next_item()
elseif luasnip.expand_or_jumpable() then
luasnip.expand_or_jump()
else
fallback()
end
end, { 'i', 's' }),
}),
sources = cmp.config.sources({
{ name = 'nvim_lsp' },
{ name = 'luasnip' },
}, {
{ name = 'buffer' },
})
}
end,
},
-- Tree-sitter
{
'nvim-treesitter/nvim-treesitter',
build = ':TSUpdate',
config = function()
require('nvim-treesitter.configs').setup{
ensure_installed = {
'lua', 'python', 'javascript', 'typescript', 'html', 'css'
},
highlight = {
enable = true,
},
indent = {
enable = true,
},
}
end,
},
-- Status line
{
'nvim-lualine/lualine.nvim',
dependencies = { 'nvim-tree/nvim-web-devicons' },
config = function()
require('lualine').setup{
options = {
theme = 'tokyonight'
}
}
end,
},
}
Custom Functions and Commands
-- ~/.config/nvim/lua/utils.lua
local M = {}
-- Function to trim trailing whitespace
function M.trim_whitespace()
local save_cursor = vim.fn.getpos('.')
vim.cmd([[%s/\s\+$//e]])
vim.fn.setpos('.', save_cursor)
end
-- Function to duplicate current line
function M.duplicate_line()
local line = vim.fn.getline('.')
vim.fn.append('.', line)
end
-- Function to get project root
function M.get_project_root()
local markers = {'.git', 'package.json', 'Cargo.toml', 'pyproject.toml'}
local current_file = vim.fn.expand('%:p:h')
for _, marker in ipairs(markers) do
local root = vim.fn.finddir(marker, current_file .. ';')
if root ~= '' then
return vim.fn.fnamemodify(root, ':h')
end
end
return current_file
end
-- Function to save file with new name
function M.save_as()
local current_file = vim.fn.expand('%:t')
local new_name = vim.fn.input('New filename: ', current_file)
if new_name ~= '' and new_name ~= current_file then
vim.cmd('saveas ' .. new_name)
end
end
-- Function to quickly edit config
function M.edit_config()
vim.cmd('edit ~/.config/nvim/init.lua')
end
-- Register commands
vim.api.nvim_create_user_command('TrimWhitespace', M.trim_whitespace, {})
vim.api.nvim_create_user_command('DuplicateLine', M.duplicate_line, {})
vim.api.nvim_create_user_command('SaveAs', M.save_as, {})
vim.api.nvim_create_user_command('EditConfig', M.edit_config, {})
-- Set key mappings
vim.keymap.set('n', '<leader>tw', M.trim_whitespace, { desc = 'Trim whitespace' })
vim.keymap.set('n', '<leader>dl', M.duplicate_line, { desc = 'Duplicate line' })
vim.keymap.set('n', '<leader>sa', M.save_as, { desc = 'Save as' })
vim.keymap.set('n', '<leader>ec', M.edit_config, { desc = 'Edit config' })
return M
Autocommands and Events
-- ~/.config/nvim/lua/autocmds.lua
local augroup = vim.api.nvim_create_augroup
local autocmd = vim.api.nvim_create_autocmd
-- General autocommands
local general = augroup('General', { clear = true })
autocmd('TextYankPost', {
group = general,
pattern = '*',
callback = function()
vim.highlight.on_yank({ higroup = 'Visual', timeout = 200 })
end,
desc = 'Highlight yanked text',
})
autocmd('BufReadPost', {
group = general,
pattern = '*',
callback = function()
local mark = vim.api.nvim_buf_get_mark(0, '"')
local lcount = vim.api.nvim_buf_line_count(0)
if mark[1] > 0 and mark[1] <= lcount then
pcall(vim.api.nvim_win_set_cursor, 0, mark)
end
end,
desc = 'Restore cursor position',
})
-- File type specific settings
local filetypes = augroup('FileTypes', { clear = true })
autocmd('FileType', {
group = filetypes,
pattern = { 'python' },
callback = function()
vim.opt_local.tabstop = 4
vim.opt_local.shiftwidth = 4
vim.opt_local.expandtab = true
end,
})
autocmd('FileType', {
group = filetypes,
pattern = { 'javascript', 'typescript', 'html', 'css', 'json' },
callback = function()
vim.opt_local.tabstop = 2
vim.opt_local.shiftwidth = 2
vim.opt_local.expandtab = true
end,
})
autocmd('FileType', {
group = filetypes,
pattern = { 'lua' },
callback = function()
vim.opt_local.tabstop = 2
vim.opt_local.shiftwidth = 2
vim.opt_local.expandtab = true
end,
})
-- Save actions
local save_group = augroup('SaveGroup', { clear = true })
autocmd('BufWritePre', {
group = save_group,
pattern = { '*.py', '*.js', '*.ts', '*.lua', '*.html', '*.css' },
callback = function()
require('utils').trim_whitespace()
end,
desc = 'Trim whitespace on save',
})
-- LSP configuration
local lsp_group = augroup('LspGroup', { clear = true })
autocmd('LspAttach', {
group = lsp_group,
callback = function(ev)
-- LSP key mappings on attach
local opts = { buffer = ev.buf }
vim.keymap.set('n', 'gD', vim.lsp.buf.declaration, opts)
vim.keymap.set('n', 'gd', vim.lsp.buf.definition, opts)
vim.keymap.set('n', 'K', vim.lsp.buf.hover, opts)
vim.keymap.set('n', 'gi', vim.lsp.buf.implementation, opts)
vim.keymap.set('n', '<C-k>', vim.lsp.buf.signature_help, opts)
vim.keymap.set('n', '<leader>rn', vim.lsp.buf.rename, opts)
vim.keymap.set('n', '<leader>ca', vim.lsp.buf.code_action, opts)
vim.keymap.set('n', 'gr', vim.lsp.buf.references, opts)
vim.keymap.set('n', '<leader>f', function()
vim.lsp.buf.format { async = true }
end, opts)
end,
})