界面已本地化;技能介绍与说明文档按所选语言由 AI 自动翻译,代码、命令、路径和专业术语保留原文,以原作者文档为准。
探索技能
开发与工程 · SKILL.md

neovim

neovim

【开发与工程】Neovim is a terminal text editor configured in Lua, with a built-in LSP client, Treesitter parsing and, since 0.12, a built-in plugin manager (vim.pack). Use this skill to install Neovim, write or repair an init.lua, add plugins, wire up language servers with vim.lsp.config and vim.lsp.enable, set up format-on-save, or run Neovim headless for scripte…

独立 GitHub 快照 · 自动分类 · 以原作者文档为准

Tterminalskills / skillsApache-2.0

技能说明

GitHub

name: neovim description: >- Neovim is a terminal text editor configured in Lua, with a built-in LSP client, Treesitter parsing and, since 0.12, a built-in plugin manager (vim.pack). Use this skill to install Neovim, write or repair an init.lua, add plugins, wire up language servers with vim.lsp.config and vim.lsp.enable, set up format-on-save, or run Neovim headless for scripted edits. Trigger phrases: "set up neovim", "fix my init.lua", "neovim LSP not working", "lspconfig deprecated warning", "add a plugin to nvim", "nvim --headless", "format on save in neovim". license: Apache-2.0 compatibility: "Neovim 0.12+ for vim.pack (0.11+ for vim.lsp.config/enable); git for plugin installs; language servers installed separately" metadata: author: terminal-skills version: "1.0.0" category: development tags: ["neovim", "lua", "lsp", "treesitter", "text-editor"] repository: https://github.com/neovim/neovim

Neovim — Lua-configured terminal editor with built-in LSP

Overview

Neovim (nvim) is a Vim-based editor that runs in a terminal and is configured with Lua. It ships an LSP client (diagnostics, go-to-definition, rename, formatting), Treesitter (syntax trees for highlighting, folding and queries) and, from 0.12, vim.pack, a Git-based plugin manager with a lockfile. The current stable line is 0.12.x (0.12.5 at the time of writing).

An agent cannot usefully drive the interactive UI, but it can do most of the setup work: install the binary, write init.lua, pin plugins, configure language servers, check the config for errors and run Neovim headless for batch edits. This skill covers those jobs.

Instructions

Installation

Package managers first; check the version afterwards, because distribution packages often lag behind and vim.pack needs 0.12.

brew install neovim            # macOS or Linux with Homebrew
winget install Neovim.Neovim   # Windows
sudo pacman -S neovim          # Arch
nvim --version | head -1       # expect NVIM v0.12.x

If the distro package is too old, use the official release archive (download, then extract as a separate step):

VER=v0.12.5
curl -LO https://github.com/neovim/neovim/releases/download/$VER/nvim-linux-x86_64.tar.gz
# GitHub records a SHA-256 digest for every release asset; compare before extracting
WANT=$(curl -s https://api.github.com/repos/neovim/neovim/releases/tags/$VER \
  | jq -r '.assets[] | select(.name == "nvim-linux-x86_64.tar.gz") | .digest' | cut -d: -f2)
echo "$WANT  nvim-linux-x86_64.tar.gz" | sha256sum -c -   # must print OK
sudo tar -C /opt -xzf nvim-linux-x86_64.tar.gz
echo 'export PATH="/opt/nvim-linux-x86_64/bin:$PATH"' >> ~/.bashrc

Prepending puts the new binary ahead of an older /usr/bin/nvim. Archives exist for linux-arm64, macos-arm64 and macos-x86_64 too.

Config layout

  • ~/.config/nvim/init.lua — entry point (:echo stdpath('config') shows the real path).
  • ~/.config/nvim/lua/<name>.lua — modules loaded with require('<name>').
  • ~/.config/nvim/lsp/<server>.lua — a language-server config returned as a table.
  • ~/.config/nvim/after/lsp/<server>.lua — overrides that win over plugin-provided configs.
  • ~/.config/nvim/nvim-pack-lock.json — the vim.pack lockfile; commit it with the config.

To try a config without touching the user's, set NVIM_APPNAME: Neovim then reads ~/.config/<name> and keeps separate data and state directories.

NVIM_APPNAME=nvim-trial nvim

Plugins with vim.pack (0.12+)

vim.pack.add() clones missing plugins into the data directory (site/pack/core/opt) and loads them. It needs git and is marked experimental in the docs.

vim.pack.add({
  'https://github.com/neovim/nvim-lspconfig',
  { src = 'https://github.com/nvim-treesitter/nvim-treesitter', version = 'main' },
})
  • vim.pack.update() fetches updates and opens a review buffer: :write applies, :quit discards. vim.pack.update(nil, { force = true }) skips the review.
  • vim.pack.update(nil, { target = 'lockfile' }) moves plugins back to the lockfile revisions.
  • To remove a plugin, first delete it from the add() list and :restart, then run vim.pack.del({ 'plugin-name' }). del() refuses a plugin that is still loaded unless you pass { force = true }.
  • Build steps go in a PackChanged autocommand defined before vim.pack.add().

Third-party managers such as lazy.nvim still work; do not mix two managers for the same plugin.

Language servers (vim.lsp.config / vim.lsp.enable)

Neovim provides the client; servers are separate programs on PATH. nvim-lspconfig ships ready-made configs in its lsp/ directory, so after installing it one line per server is enough:

vim.lsp.enable({ 'pyright', 'ruff', 'ts_ls', 'lua_ls' })

Customise with vim.lsp.config(); it merges over the plugin's defaults:

vim.lsp.config('lua_ls', {
  settings = { Lua = { runtime = { version = 'LuaJIT' } } },
})
vim.lsp.config('pyright', { root_markers = { 'pyproject.toml', '.git' } })
vim.lsp.config('*', {   -- fallback for fields a server's own config leaves unset
  capabilities = { textDocument = { semanticTokens = { multilineTokenSupport = true } } },
})

'*' has the lowest priority: nvim-lspconfig's lsp/<server>.lua files override it, so setting root_markers there changes nothing. Set root markers per server, as above, or in after/lsp/<server>.lua.

A server that nvim-lspconfig does not know needs cmd, filetypes and root_markers. The old require('lspconfig').<server>.setup{} framework is deprecated; replace each call with vim.lsp.config (settings) plus vim.lsp.enable (activation).

Built-in keymaps once a server attaches: K hover, grn rename, grr references, gri implementation, gra code action, gO document symbols, CTRL-S signature help in insert mode, and CTRL-] jumps to the definition through 'tagfunc'. :lsp restart and :lsp stop manage clients.

Format on save and completion

Some servers (ruff, for example) register formatting only after they attach, so check capability when saving rather than inside LspAttach:

vim.api.nvim_create_autocmd('BufWritePre', {
  group = vim.api.nvim_create_augroup('my.format', {}),
  callback = function(ev)
    if #vim.lsp.get_clients({ bufnr = ev.buf, method = 'textDocument/formatting' }) > 0 then
      vim.lsp.buf.format({ bufnr = ev.buf, timeout_ms = 2000 })
    end
  end,
})

vim.api.nvim_create_autocmd('LspAttach', {
  group = vim.api.nvim_create_augroup('my.lsp', {}),
  callback = function(ev)
    local client = assert(vim.lsp.get_client_by_id(ev.data.client_id))
    if client:supports_method('textDocument/completion') then
      vim.lsp.completion.enable(true, client.id, ev.buf, { autotrigger = true })
    end
  end,
})

With several formatters attached, pass name = 'ruff' or a filter function to vim.lsp.buf.format().

Treesitter

Neovim bundles parsers for C, Lua, Markdown, Vimscript, Vimdoc and Treesitter queries. Other languages need parsers, usually from nvim-treesitter (its main branch requires Neovim 0.12, tree-sitter-cli and a C compiler; the old master branch is frozen).

require('nvim-treesitter').install({ 'python', 'typescript', 'tsx' })

vim.api.nvim_create_autocmd('FileType', {
  pattern = { 'python', 'typescript', 'typescriptreact', 'lua' },
  callback = function(ev)
    -- pcall: the parser may not be installed yet (install() is asynchronous)
    if pcall(vim.treesitter.start, ev.buf) then
      vim.wo[0][0].foldexpr = 'v:lua.vim.treesitter.foldexpr()'
      vim.wo[0][0].foldmethod = 'expr'
    end
  end,
})

Without the pcall, every buffer of a language whose parser is missing raises "Parser could not be created". To install parsers before first use, for example on a new machine, wait for the install in a headless run:

nvim --headless -c "lua require('nvim-treesitter').install({ 'python', 'typescript', 'tsx' }):wait(300000)" -c qa

After updating nvim-treesitter, run :TSUpdate so parsers match the plugin.

Headless and scripted use

nvim --headless +qa                                  # load the config, install vim.pack plugins, exit
nvim --headless "+checkhealth vim.lsp" "+write! lsp-health.txt" +qa
nvim --headless -c 'lua vim.pack.update(nil, { force = true })' -c qa
nvim -l scripts/report.lua src/app.lua               # run a Lua script with Neovim's API, then exit
  • nvim -l script.lua args… skips the user config, exposes arguments as arg[1]…, and sends print() to stderr; use io.write() for stdout.
  • nvim --clean starts with no user config or plugins — the fastest way to tell a config bug from a Neovim bug.
  • nvim --headless +qa exits 0 even when init.lua fails. To get a failing exit code, test v:errmsg: nvim --headless -c 'if v:errmsg != "" | cquit 1 | endif' -c qa.

Examples

Example 1: Python IDE features over SSH

User request: "Set up Neovim on the build box for our invoicing Python service: pyright for types, ruff for lint and format on save."

uv tool install pyright   # PyPI wrapper; provides pyright-langserver
uv tool install ruff

~/.config/nvim/init.lua:

vim.g.mapleader = ' '
vim.o.number = true
vim.o.expandtab = true
vim.o.shiftwidth = 4
vim.o.undofile = true

vim.pack.add({ 'https://github.com/neovim/nvim-lspconfig' })
vim.lsp.enable({ 'pyright', 'ruff' })

vim.diagnostic.config({ virtual_text = true })
vim.keymap.set('n', '<leader>e', vim.diagnostic.open_float, { desc = 'Show diagnostic' })

vim.api.nvim_create_autocmd('BufWritePre', {
  group = vim.api.nvim_create_augroup('my.format', {}),
  callback = function(ev)
    if #vim.lsp.get_clients({ bufnr = ev.buf, method = 'textDocument/formatting' }) > 0 then
      vim.lsp.buf.format({ bufnr = ev.buf, timeout_ms = 2000 })
    end
  end,
})
nvim --headless +qa        # clones nvim-lspconfig and writes nvim-pack-lock.json

Result: opening billing.py inside the project (which has a pyproject.toml) attaches both servers with the project as root. Ruff flags "os imported but unused" and "Undefined name totl" inline, and :w rewrites def total(items:list[float])->float: as def total(items: list[float]) -> float: with two blank lines around top-level definitions.

Example 2: Remove the lspconfig deprecation warning

User request: "After updating plugins I get 'The require('lspconfig') framework is deprecated, use vim.lsp.config'. Fix my config for TypeScript and Lua."

Old code:

require('lspconfig').ts_ls.setup({ on_attach = on_attach })
require('lspconfig').lua_ls.setup({ settings = { Lua = { diagnostics = { globals = { 'vim' } } } } })

Replacement (keep nvim-lspconfig installed — its lsp/ configs are still used):

vim.lsp.config('lua_ls', {
  settings = { Lua = { diagnostics = { globals = { 'vim' } } } },
})
vim.lsp.enable({ 'ts_ls', 'lua_ls' })
-- on_attach logic moves into an LspAttach autocommand (see Instructions)
nvim --headless -c 'if v:errmsg != "" | cquit 1 | endif' -c qa && echo config-ok

Result: config-ok prints, the warning is gone, and :checkhealth vim.lsp lists ts_ls and lua_ls under "Enabled Configurations".

Example 3: Rename a class across a folder without opening the UI

User request: "Rename BillingClient to LedgerClient in every file under handlers/."

nvim --clean --headless -c 'argdo %s/\<BillingClient\>/LedgerClient/ge | update' -c qa handlers/*.py
grep -rn "LedgerClient" handlers/

Result: handlers/refunds.py and handlers/invoices.py are rewritten; handlers/health.py, which had no match, is left untouched because update writes only modified buffers. \<…\> keeps BillingClientError from being changed. For symbol-aware renames that follow imports, use grn with a language server instead.

Guidelines

  • Check nvim --version before writing config: vim.pack needs 0.12, vim.lsp.config/vim.lsp.enable need 0.11. Current nvim-lspconfig requires 0.11.3 or newer.
  • Language servers are not bundled. Install them with the ecosystem's package manager and confirm the binary (pyright-langserver, ruff, typescript-language-server) is on PATH for the process that starts Neovim.
  • Root markers (pyproject.toml, package.json, .git…) decide the workspace root. Without one, most servers (pyright, ruff, lua_ls) still attach in single-file mode with no root, so cross-file features are weaker. Servers marked workspace_required (eslint, biome, tailwindcss) do not attach at all outside a project. :checkhealth vim.lsp shows the resolved cmd, filetypes and root markers.
  • Put the user's own overrides in vim.lsp.config() or after/lsp/, not in the plugin directory; plugin updates overwrite it.
  • Commit nvim-pack-lock.json so another machine installs the same plugin revisions; do not edit it by hand.
  • Before editing someone's config, back it up by copying, or work under NVIM_APPNAME. Do not delete the data directory to "fix" plugins; use vim.pack.del() or vim.pack.update(nil, { target = 'lockfile' }).
  • Plugins are code from third parties that runs with the user's permissions. Pin to tags with version = vim.version.range('1.0') where a plugin publishes semver tags, and review vim.pack.update() changes before :write.
  • Headless regex edits are text replacements, not refactors: they ignore scope, strings and comments. Use them for mechanical changes and check the diff afterwards.
  • Do not use this skill for editor-agnostic tasks (formatting in CI, linting) — call the formatter or linter directly; for VS Code or Zed, use their own settings.
我的收藏