Wiki LogoWiki - The Power of Many

W08: 脚本化与 Lua 桥接

攻克 Vimscript 核心逻辑与 Autocommands 机制, 完成向 NeoVim Lua 现代化架构的转型.

1. 为什么要从 Vim 迁移到 Neovim

1.1 Neovim 的核心优势

特性VimNeovim
异步执行有限支持原生支持
脚本语言VimscriptLua (高性能)
LSP需插件内置
终端集成基本完整
远程开发原生支持
插件生态混合现代化

1.2 兼容性

Neovim 兼容绝大多数 Vim 配置:

  • ~/.vimrc 可直接使用
  • 可逐步迁移到 ~/.config/nvim/init.lua

2. Vimscript 基础 (理解遗留配置)

虽然 Neovim 主推 Lua, 但理解 Vimscript 对于阅读旧配置和底层机制至关重要.

2.1 变量作用域

let g:my_var = 1     " 全局变量
let b:buffer_var = 2 " 缓冲区局部
let w:window_var = 3 " 窗口局部
let l:local_var = 4  " 函数局部
let s:script_var = 5 " 脚本局部
let $ENV_VAR = 'val' " 环境变量
let &option = 'val'  " 选项
let @a = 'text'      " 寄存器

2.2 函数定义

function! MyFunction(arg1, arg2)
    let l:result = a:arg1 + a:arg2
    return l:result
endfunction

" 调用
echo MyFunction(1, 2)

2.3 自动命令 (Autocommands)

这是让编辑器"变聪明"的核心机制:

" 语法: autocmd {event} {pattern} {cmd}

" 保存 Go 文件时自动格式化
autocmd BufWritePre *.go !gofmt -w %

" 针对不同语言设置缩进
autocmd FileType python setlocal shiftwidth=4 tabstop=4
autocmd FileType javascript setlocal shiftwidth=2 tabstop=2

" 进入插入模式时高亮当前行
autocmd InsertEnter * set cursorline
autocmd InsertLeave * set nocursorline

" 自动重载配置
autocmd BufWritePost ~/.vimrc source ~/.vimrc

2.4 常用事件

事件触发时机
BufRead / BufReadPost读取文件后
BufWrite / BufWritePre写入文件前
BufWritePost写入文件后
BufEnter / BufLeave进入/离开缓冲区
FileType检测到文件类型
InsertEnter / InsertLeave进入/离开插入模式
VimEnterVim 启动完成
VimLeaveVim 退出前

3. Neovim Lua 转型

3.1 安装 Neovim

# macOS
brew install neovim

# Ubuntu/Debian
sudo apt install neovim

# 或使用 AppImage (最新版)
curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim.appimage
chmod u+x nvim.appimage
./nvim.appimage

3.2 配置目录结构

~/.config/nvim/
├── init.lua              # 入口文件
├── lua/
│   ├── core/
│   │   ├── options.lua   # 选项设置
│   │   ├── keymaps.lua   # 键位映射
│   │   └── autocmds.lua  # 自动命令
│   ├── plugins/
│   │   ├── init.lua      # 插件管理器配置
│   │   ├── lsp.lua       # LSP 配置
│   │   ├── cmp.lua       # 补全配置
│   │   └── telescope.lua # Telescope 配置
│   └── utils/
│       └── helpers.lua   # 工具函数
└── after/
    └── ftplugin/         # 文件类型特定配置
        ├── python.lua
        └── go.lua

3.3 init.lua 入口

-- ~/.config/nvim/init.lua

-- 加载核心配置
require("core.options")
require("core.keymaps")
require("core.autocmds")

-- 加载插件
require("plugins")

3.4 选项设置 (core/options.lua)

-- ~/.config/nvim/lua/core/options.lua

local opt = vim.opt

-- 行号
opt.number = true
opt.relativenumber = true

-- 缩进
opt.tabstop = 4
opt.shiftwidth = 4
opt.expandtab = true
opt.autoindent = true
opt.smartindent = true

-- 搜索
opt.hlsearch = true
opt.incsearch = true
opt.ignorecase = true
opt.smartcase = true

-- 界面
opt.termguicolors = true
opt.signcolumn = "yes"
opt.cursorline = true
opt.scrolloff = 8
opt.splitright = true
opt.splitbelow = true

-- 性能
opt.updatetime = 250
opt.timeoutlen = 300

-- 文件
opt.swapfile = false
opt.backup = false
opt.undofile = true
opt.undodir = vim.fn.stdpath("data") .. "/undo"

-- 系统剪贴板
opt.clipboard = "unnamedplus"

3.5 键位映射 (core/keymaps.lua)

-- ~/.config/nvim/lua/core/keymaps.lua

local keymap = vim.keymap.set
local opts = { noremap = true, silent = true }

-- Leader 键
vim.g.mapleader = " "
vim.g.maplocalleader = " "

-- 快速保存/退出
keymap("n", "<leader>w", ":w<CR>", opts)
keymap("n", "<leader>q", ":q<CR>", opts)
keymap("n", "<leader>x", ":x<CR>", opts)

-- 取消搜索高亮
keymap("n", "<Esc>", ":noh<CR>", opts)

-- 窗口导航
keymap("n", "<C-h>", "<C-w>h", opts)
keymap("n", "<C-j>", "<C-w>j", opts)
keymap("n", "<C-k>", "<C-w>k", opts)
keymap("n", "<C-l>", "<C-w>l", opts)

-- 窗口大小调整
keymap("n", "<C-Up>", ":resize +2<CR>", opts)
keymap("n", "<C-Down>", ":resize -2<CR>", opts)
keymap("n", "<C-Left>", ":vertical resize -2<CR>", opts)
keymap("n", "<C-Right>", ":vertical resize +2<CR>", opts)

-- Buffer 导航
keymap("n", "<S-l>", ":bnext<CR>", opts)
keymap("n", "<S-h>", ":bprevious<CR>", opts)
keymap("n", "<leader>bd", ":bdelete<CR>", opts)

-- 可视模式下移动行
keymap("v", "J", ":m '>+1<CR>gv=gv", opts)
keymap("v", "K", ":m '<-2<CR>gv=gv", opts)

-- 保持可视选择
keymap("v", "<", "<gv", opts)
keymap("v", ">", ">gv", opts)

-- 更好的粘贴 (不覆盖寄存器)
keymap("v", "p", '"_dP', opts)

-- 快速退出插入模式
keymap("i", "jk", "<Esc>", opts)
keymap("i", "jj", "<Esc>", opts)

3.6 自动命令 (core/autocmds.lua)

-- ~/.config/nvim/lua/core/autocmds.lua

local augroup = vim.api.nvim_create_augroup
local autocmd = vim.api.nvim_create_autocmd

-- 进入时高亮 yank
augroup("YankHighlight", { clear = true })
autocmd("TextYankPost", {
    group = "YankHighlight",
    callback = function()
        vim.highlight.on_yank({ higroup = "IncSearch", timeout = 200 })
    end,
})

-- 自动调整窗口大小
augroup("AutoResize", { clear = true })
autocmd("VimResized", {
    group = "AutoResize",
    command = "wincmd =",
})

-- 文件类型特定设置
augroup("FileTypeSettings", { clear = true })
autocmd("FileType", {
    group = "FileTypeSettings",
    pattern = { "python", "rust" },
    callback = function()
        vim.opt_local.tabstop = 4
        vim.opt_local.shiftwidth = 4
    end,
})

autocmd("FileType", {
    group = "FileTypeSettings",
    pattern = { "javascript", "typescript", "json", "yaml", "lua" },
    callback = function()
        vim.opt_local.tabstop = 2
        vim.opt_local.shiftwidth = 2
    end,
})

-- 保存时自动删除行尾空格
augroup("TrimWhitespace", { clear = true })
autocmd("BufWritePre", {
    group = "TrimWhitespace",
    pattern = "*",
    command = [[%s/\s\+$//e]],
})

-- 自动创建目录
augroup("AutoCreateDir", { clear = true })
autocmd("BufWritePre", {
    group = "AutoCreateDir",
    callback = function(event)
        local file = vim.loop.fs_realpath(event.match) or event.match
        vim.fn.mkdir(vim.fn.fnamemodify(file, ":p:h"), "p")
    end,
})

4. Lua API 核心

4.1 常用 API

-- 选项设置
vim.opt.number = true           -- 等同于 :set number
vim.opt.tabstop = 4
vim.opt.completeopt = { "menu", "menuone", "noselect" }

-- 全局变量
vim.g.mapleader = " "           -- 等同于 let g:mapleader = " "

-- 缓冲区选项
vim.bo.filetype = "python"      -- 等同于 setlocal filetype=python

-- 窗口选项
vim.wo.number = true            -- 等同于 setlocal number

-- 执行 Vim 命令
vim.cmd("colorscheme default")
vim.cmd([[
    autocmd BufEnter * echo "Hello"
]])

-- 获取/设置变量
local val = vim.fn.expand("%:p")  -- 调用 Vim 函数

4.2 键位映射对比

" Vimscript
nnoremap <leader>w :w<CR>
-- Lua
vim.keymap.set("n", "<leader>w", ":w<CR>", { noremap = true, silent = true })

-- 或使用 callback
vim.keymap.set("n", "<leader>w", function()
    vim.cmd("write")
    print("File saved!")
end, { desc = "Save file" })

5. 本周实战任务

5.1 安装与配置

  1. 安装 Neovim (建议 0.9+)
  2. 创建 ~/.config/nvim/init.lua
  3. 按本周示例创建模块化配置结构

5.2 迁移练习

  1. 将你的 .vimrc 中的选项迁移到 core/options.lua
  2. 将键位映射迁移到 core/keymaps.lua
  3. 将 autocmd 迁移到 core/autocmds.lua

5.3 Lua 练习

  1. 编写一个 Lua 函数, 切换相对行号
  2. 创建一个自动命令, 打开 Markdown 时启用拼写检查
-- 切换相对行号
vim.keymap.set("n", "<leader>tn", function()
    vim.opt.relativenumber = not vim.opt.relativenumber:get()
end, { desc = "Toggle relative numbers" })

6. 思考题

  1. Vimscript 的作用域前缀 (g:, b:, etc.) 有什么作用?
  2. autocmdBufWritePreBufWritePost 有什么区别?
  3. 为什么 Neovim 选择 Lua 而不是其他语言?
  4. vim.optvim.o 有什么区别?
  5. 如何在 Lua 中调用 Vim 内置函数?

Lua 的引入让 Neovim 配置从"魔法咒语"变成了真正的编程. 模块化的配置是可维护性的基础.

On this page