Skip to content

nvim dev api

ES edited this page Jul 16, 2026 · 1 revision

Neovim 能力与 API 全景

面向插件作者的 API 地图。每个条目配 :h tag,想细看就 :help


1. 整体架构

Neovim
├── C 核心(事件循环 + nvim_* API)
├── Vimscript(历史兼容 + Ex 命令 + 内置函数)
└── Lua 层(vim.* stdlib,插件开发的主战场)
    ├── 对接 C 核心     → vim.api.*
    ├── 对接 Vimscript  → vim.fn.* / vim.cmd
    └── 自有标准库      → vim.lsp / vim.treesitter / vim.diagnostic / ...

插件作者 95% 时间在 Lua 层。下面全部挂在 vim.*


2. API 分类总览

命名空间 作用 :h tag
vim.api.* C 核心 API 的 Lua 绑定(nvim_* 家族) :h api
vim.fn.* 调 Vimscript 内建函数 :h vim.fn
vim.cmd 执 Ex 命令 :h vim.cmd
vim.g/b/w/t/v/env 作用域变量(global/buf/win/tab/v:/env) :h lua-vim-variables
vim.opt/bo/wo/go/o 选项访问器(支持 list/set 语义) :h vim.opt
vim.keymap 键位映射 :h vim.keymap.set
vim.lsp LSP 客户端 + 内置功能 :h lsp
vim.treesitter Treesitter parser / query / highlighter :h treesitter
vim.diagnostic 诊断显示与导航 :h vim.diagnostic
vim.uv libuv 绑定(异步 IO/定时器) :h vim.uv
vim.system 子进程 API(推荐替代 jobstart) :h vim.system
vim.ui 可被插件覆盖的 input / select / open :h vim.ui
vim.iter 链式迭代器 :h vim.iter
vim.fs 文件系统工具 :h vim.fs
vim.filetype filetype 注册与匹配 :h vim.filetype
vim.snippet 内置 snippet 引擎 :h vim.snippet
vim.hl 高亮范围(旧名 vim.highlight :h vim.hl
vim.pack 原生包管理器(0.12+) :h vim.pack

3. 核心 API 详解

3.1 vim.api.* —— C 核心绑定

最常用、最底层。所有 nvim_* 都可以从 Lua 调

Buffer

API 说明
nvim_create_buf(listed, scratch) 创建 buffer
nvim_buf_get_lines(buf, start, end, strict) 读行(0-indexed,end 是 exclusive)
nvim_buf_set_lines(buf, start, end, strict, lines) 写行(需要 modifiable=true
nvim_buf_attach(buf, send_buffer, {on_lines=...}) 订阅 buffer 变化
nvim_buf_get_name(buf) 获取 buffer 文件路径
nvim_buf_delete(buf, {force}) 删除 buffer

Window

API 说明
nvim_open_win(buf, enter, config) 打开浮窗 / split(relative='editor' 为浮窗)
nvim_win_set_buf(win, buf) 换窗口内的 buffer
nvim_win_get_cursor(win) / set_cursor(win, {r, c}) 读/设光标(1-indexed 行,0-indexed 列)
nvim_win_set_config(win, config) 修改浮窗配置
nvim_win_close(win, force) 关窗口

浮窗配置关键字段relative / row / col / width / height / border / title / footer / zindex / style='minimal'

Extmark —— 装饰万能药

虚拟文本、内联诊断、sign、高亮都走它。:h api-extended-marks

local ns = vim.api.nvim_create_namespace('myplug')
vim.api.nvim_buf_set_extmark(buf, ns, row, col, {
  virt_text = { {'TODO', 'WarningMsg'} },   -- 行尾虚拟文本
  virt_lines = { {{'-- above line', 'Comment'}} },  -- 虚拟行
  sign_text = 'E', sign_hl_group = 'Error', -- sign 列图标
  hl_group = 'Search',                       -- 范围高亮
  end_row = row, end_col = col + 10,
})
vim.api.nvim_buf_clear_namespace(buf, ns, 0, -1)  -- 直接清除

Autocmd / 用户命令 / 高亮

-- autocmd
local aug = vim.api.nvim_create_augroup('MyPlug', { clear = true })
vim.api.nvim_create_autocmd('BufWritePost', {
  group = aug, pattern = '*.ts',
  callback = function(args) print('saved', args.file) end,
})

-- 用户命令
vim.api.nvim_create_user_command('MyCmd', function(o)
  print(o.args, o.bang)
end, { nargs = '?', bang = true, desc = 'my command' })

-- 高亮
vim.api.nvim_set_hl(0, 'MyHl', { fg = '#ff6ac1', bold = true })

RPC / channel

nvim_open_term, nvim_chan_send, rpcrequest, rpcnotify —— 用于终端、外部 UI、远程插件。:h rpc


3.2 vim.fn.* / vim.cmd

vim.fn.expand('%:p')           -- Vimscript 函数
vim.fn.glob('*.lua', nil, true)

vim.cmd('edit foo.lua')        -- Ex 命令字符串
vim.cmd.edit('foo.lua')        -- 新式调用语法
vim.cmd.colorscheme('tokyonight')

3.3 作用域变量与选项

-- 变量
vim.g.mapleader = ' '          -- 全局
vim.b.my_state = 1             -- 当前 buffer
vim.b[bufnr].my_state = 1      -- 指定 buffer
vim.w[win].my_state = 1        -- window
vim.env.FOO = 'bar'            -- 进程环境变量

-- 选项(三套 API,按需选)
vim.o.number = true            -- 普通值(get/set)
vim.bo.filetype = 'lua'        -- buf 局部
vim.wo[win].number = false     -- win 局部
vim.opt.list:append({ tab = '»·' })  -- list/set 语义
vim.opt_local.wrap = false     -- setlocal

3.4 键位映射

vim.keymap.set('n', '<leader>w', '<cmd>write<cr>', { desc = 'save', silent = true })
vim.keymap.set({'n', 'v'}, 'gf', function() vim.lsp.buf.format() end)
vim.keymap.set('n', '<leader>x', '<cmd>MyCmd<cr>', { buffer = buf })  -- buffer-local
vim.keymap.del('n', '<leader>w')

3.5 vim.lsp —— LSP 子系统

0.11 新 API(声明式,推荐)

-- 全局注册 server 配置
vim.lsp.config('lua_ls', {
  cmd = { 'lua-language-server' },
  root_markers = { '.luarc.json', '.git' },
  settings = { Lua = { diagnostics = { globals = { 'vim' } } } },
})

-- 按需启用
vim.lsp.enable({ 'lua_ls', 'tsserver' })

常用入口

API 用途
vim.lsp.buf.hover() K 显示 hover
vim.lsp.buf.definition() 跳定义
vim.lsp.buf.references() 引用列表
vim.lsp.buf.rename() 重命名
vim.lsp.buf.code_action() 代码动作
vim.lsp.buf.format({async=true}) 格式化
vim.lsp.inlay_hint.enable(true) 开 inlay hint
vim.lsp.get_clients({bufnr=0}) 查当前 buffer 的 client
vim.lsp.log.set_level('debug') 调试 LSP

子模块:handlers / util / protocol / semantic_tokens / codelens / inlay_hint / completion / diagnostic / rpc / client


3.6 vim.treesitter

local parser = vim.treesitter.get_parser(bufnr, 'typescript')
local tree = parser:parse()[1]
local root = tree:root()

-- 查询
local query = vim.treesitter.query.parse('typescript', [[
  (function_declaration name: (identifier) @func)
]])
for id, node in query:iter_captures(root, bufnr, 0, -1) do
  local name = query.captures[id]
  local text = vim.treesitter.get_node_text(node, bufnr)
end

-- 光标下节点
local node = vim.treesitter.get_node()

子模块:query / highlighter / language / languagetree / dev(playground、:Inspect:EditQuery


3.7 vim.diagnostic

-- 发布诊断(给自己插件用)
local ns = vim.api.nvim_create_namespace('myplug.diag')
vim.diagnostic.set(ns, bufnr, {
  { lnum = 0, col = 0, severity = vim.diagnostic.severity.WARN, message = 'oops' },
})

-- 读取 / 跳转
vim.diagnostic.get(bufnr, { severity = vim.diagnostic.severity.ERROR })
vim.diagnostic.jump({ count = 1 })    -- next
vim.diagnostic.open_float()

-- 全局配置
vim.diagnostic.config({
  virtual_text = false,
  virtual_lines = true,       -- 0.11 新:多行展示
  severity_sort = true,
  signs = { text = { [vim.diagnostic.severity.ERROR] = 'E' } },
})

3.8 异步 / 子进程

-- vim.uv:libuv 全绑定
local timer = vim.uv.new_timer()
timer:start(100, 0, vim.schedule_wrap(function()
  vim.notify('tick')
  timer:close()
end))

-- vim.system:子进程推荐 API
vim.system({ 'git', 'status', '--porcelain' }, { text = true }, function(obj)
  vim.schedule(function()
    print(obj.code, obj.stdout)
  end)
end)

-- 同步版
local result = vim.system({'ls', '-la'}, { text = true }):wait()

-- 主循环调度
vim.schedule(function() ... end)        -- 下一个安全点执行
vim.schedule_wrap(cb)                   -- 包装一个 cb
vim.defer_fn(fn, 100)                   -- 100ms 后
vim.wait(100, function() return done end, 10)  -- 忙等(尽量别用)

关键:libuv 回调里不能直接调 vim.api.*,必须走 vim.schedule


3.9 UI 可替换层

-- 用户级调用
vim.ui.input({ prompt = 'Name: ', default = 'foo' }, function(val)
  if val then print('got', val) end
end)

vim.ui.select({ 'a', 'b', 'c' }, { prompt = 'Pick:' }, function(choice)
  print(choice)
end)

vim.ui.open('https://google.com')  -- 走系统默认打开器

-- 插件级覆盖(dressing / telescope 都这么干)
vim.ui.select = function(items, opts, on_choice) ... end

vim.ui_attach 可订阅底层 UI event(cmdline_show/popupmenu_show),用于写浮窗 cmdline(见 noice.nvim)


3.10 数据结构 / 工具

-- iter:链式迭代器
vim.iter({1,2,3,4})
  :filter(function(x) return x > 1 end)
  :map(function(x) return x * 2 end)
  :totable()           -- { 4, 6, 8 }

-- fs
vim.fs.find('.git', { upward = true, path = vim.fn.expand('%:p') })
vim.fs.root(0, { 'package.json', '.git' })     -- 0.10+ 根目录查找
vim.fs.dirname('/a/b/c.lua')
vim.fs.normalize('~/x/../y')

-- filetype
vim.filetype.add({
  extension = { tsx = 'typescriptreact' },
  pattern   = { ['.*%.env%..*'] = 'sh' },
})

-- validate
vim.validate('name', name, 'string')
vim.validate('count', count, 'number', true)  -- 可选

-- inspect / print
vim.print(obj)          -- = print(vim.inspect(obj))
vim.inspect({a=1, b={2,3}})

-- tbl_*
vim.tbl_deep_extend('force', defaults, opts)
vim.tbl_keys(t) / vim.tbl_values(t) / vim.tbl_map(fn, t) / vim.tbl_filter(fn, t)
vim.islist(t)

-- version
local v = vim.version.parse('1.2.3')
vim.version.lt(v1, v2)

3.11 vim.snippet(0.10+ 内置)

vim.snippet.expand('console.log($1);$0')
vim.snippet.jump(1)     -- 下一个 tabstop
vim.snippet.stop()
vim.snippet.active({ direction = 1 })

3.12 vim.pack(0.12+ 原生包管理)

vim.pack.add({
  'https://github.com/folke/tokyonight.nvim',
  { src = 'https://github.com/nvim-telescope/telescope.nvim', version = '0.1.8' },
})
vim.pack.update()       -- 升级
vim.pack.del({ 'tokyonight.nvim' })

Lockfile:$XDG_CONFIG_HOME/nvim/nvim-pack-lock.json


3.13 显示相关(API + 选项组合)

特性 写法
浮窗 nvim_open_win(buf, true, { relative='editor', border='rounded', ... })
statusline vim.o.statusline = '%f %m'%!v:lua.MyFn()
winbar vim.wo.winbar = ...
statuscolumn vim.o.statuscolumn = '%s%=%l '
foldtext / foldexpr vim.wo.foldexpr = 'v:lua.MyFn()'
sign 列 推荐走 extmark 的 sign_text / sign_hl_group

4. 插件开发:目录结构

myplugin/
├── plugin/*.lua           Nvim 启动时自动 source(每次一次)
├── ftplugin/<ft>.lua      对应 filetype buffer 打开时加载(每 buffer 一次)
├── after/{plugin,ftplugin}/   runtimepath 末尾加载,用于覆盖默认
├── lua/<mod>/init.lua     require('mod') 触发,不会自动执行
├── queries/<lang>/*.scm   Treesitter 查询
├── colors/<scheme>.lua    :colorscheme 触发
└── doc/*.txt              :h 文档(:helptags 生成 tags)

核心规则

  • lua/惰性的(require 触发)
  • plugin/主动的(启动加载)
  • 副作用集中到 M.setup(opts)

5. M.setup(opts) 模板

-- lua/myplug/init.lua
local M = {}

---@class MyPlugConfig
---@field border string @default 'rounded'
---@field debug boolean @default false
local defaults = { border = 'rounded', debug = false }

---@param opts MyPlugConfig?
function M.setup(opts)
  M.config = vim.tbl_deep_extend('force', defaults, opts or {})
  vim.validate('border', M.config.border, 'string')

  vim.api.nvim_create_user_command('MyPlug', function()
    M.hello()
  end, {})
end

function M.hello()
  vim.notify('hi from myplug', vim.log.levels.INFO)
end

return M

6. 健康检查 / 调试

-- lua/myplug/health.lua
local M = {}
function M.check()
  vim.health.start('myplug')
  if vim.fn.executable('rg') == 1 then
    vim.health.ok('rg found')
  else
    vim.health.error('ripgrep not installed')
  end
end
return M

调用 :checkhealth myplug

常用调试

  • vim.print(x) = print(vim.inspect(x))
  • vim.notify(msg, vim.log.levels.WARN)
  • :messages 看历史
  • :lua =vim.o.runtimepath 快查值

7. 测试框架选型

工具 特点
plenary.nvim (PlenaryBustedDirectory) 生态最广,busted 语法
mini.test 无依赖、支持 child Nvim 进程
busted + nlua 纯 CLI,CI 最轻

8. First 60 Minutes 清单

  1. mkdir -p ~/plugins/myplug/{lua/myplug,plugin,doc} && cd $_ && git init
  2. lua/myplug/init.lua:导出 M.setup(opts) + demo 函数
  3. plugin/myplug.luanvim_create_user_command('MyPlug', ...)
  4. 本地测试:vim.pack.add({ 'file:///abs/path/to/myplug' });在本配置中也可把仓库放入 vendors/,由 pack.dev 自动重定向
  5. 加 autocmd demo:nvim_create_augroup + nvim_create_autocmd
  6. 玩 extmark:nvim_create_namespace + nvim_buf_set_extmark
  7. doc/myplug.txt(带 *myplug-setup* tag),:helptags doc/:h myplug 验证
  8. lua/myplug/health.lua:checkhealth myplug

9. 关键引用

Clone this wiki locally