Neovim 配置与使用:LazyVim 精简版 个人备忘

Neovim 配置与使用:LazyVim 精简版 个人备忘

概览

整套配置 = LazyVim + 7 个官方 extras + 少量自定义,只有 5 个文件、约 160 行。搜索、LSP、补全、格式化、文件树、Git 等都用 LazyVim 自带的实现;本地 WSL 和 win-dev 已切换到这套并验证通过。

bash 复制代码
~/.config/nvim/
├── init.lua                 # 只有一行 require
└── lua/
    ├── config/
    │   ├── lazy.lua         # 引导 lazy.nvim + 选择 LazyVim extras
    │   └── options.lua      # 全局选项、剪贴板
    └── plugins/
        ├── custom.lua       # 只写和 LazyVim 默认不同的地方
        └── project.lua      # 项目切换(project.nvim)

和 LazyVim 默认不同的地方:

文件 改动 作用
custom.lua Go 用 goimports,gopls 关掉 gofumpt 团队用标准 gofmt,避免无关 diff
custom.lua Python 保存时 ruff 整理 import + 格式化 import 自动排序
custom.lua golangci-lint 在文件所属 go.mod 目录运行,检查整个包 从仓库根目录打开(go.mod 在子目录)时不再误报 could not import / undefined
custom.lua fzf-lua rg_glob = true 全文搜索支持 关键词 -- *.go
custom.lua mini.map 右侧代码缩略图,启动时自动打开 显示搜索匹配、git 改动、诊断位置
options.lua 相对行号、GBK 编码识别、忽略大小写 + smartcase 中文旧文件不乱码
options.lua 剪贴板走 OSC 52 复制经终端(含 tmux、ssh)写到本机剪贴板
project.lua project.nvim 自动切换到项目根目录 + fzf 项目列表 打开文件即进入所在项目,<leader>fp 在最近项目间切换

旧的手写配置备份在 ~/.config/nvim.bak-lazyvim。

Mac 快速搭建

大约 10 分钟,不需要 git 仓库。

  1. 装外部工具

    bash 复制代码
    xcode-select --install
    brew install neovim git ripgrep fd fzf lazygit node go python
    brew install --cask font-jetbrains-mono-nerd-font
  2. 复制下文「配置脚本」代码块,在终端执行(已有的 ~/.config/nvim 会先改名备份):

    bash 复制代码
    pbpaste > ~/setup-nvim.sh && bash ~/setup-nvim.sh
  3. 装插件,然后正常打开一次 nvim,等 Mason 自动装完 gopls、pyright、ruff、vtsls、prettier、golangci-lint、tree-sitter 等工具(:Mason 看进度,约 2--3 分钟):

    bash 复制代码
    nvim --headless "+Lazy! sync" +qa
    nvim
  4. 验证:打开一个 Go 项目,gd 能跳定义;<leader>/ 输入 func -- *.go 只出 .go 结果;改乱 Python 的 import 顺序后保存会自动排好;:checkhealth 没有红色错误。

终端设置:iTerm2 / Ghostty 里把字体设为 JetBrainsMono Nerd Font,并把 Option 键设为 Meta(iTerm2:Profiles → Keys → Left Option = Esc+;Ghostty:macos-option-as-alt = true),否则 Alt-j/k 移动行不生效。

配置脚本

下面就是完整配置,每个 cat > ... <<'EOF' 块就是一个文件的全部内容,也可以手动建这 5 个文件。按「Mac 快速搭建」第 2 步执行。

bash 复制代码
#!/usr/bin/env bash
# 写出 LazyVim 精简版 Neovim 配置(5 个文件);已有 ~/.config/nvim 会先备份
set -e
C="$HOME/.config/nvim"
[ -e "$C" ] && mv "$C" "$C.bak-$(date +%Y%m%d%H%M%S)"
mkdir -p "$C/lua/config" "$C/lua/plugins"

cat > "$C/init.lua" <<'EOF'
require("config.lazy")
EOF

cat > "$C/lua/config/lazy.lua" <<'EOF'
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not vim.uv.fs_stat(lazypath) then
  vim.fn.system({ "git", "clone", "--filter=blob:none", "--branch=stable", "https://github.com/folke/lazy.nvim.git", lazypath })
end
vim.opt.rtp:prepend(lazypath)

require("lazy").setup({
  spec = {
    { "LazyVim/LazyVim", import = "lazyvim.plugins" },
    -- 语言与工具扩展(等价于原来手写的 LSP / 格式化 / 搜索配置)
    { import = "lazyvim.plugins.extras.editor.fzf" },
    { import = "lazyvim.plugins.extras.lang.python" },
    { import = "lazyvim.plugins.extras.lang.typescript" },
    { import = "lazyvim.plugins.extras.lang.go" },
    { import = "lazyvim.plugins.extras.lang.json" },
    { import = "lazyvim.plugins.extras.formatting.prettier" },
    { import = "lazyvim.plugins.extras.linting.eslint" },
    { import = "plugins" },
  },
  checker = { enabled = false },
})
EOF

cat > "$C/lua/config/options.lua" <<'EOF'
vim.g.lazyvim_picker = "fzf"            -- 搜索用 fzf-lua
vim.g.lazyvim_prettier_needs_config = true -- 项目里有 .prettierrc 才用 prettier 格式化
vim.g.lazyvim_python_lsp = "pyright"
vim.g.lazyvim_python_ruff = "ruff"
vim.opt.relativenumber = true
vim.opt.fileencodings = { "ucs-bom", "utf-8", "gbk", "gb18030", "latin1" }
-- 搜索行为
vim.o.incsearch = true   -- 输入时实时预览
vim.o.hlsearch = true    -- 搜索后高亮所有匹配
vim.o.ignorecase = true  -- 忽略大小写
vim.o.smartcase = true   -- 含大写字母时区分大小写
-- 剪贴板:复制通过 OSC 52 写到终端(经 tmux 转发到 Windows 剪贴板)
local osc52 = require("vim.ui.clipboard.osc52")
local function paste_unnamed()
  -- 终端不支持读取 OSC 52 时,直接用 nvim 内部的无名寄存器,避免 p 卡住等待
  return { vim.fn.split(vim.fn.getreg('"'), "\n"), vim.fn.getregtype('"') }
end
vim.g.clipboard = {
  name = "OSC 52",
  copy = { ["+"] = osc52.copy("+"), ["*"] = osc52.copy("*") },
  -- tmux 会用自己的缓冲区回应 OSC 52 读取请求,所以在 tmux 里可以真正共享
  paste = vim.env.TMUX
      and { ["+"] = osc52.paste("+"), ["*"] = osc52.paste("*") }
    or { ["+"] = paste_unnamed, ["*"] = paste_unnamed },
}
vim.opt.clipboard = "unnamedplus"
EOF

cat > "$C/lua/plugins/custom.lua" <<'EOF'
return {
  -- 格式化:Go 用 goimports(标准 gofmt 风格);Python 保存时整理 import + 格式化
  {
    "stevearc/conform.nvim",
    opts = {
      formatters_by_ft = {
        go = { "goimports" },
        python = { "ruff_organize_imports", "ruff_format" },
      },
    },
  },
  -- gopls:团队用标准 gofmt,不用 gofumpt
  {
    "neovim/nvim-lspconfig",
    opts = { servers = { gopls = { settings = { gopls = { gofumpt = false } } } } },
  },
  -- golangci-lint 在当前文件所属 go.mod 目录下运行:默认用 nvim 的 cwd,
  -- 从仓库根目录打开时(go.mod 在 backend/src)会误报 could not import / undefined
  {
    "mfussenegger/nvim-lint",
    opts = {
      linters = {
        golangcilint = function()
          local linter = vim.deepcopy(require("lint.linters.golangcilint"))
          local root = vim.fs.root(0, "go.mod")
          if root then
            linter.cwd = root
            -- nvim-lint 按启动 cwd 判断有无 go.mod,仓库根目录下会退化为只检查单个文件,
            -- 同包其他文件里的定义随之报 undefined;这里改回检查整个包目录
            linter.args[#linter.args] = function()
              return vim.fn.expand("%:p:h")
            end
          end
          return linter
        end,
      },
    },
  },
  -- live_grep 支持 `关键词 -- *.go` 过滤
  {
    "ibhagwan/fzf-lua",
    opts = { grep = { rg_glob = true } },
  },
  -- 右侧代码缩略图:显示搜索匹配、git 改动和诊断标记
  {
    "nvim-mini/mini.map",
    version = false, -- 使用最新开发版
    event = "VeryLazy",
    config = function()
      local map = require("mini.map")
      map.setup({
        integrations = {
          map.gen_integration.builtin_search(),
          -- LazyVim 默认用 gitsigns 显示 git 改动,没有 mini.diff,因此用 gitsigns 集成
          map.gen_integration.gitsigns(),
          map.gen_integration.diagnostic(),
        },
        -- 默认的 3x2 块字符很多终端字体缺字形,会显示成大方块;改用盲文点阵,兼容性好
        symbols = { encode = map.gen_encode_symbols.dot("4x2") },
      })
      vim.keymap.set("n", "<Leader>mm", MiniMap.toggle, { desc = "Toggle minimap" })
      -- 默认不会自动显示,启动后主动打开
      MiniMap.open()
    end,
  },
}
EOF

cat > "$C/lua/plugins/project.lua" <<'EOF'
-- 项目管理(ahmedkhalf/project.nvim):打开文件时自动 cd 到所在项目根目录,<leader>fp 列出最近的项目
return {
	"ahmedkhalf/project.nvim",
	-- 插件的 Lua 模块名是 project_nvim,和仓库名对不上,不写 main 的话 lazy.nvim 不会调用 setup,插件等于没启用
	main = "project_nvim",
	-- 启动时就加载:要靠它的 BufEnter 自动命令切换目录;配了 keys 后 lazy.nvim 默认会改成按键时才加载
	lazy = false,
	opts = {
		manual_mode = false,
		-- 只按 .git 等文件识别项目:LSP 识别在 monorepo 里会把 cwd 切到 backend/src,且插件的 LSP 代码用的是已废弃 API
		detection_methods = { "pattern" },
	},
	keys = {
		{
			"<leader>fp",
			function()
				local fzf = require("fzf-lua")
				local projects = vim.fn.reverse(require("project_nvim").get_recent_projects()) -- 最近的在前
				fzf.fzf_exec(projects, {
					prompt = "Projects> ",
					actions = {
						-- 回车:切到该项目目录,并打开文件树(已开着的旧文件树先关掉,避免还停在上个项目)
						["default"] = function(sel)
							vim.api.nvim_set_current_dir(sel[1])
							for _, picker in ipairs(Snacks.picker.get({ source = "explorer" })) do
								picker:close()
							end
							Snacks.explorer({ cwd = sel[1] })
						end,
						-- ctrl-f:切到该项目并在里面找文件
						["ctrl-f"] = function(sel)
							vim.api.nvim_set_current_dir(sel[1])
							fzf.files({ cwd = sel[1] })
						end,
						-- ctrl-s:在该项目里全文搜索
						["ctrl-s"] = function(sel)
							vim.api.nvim_set_current_dir(sel[1])
							fzf.live_grep({ cwd = sel[1] })
						end,
					},
				})
			end,
			desc = "Projects",
		},
	},
}
EOF

echo "已写入 $C,下一步:nvim --headless \"+Lazy! sync\" +qa"

LSP、格式化与搜索

保存时自动格式化;<leader>cf 手动格式化,<leader>uf 开关保存时格式化。工具全由 Mason 自动安装。

语言 LSP 保存时格式化 Lint
Go gopls(gofumpt 关闭) goimports golangci-lint
Python pyright + ruff ruff_organize_imports → ruff_format ruff
JS / TS vtsls prettier(仅项目有 .prettierrc 时) eslint
JSON jsonls prettier(同上) ---

全文搜索 <leader>/ 支持 glob 过滤,-- 前后都要留空格:

go 复制代码
create -- *.go               # 只搜 .go
create -- *.go !*_test.go    # 排除测试文件
TODO -- src/**/*.ts          # 限定目录

fzf 窗口内:Ctrl-j/k 上下移动,Ctrl-q 把所有结果发到 quickfix,Alt-i / Alt-h 切换是否包含被忽略 / 隐藏文件,Ctrl-r 在项目根目录和当前目录间切换,Esc 关闭。

搜索指定目录

最常用的是在 <leader>/ 的 -- 后写路径 glob,路径相对项目根目录:

lua 复制代码
create -- backend/**                  # 只搜 backend 目录
create -- backend/src/**/*.go         # backend/src 下的 .go
create -- backend/** !**/*_test.go    # backend 下,排除测试文件
create -- !web/**                     # 排除 web 目录
其他做法 写法
搜索根目录直接设为某目录 :FzfLua live_grep cwd=backend/src、:FzfLua files cwd=backend/src
在项目根目录 / nvim 当前目录间切换 fzf 窗口里按 Ctrl-r
按 nvim 当前目录搜索 先 :cd backend/src,再 <leader>sG(全文)或 <leader>fF(文件)

搜索替换(grug-far)

<leader>sr 打开替换面板;visual 模式下选中文字再按,会自动填进 Search。各栏输入后下方实时显示匹配和替换预览。

栏 填什么
Search 要搜的内容,默认是 rg 正则
Replace 替换成什么,正则分组用 $1 引用
Files Filter 文件过滤,如 *.go;默认自动填当前文件的扩展名
Flags rg 参数:-i 忽略大小写、-F 按普通文本匹配、-w 整词匹配
Paths 限定目录,如 backend/src

确认预览后回到 normal 模式操作(<localleader> 是 \):

按键 作用
\r 执行全部替换
Enter / \i 跳到 / 预览光标所在结果的位置
\j 只替换下一处
dd,然后 \s 在结果里删掉不想改的行,只把剩下的应用到文件
\q / \t / \c 发到 quickfix / 历史记录 / 关闭
g? 查看全部按键

例:把 backend 下所有 Go 文件里的 oldName 换成 newName。

  1. <leader>sr
  2. Search 填 oldName,Replace 填 newName
  3. Files Filter 填 *.go,Paths 填 backend
  4. 看一眼预览,Esc 回 normal 模式,按 \r

只改当前文件用 Vim 自带的 :%s/old/new/gc 更快,c 表示每处都确认。

Git:lazygit 与内置 Git 命令

LazyVim 自带 lazygit 集成,只要装了 lazygit 命令就能用(Mac:brew install lazygit;Ubuntu:sudo apt install lazygit)。<leader>gg 在浮窗里打开 lazygit,q 退出回到 nvim。

快捷键 作用
<leader>gg / <leader>gG lazygit(项目根目录 / nvim 当前目录)
<leader>gf 当前文件的提交历史
<leader>gl / <leader>gL 提交日志(项目根目录 / 当前目录)
<leader>gb 当前行是谁、哪次提交改的(blame)
<leader>gs / <leader>gd Git 状态 / 改动的文件(fzf 列表)
<leader>gS stash 列表
<leader>gB 在浏览器里打开当前文件的远程页面

lazygit 里的常用键:空格 暂存 / 取消暂存文件,a 全部暂存,c 提交,P push,p pull,e 在 nvim 里打开文件,1--5 切换左侧面板,? 查看全部按键。

项目切换(project.nvim)

打开任意文件时,nvim 的当前目录会自动切到该文件所在的项目根目录(按 .git 等文件识别),并把项目记入历史。<leader>fp 列出最近的项目,最近的在前:

按键 作用
Enter 进入项目:切换目录并打开文件树
Ctrl-f 进入项目并在里面找文件
Ctrl-s 进入项目并在里面全文搜索

新项目要先打开过其中的文件才会出现在列表里;历史存在 ~/.local/share/nvim/project_nvim/project_history,每行一个路径,可以直接编辑删掉不要的。

自动切换目录后,<leader>fF、<leader>sG 这类「按当前目录」的命令,范围就是当前文件所在的项目。

配置要点:插件的 Lua 模块名是 project_nvim,必须写 main = "project_nvim",否则 lazy.nvim 不会调用 setup、插件不生效;还要 lazy = false,否则配了快捷键后它只在按 <leader>fp 时才加载,打开文件不会切目录。维护分支 DrKJeff16/project.nvim 目前有 bug(打开文件不自动切目录、项目菜单报错),所以用的是原版 ahmedkhalf/project.nvim。

剪贴板、缩略图与 Go lint

剪贴板(OSC 52) :y 复制的内容经终端写到本机剪贴板,在 tmux 里、ssh 到 win-dev 上都有效,不依赖 win32yank / pbcopy。粘贴分两种情况:

场景 p 粘贴的内容 粘贴外部(浏览器等)复制的内容
在 tmux 里 tmux 缓冲区(nvim / tmux 里复制过的) 插入模式下用终端粘贴:Windows Terminal Ctrl-Shift-V,Mac Cmd-V
不在 tmux 里 nvim 内部最近复制的内容(避免 p 卡住等待) 同上

tmux 需要 set -s set-clipboard on(本机的 tmux 已是 on)。Mac 上 iTerm2 要在 Settings → General → Selection 勾上「Applications in terminal may access clipboard」;Ghostty 默认允许写入。

代码缩略图(mini.map) :启动后右侧自动显示,标出搜索匹配、git 改动和诊断的位置;<leader>mm 开关。显示用盲文点阵字符,如果是大方块,说明终端字体不是 Nerd Font。

Go lint(golangci-lint) :保存时在当前文件所属的 go.mod 目录下检查整个包。所以直接在仓库根目录 nvim . 就行(如 go.mod 在 backend/src),不用先 cd 到 go.mod 所在目录。

快捷键速查

<leader> = 空格。按下空格稍等会弹出 which-key 提示,忘了就用 <leader>sk 搜索。以下都是 LazyVim 默认键位。

分类 快捷键 作用
文件 <leader><space> / <leader>ff 查找文件(项目根目录)
文件 <leader>fr 最近文件
文件 <leader>, / <leader>fb 切换 buffer
文件 <leader>e 文件树
搜索 <leader>/ / <leader>sg 全文搜索(支持 glob)
搜索 <leader>sw 搜索光标词 / 选中内容
搜索 <leader>sR 恢复上次搜索
搜索 <leader>sr 全局搜索替换
搜索 <leader>ss / <leader>sS 文件符号 / 工作区符号
搜索 <leader>sd / <leader>sD 诊断(项目 / 当前文件)
搜索 <leader>st TODO 列表
LSP gd / gr / gI / gy 定义 / 引用 / 实现 / 类型定义
LSP Ctrl-o / Ctrl-i 跳转后后退 / 前进
LSP K 悬浮文档
LSP <leader>ca / <leader>cr 代码操作 / 重命名
LSP ]d / [d / <leader>cd 下一个 / 上一个 / 当前行诊断
LSP <leader>xx 诊断列表(Trouble)
格式化 <leader>cf / <leader>uf 手动格式化 / 开关保存时格式化
Git <leader>gg lazygit
Git <leader>gs Git 状态
编辑 s Flash 跳转
编辑 Alt-j / Alt-k 下移 / 上移行
窗口 Shift-h / Shift-l 上一个 / 下一个 buffer
窗口 <leader>bd 关闭 buffer
窗口 <leader>- / ` `
其他 Ctrl-s / <leader>qq 保存 / 退出全部
其他 <leader>l / <leader>cm 插件管理 Lazy / Mason
其他 <leader>mm 开关代码缩略图
文件 <leader>fp 最近项目列表,回车进入并打开文件树

常见问题

现象 原因 / 处理
gd 没反应、没有补全 LSP 还没装好或没启动::Mason 看安装状态,:LspInfo 看是否已挂上
golangci-lint ENOENT Go 的 linter 还在安装,等 Mason 装完即可
treesitter 报 tree-sitter CLI 缺失 同上,Mason 会自动装 tree-sitter-cli;失败就在 :Mason 里重装
JS/TS 保存不格式化 项目没有 .prettierrc;想总是格式化就删掉 options.lua 里的 lazyvim_prettier_needs_config
-- *.go 搜不到 -- 前后要有空格;修改配置后要重启 nvim
Alt 快捷键不生效(Mac) 终端里把 Option 键设为 Meta,见「Mac 快速搭建」
想回到旧的手写配置 mv ~/.config/nvim ~/.config/nvim.lazyvim && mv ~/.config/nvim.bak-lazyvim ~/.config/nvim,再执行 :Lazy restore
Go 文件报 could not import / undefined golangci-lint 没在 go.mod 目录运行;本配置已处理,仍出现就确认 custom.lua 里的 nvim-lint 段落还在,并重启 nvim
y 复制后系统剪贴板里没有 终端没开 OSC 52:tmux 加 set -s set-clipboard on,iTerm2 勾上允许访问剪贴板
缩略图显示成大方块 终端字体换成 Nerd Font;不想要就 <leader>mm 关掉

要加功能先看 LazyVim 有没有对应 extra::LazyExtras 里按 x 启用,不用写代码。

相关推荐
奶茶树6 天前
【Linux】环境开发工具(vim,gcc,g++,Makefile)
linux·运维·vim
NextTech_10 天前
不想学 vim,怎么改服务器上的配置文件?
服务器·编辑器·vim
行走的陀螺仪13 天前
Claude Code 从入门到实战(三):Hooks、Skills、SubAgents 高级扩展
linux·编辑器·vim
斐夷所非13 天前
Vim 学习速成:理解快捷键
vim
斐夷所非14 天前
Vim 交换文件处理、内容恢复、保存退出与权限提升
vim
by2099917 天前
Linux 软件安装与 Vim 编辑
linux·经验分享·vim
FriendshipT21 天前
Vim 完整指南:从入门到高效编辑
linux·人工智能·编辑器·vim·1024程序员节
.道阻且长.21 天前
Linux 基本开发工具:编辑器vim,配置vim
linux·运维·服务器·职场和发展·编辑器·vim·1024程序员节
Rabitebla22 天前
【Linux底层原理】 工具篇(二):Vim 编辑器基础操作与简单配置
linux·c++·编辑器·vim