vim-neovim
Use this skill when configuring Neovim, writing Lua plugins, setting up keybindings, or optimizing the Vim editing workflow. Triggers on Neovim configuration, init.lua, lazy.nvim, LSP setup, telescope, treesitter, vim motions, keymaps, and any task requiring Vim or Neovim customization.
devtools neovimvimluaeditorpluginskeybindingsWhat is vim-neovim?
Use this skill when configuring Neovim, writing Lua plugins, setting up keybindings, or optimizing the Vim editing workflow. Triggers on Neovim configuration, init.lua, lazy.nvim, LSP setup, telescope, treesitter, vim motions, keymaps, and any task requiring Vim or Neovim customization.
vim-neovim
vim-neovim is a production-ready AI agent skill for claude-code, gemini-cli, openai-codex. Configuring Neovim, writing Lua plugins, setting up keybindings, or optimizing the Vim editing workflow.
Quick Facts
| Field | Value |
|---|---|
| Category | devtools |
| Version | 0.1.0 |
| Platforms | claude-code, gemini-cli, openai-codex |
| License | MIT |
How to Install
- Make sure you have Node.js installed on your machine.
- Run the following command in your terminal:
npx skills add AbsolutelySkilled/AbsolutelySkilled --skill vim-neovim- The vim-neovim skill is now available in your AI coding agent (Claude Code, Gemini CLI, OpenAI Codex, etc.).
Overview
Neovim is a hyperextensible Vim-based text editor configured entirely in Lua.
The ~/.config/nvim/init.lua file is the entry point. Plugins are managed via
lazy.nvim, LSPs via mason.nvim, syntax via nvim-treesitter, and fuzzy
finding via telescope.nvim. Neovim exposes a rich Lua API (vim.api,
vim.keymap, vim.opt, vim.fn) for deep customization without Vimscript.
Tags
neovim vim lua editor plugins keybindings
Platforms
- claude-code
- gemini-cli
- openai-codex
Related Skills
Pair vim-neovim with these complementary skills:
Frequently Asked Questions
What is vim-neovim?
Use this skill when configuring Neovim, writing Lua plugins, setting up keybindings, or optimizing the Vim editing workflow. Triggers on Neovim configuration, init.lua, lazy.nvim, LSP setup, telescope, treesitter, vim motions, keymaps, and any task requiring Vim or Neovim customization.
How do I install vim-neovim?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill vim-neovim in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support vim-neovim?
This skill works with claude-code, gemini-cli, openai-codex. Install it once and use it across any supported AI coding agent.
Maintainers
Generated from AbsolutelySkilled
SKILL.md
Vim / Neovim
Neovim is a hyperextensible Vim-based text editor configured entirely in Lua.
The ~/.config/nvim/init.lua file is the entry point. Plugins are managed via
lazy.nvim, LSPs via mason.nvim, syntax via nvim-treesitter, and fuzzy
finding via telescope.nvim. Neovim exposes a rich Lua API (vim.api,
vim.keymap, vim.opt, vim.fn) for deep customization without Vimscript.
When to use this skill
Trigger this skill when the user:
- Bootstraps or restructures an
init.luaor~/.config/nvim/directory - Installs or configures plugins with
lazy.nvim - Sets up an LSP server with
mason.nvim+nvim-lspconfig - Configures
telescope.nvimpickers or extensions - Installs or queries
nvim-treesitterparsers - Adds or refactors keymaps with
vim.keymap.set - Writes a custom Lua plugin, module, or autocommand
Do NOT trigger this skill for:
- Generic shell scripting or terminal multiplexer questions unrelated to Neovim
- VS Code, JetBrains, or other editors unless explicitly comparing to Neovim
Key principles
- Lua over Vimscript - All new configuration and plugins must be written in
Lua. Use
vim.cmdonly for legacy Vimscript interop where no Lua API exists. - Lazy-load everything - Plugins should specify
event,ft,cmd, orkeysin theirlazy.nvimspec so startup time stays under 50 ms. - Structured config - Split concerns into
lua/config/(options, keymaps, autocmds) andlua/plugins/(one file per plugin or logical group). - LSP-native features first - Prefer built-in LSP for go-to-definition, rename, diagnostics, and formatting before reaching for external plugins.
- No global namespace pollution - Wrap plugin code in modules and return
public APIs. Never define functions at the global
_Glevel.
Core concepts
Modes
| Mode | Key | Purpose |
|---|---|---|
| Normal | <Esc> |
Navigation and operator entry |
| Insert | i, a, o |
Text insertion |
| Visual | v, V, <C-v> |
Selection (char/line/block) |
| Command | : |
Ex commands |
| Terminal | :terminal + i |
Embedded shell |
Motions
Motions describe where to move: w (word), b (back word), e (end of word),
0/^/$ (line start/first-char/end), gg/G (file start/end), % (matching bracket),
f{char} (find char), t{char} (till char), /{pattern} (search forward).
Operators (d, c, y, =, >) combine with motions: dw, ci", ya{.
Text objects
i (inner) and a (around): iw (inner word), i" (inner quotes), i{ (inner braces),
ip (inner paragraph), it (inner tag). Use with any operator.
Registers
""- default (unnamed) register"0- last yank"+/"*- system clipboard"_- black hole (discard)"/- last search pattern
Access in insert mode with <C-r>{register}.
Lua API surface
vim.opt.option = value -- set option (OOP style)
vim.o.option = value -- set global option (raw)
vim.keymap.set(mode, lhs, rhs, opts) -- define keymap
vim.api.nvim_create_autocmd(event, opts) -- autocommand
vim.api.nvim_create_user_command(name, fn, opts) -- user command
vim.api.nvim_buf_get_lines(0, 0, -1, false) -- buffer lines
vim.fn.expand("%:p") -- call Vimscript function
vim.cmd("colorscheme catppuccin") -- run Ex commandCommon tasks
1. Bootstrap init.lua with lazy.nvim
-- ~/.config/nvim/init.lua
-- Bootstrap lazy.nvim
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", lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
-- Leader key must be set before lazy loads plugins
vim.g.mapleader = " "
vim.g.maplocalleader = "\\"
require("lazy").setup("plugins", {
change_detection = { notify = false },
install = { colorscheme = { "catppuccin", "habamax" } },
performance = {
rtp = {
disabled_plugins = {
"gzip", "matchit", "netrwPlugin", "tarPlugin",
"tohtml", "tutor", "zipPlugin",
},
},
},
})
require("config.options")
require("config.keymaps")
require("config.autocmds")Each file in ~/.config/nvim/lua/plugins/ is auto-loaded by lazy.nvim and
must return a plugin spec table (or array of specs).
2. Configure LSP with mason
-- lua/plugins/lsp.lua
return {
{
"williamboman/mason.nvim",
build = ":MasonUpdate",
opts = {},
},
{
"williamboman/mason-lspconfig.nvim",
dependencies = { "williamboman/mason.nvim", "neovim/nvim-lspconfig" },
opts = {
ensure_installed = { "lua_ls", "ts_ls", "pyright", "rust_analyzer" },
automatic_installation = true,
},
},
{
"neovim/nvim-lspconfig",
event = { "BufReadPre", "BufNewFile" },
config = function()
local lspconfig = require("lspconfig")
local capabilities = require("cmp_nvim_lsp").default_capabilities()
local on_attach = function(_, bufnr)
local opts = { buffer = bufnr, silent = true }
vim.keymap.set("n", "gd", vim.lsp.buf.definition, opts)
vim.keymap.set("n", "gr", vim.lsp.buf.references, opts)
vim.keymap.set("n", "K", vim.lsp.buf.hover, opts)
vim.keymap.set("n", "<leader>rn", vim.lsp.buf.rename, opts)
vim.keymap.set({ "n", "v" }, "<leader>ca", vim.lsp.buf.code_action, opts)
vim.keymap.set("n", "<leader>f", function()
vim.lsp.buf.format({ async = true })
end, opts)
end
local servers = { "lua_ls", "ts_ls", "pyright", "rust_analyzer" }
for _, server in ipairs(servers) do
lspconfig[server].setup({ capabilities = capabilities, on_attach = on_attach })
end
-- Diagnostics UI
vim.diagnostic.config({
virtual_text = { prefix = "●" },
signs = true,
underline = true,
update_in_insert = false,
severity_sort = true,
})
end,
},
}3. Set up telescope
-- lua/plugins/telescope.lua
return {
{
"nvim-telescope/telescope.nvim",
cmd = "Telescope",
keys = {
{ "<leader>ff", "<cmd>Telescope find_files<cr>", desc = "Find files" },
{ "<leader>fg", "<cmd>Telescope live_grep<cr>", desc = "Live grep" },
{ "<leader>fb", "<cmd>Telescope buffers<cr>", desc = "Buffers" },
{ "<leader>fh", "<cmd>Telescope help_tags<cr>", desc = "Help tags" },
{ "<leader>fr", "<cmd>Telescope oldfiles<cr>", desc = "Recent files" },
},
dependencies = {
"nvim-lua/plenary.nvim",
{ "nvim-telescope/telescope-fzf-native.nvim", build = "make" },
},
config = function()
local telescope = require("telescope")
telescope.setup({
defaults = {
sorting_strategy = "ascending",
layout_config = { prompt_position = "top" },
mappings = {
i = {
["<C-j>"] = "move_selection_next",
["<C-k>"] = "move_selection_previous",
["<C-q>"] = "send_selected_to_qflist",
},
},
},
})
telescope.load_extension("fzf")
end,
},
}4. Configure treesitter
-- lua/plugins/treesitter.lua
return {
{
"nvim-treesitter/nvim-treesitter",
build = ":TSUpdate",
event = { "BufReadPost", "BufNewFile" },
dependencies = { "nvim-treesitter/nvim-treesitter-textobjects" },
config = function()
require("nvim-treesitter.configs").setup({
ensure_installed = {
"lua", "vim", "vimdoc", "typescript", "javascript",
"python", "rust", "go", "json", "yaml", "markdown",
},
highlight = { enable = true },
indent = { enable = true },
textobjects = {
select = {
enable = true,
lookahead = true,
keymaps = {
["af"] = "@function.outer",
["if"] = "@function.inner",
["ac"] = "@class.outer",
["ic"] = "@class.inner",
["aa"] = "@parameter.outer",
["ia"] = "@parameter.inner",
},
},
move = {
enable = true,
goto_next_start = { ["]f"] = "@function.outer" },
goto_previous_start = { ["[f"] = "@function.outer" },
},
},
})
end,
},
}5. Create custom keymaps
-- lua/config/keymaps.lua
local map = vim.keymap.set
-- Window navigation (replaces <C-w>h/j/k/l)
map("n", "<C-h>", "<C-w>h", { desc = "Move to left window" })
map("n", "<C-j>", "<C-w>j", { desc = "Move to lower window" })
map("n", "<C-k>", "<C-w>k", { desc = "Move to upper window" })
map("n", "<C-l>", "<C-w>l", { desc = "Move to right window" })
-- Stay in visual mode after indenting
map("v", "<", "<gv", { desc = "Indent left" })
map("v", ">", ">gv", { desc = "Indent right" })
-- Paste without overwriting register
map("v", "p", '"_dP', { desc = "Paste without yank" })Always set desc - it powers which-key.nvim and :help lookups.
6. Write a simple plugin
-- lua/myplugin/init.lua
local M = {}
M.config = {
greeting = "Hello from Neovim!",
}
---Setup the plugin.
---@param opts? table Optional config overrides
function M.setup(opts)
M.config = vim.tbl_deep_extend("force", M.config, opts or {})
vim.api.nvim_create_user_command("Greet", function()
vim.notify(M.config.greeting, vim.log.levels.INFO)
end, { desc = "Show greeting" })
end
return MLoad in init.lua:
require("myplugin").setup({ greeting = "Hello, world!" })Use vim.tbl_deep_extend("force", defaults, overrides) for option merging.
Expose only setup() and intentional public functions; keep internals local.
7. Set up autocommands
-- lua/config/autocmds.lua
local augroup = function(name)
return vim.api.nvim_create_augroup(name, { clear = true })
end
-- Highlight yanked text briefly
vim.api.nvim_create_autocmd("TextYankPost", {
group = augroup("highlight_yank"),
callback = function()
vim.highlight.on_yank({ higroup = "IncSearch", timeout = 150 })
end,
})
-- Restore cursor position on file open
vim.api.nvim_create_autocmd("BufReadPost", {
group = augroup("restore_cursor"),
callback = function()
local mark = vim.api.nvim_buf_get_mark(0, '"')
if mark[1] > 0 and mark[1] <= vim.api.nvim_buf_line_count(0) then
vim.api.nvim_win_set_cursor(0, mark)
end
end,
})Always pass a named augroup with clear = true to prevent duplicate autocmds
on re-sourcing.
Anti-patterns
| Anti-pattern | Problem | Correct approach |
|---|---|---|
vim.cmd("set number") for every option |
Mixes Vimscript style into Lua config | Use vim.opt.number = true |
No augroup or reusing unnamed groups |
Autocmds duplicate on :source or re-require |
Always create a named group with clear = true |
| Eager-loading all plugins | Slow startup (>200 ms) | Specify event, cmd, ft, or keys in lazy spec |
| Global functions in plugin code | Pollutes _G, causes name collisions |
Use modules: local M = {} ... return M |
| Hard-coding absolute paths | Breaks portability across machines | Use vim.fn.stdpath("config") and vim.fn.stdpath("data") |
Calling require inside hot loops |
Repeated require is a table lookup but adding logic there is a smell |
Cache the result: local lsp = require("lspconfig") at module top |
Gotchas
mapleadermust be set beforelazy.setup()- If you setvim.g.mapleaderafter callingrequire("lazy").setup(...), plugins that define keymaps using<leader>in their spec will use the default\leader instead. Always set leader keys before the lazy setup call ininit.lua.Autocommands duplicate on re-sourcing if not cleared - Every time you
:source $MYVIMRCor a module is re-required,nvim_create_autocmdappends another listener. Without a named augroup withclear = true, you accumulate duplicate handlers that fire multiple times. This is especially visible with format-on-save callbacks.LSP
on_attachruns once per buffer, not per server - If multiple LSP servers attach to the same buffer,on_attachruns for each. Keymaps defined inon_attachwithoutbuffer = bufnrscope become global and conflict. Always pass{ buffer = bufnr }to all keymaps defined inon_attach.Lazy-loading by
cmdbreaks if the plugin registers the command insetup()- If a plugin's command only exists aftersetup()is called, and you lazy-load it withcmd = "PluginCommand", Neovim will try to open the plugin to run the command but the command won't exist yet. Either eager-load plugins that register commands dynamically or useevent = "VeryLazy".Treesitter and LSP syntax highlighting conflict when both are enabled for the same language - With both
highlight.enable = truein treesitter and an active LSP, you may see double-highlighted tokens or incorrect colors. Disable LSP semantic token highlighting explicitly:client.server_capabilities.semanticTokensProvider = nilinon_attachif treesitter handles highlighting.
References
For detailed content on specific Neovim sub-domains, read the relevant file
from the references/ folder:
references/plugin-ecosystem.md- Essential plugins by category with lazy.nvim specs
Only load a references file if the current task requires it.
References
plugin-ecosystem.md
Neovim Plugin Ecosystem
Canonical plugin selections by category. All specs are written for lazy.nvim.
Prefer these over alternatives unless the user has an existing preference.
Plugin manager
lazy.nvim
The standard plugin manager. Handles lazy-loading, lockfiles, profiling,
and UI. Bootstrap snippet belongs in init.lua before any require calls.
{ "folke/lazy.nvim", tag = "stable" }LSP & completion
mason.nvim + mason-lspconfig + nvim-lspconfig
The standard trio for LSP. Mason installs servers; mason-lspconfig bridges mason to lspconfig; lspconfig configures each server.
{ "williamboman/mason.nvim", build = ":MasonUpdate", opts = {} },
{ "williamboman/mason-lspconfig.nvim",
dependencies = { "williamboman/mason.nvim", "neovim/nvim-lspconfig" },
opts = { ensure_installed = { "lua_ls", "ts_ls", "pyright" } } },
{ "neovim/nvim-lspconfig", event = { "BufReadPre", "BufNewFile" } },nvim-cmp (completion engine)
{
"hrsh7th/nvim-cmp",
event = "InsertEnter",
dependencies = {
"hrsh7th/cmp-nvim-lsp", -- LSP source
"hrsh7th/cmp-buffer", -- buffer words
"hrsh7th/cmp-path", -- filesystem paths
"L3MON4D3/LuaSnip", -- snippet engine
"saadparwaiz1/cmp_luasnip",
"rafamadriz/friendly-snippets",
},
}none-ls.nvim (formatters / linters via LSP)
{
"nvimtools/none-ls.nvim",
event = { "BufReadPre", "BufNewFile" },
dependencies = { "nvim-lua/plenary.nvim" },
config = function()
local null_ls = require("null-ls")
null_ls.setup({
sources = {
null_ls.builtins.formatting.prettier,
null_ls.builtins.formatting.stylua,
null_ls.builtins.diagnostics.eslint_d,
},
})
end,
}Fuzzy finding
telescope.nvim
{
"nvim-telescope/telescope.nvim",
cmd = "Telescope",
dependencies = {
"nvim-lua/plenary.nvim",
{ "nvim-telescope/telescope-fzf-native.nvim", build = "make" },
"nvim-telescope/telescope-ui-select.nvim",
},
}Useful pickers: find_files, live_grep, buffers, lsp_references,
lsp_document_symbols, git_commits, diagnostics.
Syntax & parsing
nvim-treesitter
{
"nvim-treesitter/nvim-treesitter",
build = ":TSUpdate",
event = { "BufReadPost", "BufNewFile" },
dependencies = {
"nvim-treesitter/nvim-treesitter-textobjects",
"nvim-treesitter/nvim-treesitter-context", -- sticky function headers
},
}UI
catppuccin (colorscheme)
{
"catppuccin/nvim",
name = "catppuccin",
priority = 1000, -- load before other plugins
opts = { flavour = "mocha", integrations = { telescope = true, cmp = true } },
}lualine.nvim (statusline)
{
"nvim-lualine/lualine.nvim",
event = "VeryLazy",
dependencies = { "nvim-tree/nvim-web-devicons" },
opts = { options = { theme = "catppuccin", globalstatus = true } },
}bufferline.nvim (tab/buffer bar)
{
"akinsho/bufferline.nvim",
event = "VeryLazy",
dependencies = "nvim-tree/nvim-web-devicons",
opts = { options = { diagnostics = "nvim_lsp", separator_style = "slant" } },
}noice.nvim (UI overhaul - cmdline, messages, popups)
{
"folke/noice.nvim",
event = "VeryLazy",
dependencies = { "MunifTanjim/nui.nvim", "rcarriga/nvim-notify" },
opts = { lsp = { override = { ["vim.lsp.util.convert_input_to_markdown_lines"] = true } } },
}File navigation
neo-tree.nvim
{
"nvim-neo-tree/neo-tree.nvim",
branch = "v3.x",
cmd = "Neotree",
keys = { { "<leader>e", "<cmd>Neotree toggle<cr>", desc = "Explorer" } },
dependencies = { "nvim-lua/plenary.nvim", "nvim-tree/nvim-web-devicons", "MunifTanjim/nui.nvim" },
}harpoon (mark and jump between files)
{
"ThePrimeagen/harpoon",
branch = "harpoon2",
dependencies = { "nvim-lua/plenary.nvim" },
keys = {
{ "<leader>ha", function() require("harpoon"):list():add() end, desc = "Harpoon add" },
{ "<leader>hh", function() require("harpoon").ui:toggle_quick_menu(require("harpoon"):list()) end, desc = "Harpoon menu" },
},
}Git
gitsigns.nvim (inline git blame, hunks, staging)
{
"lewis6991/gitsigns.nvim",
event = { "BufReadPre", "BufNewFile" },
opts = {
signs = { add = { text = "+" }, change = { text = "~" }, delete = { text = "_" } },
on_attach = function(bufnr)
local gs = package.loaded.gitsigns
vim.keymap.set("n", "]h", gs.next_hunk, { buffer = bufnr, desc = "Next hunk" })
vim.keymap.set("n", "[h", gs.prev_hunk, { buffer = bufnr, desc = "Prev hunk" })
vim.keymap.set("n", "<leader>hs", gs.stage_hunk, { buffer = bufnr, desc = "Stage hunk" })
vim.keymap.set("n", "<leader>hr", gs.reset_hunk, { buffer = bufnr, desc = "Reset hunk" })
vim.keymap.set("n", "<leader>gb", gs.blame_line, { buffer = bufnr, desc = "Git blame" })
end,
},
}diffview.nvim (full diff and merge UI)
{
"sindrets/diffview.nvim",
cmd = { "DiffviewOpen", "DiffviewFileHistory" },
dependencies = "nvim-lua/plenary.nvim",
}Editing enhancements
nvim-autopairs
{ "windwp/nvim-autopairs", event = "InsertEnter", opts = { check_ts = true } }Comment.nvim
{ "numToStr/Comment.nvim", event = { "BufReadPost", "BufNewFile" }, opts = {} }nvim-surround
{ "kylechui/nvim-surround", event = { "BufReadPost", "BufNewFile" }, opts = {} }Keymaps: ys{motion}{char} add, cs{old}{new} change, ds{char} delete.
which-key.nvim (keymap hints)
{
"folke/which-key.nvim",
event = "VeryLazy",
opts = { delay = 500 },
}Debugging
nvim-dap + nvim-dap-ui
{
"mfussenegger/nvim-dap",
keys = {
{ "<leader>db", function() require("dap").toggle_breakpoint() end, desc = "Toggle breakpoint" },
{ "<leader>dc", function() require("dap").continue() end, desc = "Continue" },
},
},
{
"rcarriga/nvim-dap-ui",
dependencies = { "mfussenegger/nvim-dap", "nvim-neotest/nvim-nio" },
config = function()
local dap, dapui = require("dap"), require("dapui")
dapui.setup()
dap.listeners.after.event_initialized["dapui_config"] = function() dapui.open() end
dap.listeners.before.event_terminated["dapui_config"] = function() dapui.close() end
end,
},AI / Copilot
copilot.lua
{
"zbirenbaum/copilot.lua",
cmd = "Copilot",
event = "InsertEnter",
opts = {
suggestion = { enabled = false }, -- use cmp source instead
panel = { enabled = false },
},
},
{ "zbirenbaum/copilot-cmp", opts = {} }, -- feeds into nvim-cmpavante.nvim (cursor-style AI sidepanel)
{
"yetone/avante.nvim",
event = "VeryLazy",
build = "make",
opts = { provider = "claude", claude = { model = "claude-sonnet-4-5" } },
dependencies = { "nvim-lua/plenary.nvim", "MunifTanjim/nui.nvim" },
}Terminal
toggleterm.nvim
{
"akinsho/toggleterm.nvim",
keys = { { "<C-\\>", desc = "Toggle terminal" } },
opts = { open_mapping = "<C-\\>", direction = "horizontal", size = 15 },
}Performance tips
- Use
priority = 1000only on colorschemes. - Set
lazy = trueexplicitly for plugins that have no good event/cmd/ft/keys trigger. - Run
:Lazy profileto inspect startup time contributions. - Disable unused built-in plugins in
lazy.nvimperformance.rtp.disabled_plugins. - Use
:checkhealthafter major config changes to validate LSP and plugin state.
Frequently Asked Questions
What is vim-neovim?
Use this skill when configuring Neovim, writing Lua plugins, setting up keybindings, or optimizing the Vim editing workflow. Triggers on Neovim configuration, init.lua, lazy.nvim, LSP setup, telescope, treesitter, vim motions, keymaps, and any task requiring Vim or Neovim customization.
How do I install vim-neovim?
Run npx skills add AbsolutelySkilled/AbsolutelySkilled --skill vim-neovim in your terminal. The skill will be immediately available in your AI coding agent.
What AI agents support vim-neovim?
vim-neovim works with claude-code, gemini-cli, openai-codex. Install it once and use it across any supported AI coding agent.