Add comprehensive Chinese comments and uniform 4-space indentation across all config files

- Document every Lua module with detailed Chinese comments explaining
  purpose, design decisions, and key implementation details
- Standardize indentation from tabs to 4 spaces for consistency
- Add .claude/ to .gitignore

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
xfy 2026-05-28 15:36:55 +08:00
parent c5a31a26f6
commit 4366e3d696
12 changed files with 1375 additions and 576 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
.omc/
others/
.claude/

114
init.lua
View File

@ -1,42 +1,88 @@
-- =============================================================================
-- Neovim 配置文件入口 (init.lua)
-- =============================================================================
-- 本文件是 Neovim 启动时第一个加载的配置文件。
-- 设计目标:最小化启动开销,所有重模块均通过自定义懒加载框架延迟加载。
--
-- 文件加载顺序:
-- 1. init.lua → 禁用内置插件、设置 colorscheme、加载核心模块
-- 2. options.lua → 全局选项 (number, indent, clipboard 等)
-- 3. keymaps.lua → 键位映射 (leader = space)
-- 4. autocmds.lua → 自动命令 (恢复光标位置、折叠、yank 高亮等)
-- 5. usercmds.lua → 自定义命令 (:PackAdd, :PackDel, :PackUpdate)
-- 6. pack.lua → 插件声明与懒加载配置
-- =============================================================================
-- 记录启动时间点,用于在 starter 页脚展示启动耗时。
-- vim.uv (Neovim 0.10+) 是 libuv 的绑定,提供高性能计时器;
-- vim.loop 是旧版兼容名,在 0.10 之前使用。
_G.nvim_start_time = (vim.uv or vim.loop).hrtime()
-- 禁用不需要的内置插件,减少启动时 source 的 plugin 文件
vim.g.loaded_2html_plugin = 1
vim.g.loaded_getscript = 1
vim.g.loaded_getscriptPlugin = 1
vim.g.loaded_gzip = 1
vim.g.loaded_logipat = 1
vim.g.loaded_matchit = 1
vim.g.loaded_matchparen = 1
vim.g.loaded_netrw = 1
vim.g.loaded_netrwFileHandlers = 1
vim.g.loaded_netrwPlugin = 1
vim.g.loaded_netrwSettings = 1
vim.g.loaded_rrhelper = 1
vim.g.loaded_spellfile_plugin = 1
vim.g.loaded_tar = 1
vim.g.loaded_tarPlugin = 1
vim.g.loaded_tutor_mode_plugin = 1
vim.g.loaded_vimball = 1
vim.g.loaded_vimballPlugin = 1
vim.g.loaded_zip = 1
vim.g.loaded_zipPlugin = 1
vim.g.loaded_rplugin = 1
vim.g.loaded_tohtml = 1
vim.g.loaded_syntax = 1
vim.g.loaded_synmenu = 1
vim.g.loaded_optwin = 1
vim.g.loaded_compiler = 1
vim.g.loaded_bugreport = 1
vim.g.loaded_ftplugin = 1
-- =============================================================================
-- 禁用不需要的内置插件
-- =============================================================================
-- Neovim 默认会加载大量内置插件netrw、tar、zip 等),
-- 这些插件会在 startup 阶段 source 对应的 plugin/*.vim 文件,增加启动时间。
-- 通过设置 loaded_* 全局变量为 1真值可以跳过这些插件的加载。
--
-- 注意loaded_syntax 设为 1 是为了避免旧版 Vim 兼容的 syntax 加载路径;
-- 实际语法高亮由 treesitter 接管,不受影响。
vim.g.loaded_2html_plugin = 1 -- :TOhtml 命令(将 buffer 导出为 HTML
vim.g.loaded_getscript = 1 -- GetScript 插件(自动下载脚本)
vim.g.loaded_getscriptPlugin = 1 -- GetScript 插件主体
vim.g.loaded_gzip = 1 -- Gzip 压缩文件读写支持
vim.g.loaded_logipat = 1 -- LogiPat 逻辑模式匹配
vim.g.loaded_matchit = 1 -- % 匹配扩展(由 treesitter 或 mini.pairs 替代)
vim.g.loaded_matchparen = 1 -- 括号匹配高亮(由 treesitter 替代)
vim.g.loaded_netrw = 1 -- Netrw 文件浏览器(由 mini.files 替代)
vim.g.loaded_netrwFileHandlers = 1 -- Netrw 文件处理器
vim.g.loaded_netrwPlugin = 1 -- Netrw 插件主体
vim.g.loaded_netrwSettings = 1 -- Netrw 设置
vim.g.loaded_rrhelper = 1 -- RRHelperR 语言相关)
vim.g.loaded_spellfile_plugin = 1 -- 拼写文件自动下载
vim.g.loaded_tar = 1 -- Tar 归档支持
vim.g.loaded_tarPlugin = 1 -- Tar 插件主体
vim.g.loaded_tutor_mode_plugin = 1 -- Vim Tutor 教程模式
vim.g.loaded_vimball = 1 -- Vimball 归档格式
vim.g.loaded_vimballPlugin = 1 -- Vimball 插件主体
vim.g.loaded_zip = 1 -- Zip 归档支持
vim.g.loaded_zipPlugin = 1 -- Zip 插件主体
vim.g.loaded_rplugin = 1 -- 远程插件框架
vim.g.loaded_tohtml = 1 -- :TOhtml 的另一入口
vim.g.loaded_syntax = 1 -- 旧版 syntax 自动加载
vim.g.loaded_synmenu = 1 -- Syntax 菜单
vim.g.loaded_optwin = 1 -- 选项窗口 (:options)
vim.g.loaded_compiler = 1 -- 编译器插件自动加载
vim.g.loaded_bugreport = 1 -- Bug 报告生成
vim.g.loaded_ftplugin = 1 -- 文件类型插件(手动管理)
-- =============================================================================
-- 启用 Neovim 0.12+ 内置 UI 增强
-- =============================================================================
-- vim._core.ui2 是 Neovim 0.12 实验性的 UI 增强模块,
-- 启用后提供更现代的默认界面行为(如更好的消息处理)。
require("vim._core.ui2").enable({})
require("options")
require("keymaps")
require("autocmds")
require("usercmds")
require("pack")
-- =============================================================================
-- 加载核心配置模块
-- =============================================================================
-- 这些模块按顺序加载,后续模块可以依赖前面模块的设置。
-- 例如 pack.lua 中使用的键位映射依赖于 keymaps.lua 中设置的 leader 键。
require("options") -- 全局 vim 选项设置
require("keymaps") -- 键位映射定义
require("autocmds") -- 自动命令autocmd
require("usercmds") -- 用户自定义命令
require("pack") -- 插件管理与懒加载配置
-- =============================================================================
-- Colorscheme 设置
-- =============================================================================
-- moonflyTransparent使 moonfly 主题的背景透明,
-- 即使当前使用 catppuccin-mocha此变量仍保留以备切换主题。
vim.g.moonflyTransparent = true
-- 应用 colorscheme。catppuccin-mocha 的定义在 colors/catppuccin-mocha.lua 中。
-- 该文件是一个精简版,只包含 Mocha 变体的调色板和高亮组定义。
vim.cmd("colorscheme catppuccin-mocha")

View File

@ -1,36 +1,84 @@
-- =============================================================================
-- 自动命令配置 (lua/autocmds.lua)
-- =============================================================================
-- 自动命令autocmd是事件驱动的回调机制
-- 在特定编辑器事件(如打开文件、切换 buffer、进入插入模式等发生时自动执行。
--
-- 所有 autocmd 归属于 "UserAutocmds" 组,
-- 使用 { clear = true } 确保每次重载配置时先清除旧的 autocmd
-- 避免重复注册导致命令被多次触发。
-- =============================================================================
-- 创建用户 autocmd 组clear = true 表示创建前清空组内所有现有命令
local group = vim.api.nvim_create_augroup("UserAutocmds", { clear = true })
-- 恢复上次编辑位置
-- ---------------------------------------------------------------------------
-- 1. 恢复上次编辑位置
-- ---------------------------------------------------------------------------
-- 打开文件后,将光标恢复到上次关闭时的位置。
-- 原理Vim 在关闭文件时会保存光标位置到 `"` 标记(双引号标记),
-- 下次打开时可通过此标记恢复。
--
-- 跳过的情况:
-- - commit / gitcommitGit 提交消息文件,总是从头开始编辑
-- - xxd十六进制编辑模式
-- - gitrebaseGit rebase 交互式编辑
vim.api.nvim_create_autocmd("BufReadPost", {
group = group,
callback = function()
local mark = vim.api.nvim_buf_get_mark(0, '"')
local row = mark[1]
local ft = vim.bo.filetype
group = group,
callback = function()
-- 获取 `"` 标记的位置(行, 列)
local mark = vim.api.nvim_buf_get_mark(0, '"')
local row = mark[1] -- 行号1-based
local ft = vim.bo.filetype
if row > 1 and row <= vim.fn.line("$") then
if ft ~= "commit" and ft ~= "gitcommit" and not ft:match("xxd") and not ft:match("gitrebase") then
vim.api.nvim_win_set_cursor(0, mark)
end
end
end,
-- 仅当标记有效(行号 > 1 且在文件范围内)时恢复光标
if row > 1 and row <= vim.fn.line("$") then
-- 排除特定文件类型
if ft ~= "commit" and ft ~= "gitcommit" and not ft:match("xxd") and not ft:match("gitrebase") then
vim.api.nvim_win_set_cursor(0, mark)
end
end
end,
})
-- 注释延续行为
-- ---------------------------------------------------------------------------
-- 2. 注释延续行为
-- ---------------------------------------------------------------------------
-- 控制按回车或 o/O 时是否自动延续注释符号(如 //、#、--)。
--
-- formatoptions 说明:
-- o - 使用 o/O 换行时延续注释(被移除,避免在 normal 模式下意外延续)
-- r - 按回车时延续注释保留insert 模式下回车延续注释符)
--
-- 注意此设置对自动格式化conform.nvim的行为也有影响。
vim.api.nvim_create_autocmd("FileType", {
group = group,
callback = function()
vim.opt_local.formatoptions:remove("o") -- o/O 换行不延续注释
vim.opt_local.formatoptions:append("r") -- 回车延续注释
end,
group = group,
callback = function()
-- 移除 'o':按 o/O 时不自动插入注释符号
vim.opt_local.formatoptions:remove("o")
-- 追加 'r':按回车时自动插入注释符号
vim.opt_local.formatoptions:append("r")
end,
})
-- 延迟设置 Treesitter 折叠(避免启动时加载 treesitter 模块)
-- ---------------------------------------------------------------------------
-- 3. 延迟设置 Treesitter 折叠
-- ---------------------------------------------------------------------------
-- 使用 treesitter 的 foldexpr 进行语法感知的代码折叠。
-- 延迟到 BufEnter 时设置(而非启动时),避免在 startup 阶段加载 treesitter 模块。
--
-- once = true 表示此 autocmd 只触发一次,首次 BufEnter 后自动销毁。
-- foldmethod = "expr" 表示使用表达式foldexpr计算折叠范围。
-- foldexpr = "v:lua.vim.treesitter.foldexpr()" 是 Neovim 0.10+ 的内置函数,
-- 基于 treesitter 语法树计算折叠边界。
vim.api.nvim_create_autocmd("BufEnter", {
group = group,
once = true,
callback = function()
vim.o.foldmethod = "expr"
vim.o.foldexpr = "v:lua.vim.treesitter.foldexpr()"
end,
group = group,
once = true,
callback = function()
vim.o.foldmethod = "expr"
vim.o.foldexpr = "v:lua.vim.treesitter.foldexpr()"
end,
})

View File

@ -1,111 +1,202 @@
-- =============================================================================
-- Git 工具封装 (lua/git.lua)
-- =============================================================================
-- 本模块提供基于 mini.diff 和原生 git 命令的 Git 相关功能。
--
-- 功能列表:
-- 1. Hunk Preview<leader>ghp— 预览当前光标处 git diff hunk
-- 2. Blame Line<leader>ghb— 查看当前行的 git blame 信息
-- 3. Blame Buffer<leader>gB— 整文件 blamevia fugitive
-- 4. File History<leader>gD— 查看当前文件的 git log
--
-- 依赖:
-- - mini.diff已在 pack.lua 中通过 BufReadPost 懒加载)
-- - vim-fugitive已在 pack.lua 中通过按键触发懒加载)
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 辅助函数:获取光标所在行的 hunk
-- ---------------------------------------------------------------------------
-- 遍历当前 buffer 的所有 hunks找到包含光标所在行的那个。
--
-- 返回值:
-- hunk - hunk 对象 { type, buf_start, buf_count, ref_start, ref_count }
-- ref_text - 参考文本HEAD 版本的内容)
-- nil - 光标不在任何 hunk 上
local function get_cursor_hunk()
local buf = vim.api.nvim_get_current_buf()
local data = require("mini.diff").get_buf_data(buf)
if not data or not data.hunks or #data.hunks == 0 then
return nil
end
local cursor_line = vim.api.nvim_win_get_cursor(0)[1]
for _, h in ipairs(data.hunks) do
local start_line = math.max(h.buf_start, 1)
local end_line = h.buf_start + h.buf_count - 1
if h.buf_count == 0 then
end_line = start_line
end
if cursor_line >= start_line and cursor_line <= end_line then
return h, data.ref_text
end
end
return nil
local buf = vim.api.nvim_get_current_buf()
-- 从 mini.diff 获取当前 buffer 的 diff 数据
local data = require("mini.diff").get_buf_data(buf)
if not data or not data.hunks or #data.hunks == 0 then
return nil
end
local cursor_line = vim.api.nvim_win_get_cursor(0)[1]
for _, h in ipairs(data.hunks) do
-- hunk 在 buffer 中的起始行1-based
local start_line = math.max(h.buf_start, 1)
-- hunk 在 buffer 中的结束行
local end_line = h.buf_start + h.buf_count - 1
-- 空删除0 行)时特殊处理
if h.buf_count == 0 then
end_line = start_line
end
-- 检查光标是否在此 hunk 范围内
if cursor_line >= start_line and cursor_line <= end_line then
return h, data.ref_text
end
end
return nil
end
-- ---------------------------------------------------------------------------
-- Hunk Preview — 浮动窗口预览当前 hunk
-- ---------------------------------------------------------------------------
-- 创建一个居中的浮动窗口,显示光标所在 hunk 的详细 diff 信息。
-- 支持三种 hunk 类型:
-- add — 显示新增的行
-- delete — 显示被删除的行(从参考文本中恢复)
-- change — 并排显示修改前后的内容
local function preview_hunk()
local hunk, ref_text = get_cursor_hunk()
if not hunk then
vim.notify("Cursor not on a hunk", vim.log.levels.WARN)
return
end
local lines = {}
table.insert(lines, "Hunk type: " .. hunk.type)
table.insert(lines, string.format("Buffer lines: %d-%d", hunk.buf_start, hunk.buf_start + hunk.buf_count - 1))
table.insert(lines, string.format("Reference lines: %d-%d", hunk.ref_start, hunk.ref_start + hunk.ref_count - 1))
table.insert(lines, "---")
local buf = vim.api.nvim_get_current_buf()
local ref_lines = vim.split(ref_text or "", "\n")
if hunk.type == "delete" then
table.insert(lines, "Deleted lines (from reference):")
for i = hunk.ref_start, hunk.ref_start + hunk.ref_count - 1 do
table.insert(lines, "- " .. (ref_lines[i] or ""))
end
elseif hunk.type == "add" then
table.insert(lines, "Added lines:")
for i = hunk.buf_start, hunk.buf_start + hunk.buf_count - 1 do
table.insert(lines, "+ " .. (vim.api.nvim_buf_get_lines(buf, i - 1, i, false)[1] or ""))
end
else
table.insert(lines, "Before (reference):")
for i = hunk.ref_start, hunk.ref_start + hunk.ref_count - 1 do
table.insert(lines, "- " .. (ref_lines[i] or ""))
end
table.insert(lines, "After (buffer):")
for i = hunk.buf_start, hunk.buf_start + hunk.buf_count - 1 do
table.insert(lines, "+ " .. (vim.api.nvim_buf_get_lines(buf, i - 1, i, false)[1] or ""))
end
end
local width = math.min(80, vim.o.columns - 4)
local height = math.min(#lines + 2, vim.o.lines - 4)
local preview_buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(preview_buf, 0, -1, false, lines)
vim.api.nvim_set_option_value("modifiable", false, { buf = preview_buf })
vim.api.nvim_set_option_value("filetype", "diff", { buf = preview_buf })
local win = vim.api.nvim_open_win(preview_buf, true, {
relative = "editor",
row = math.floor((vim.o.lines - height) / 2),
col = math.floor((vim.o.columns - width) / 2),
width = width,
height = height,
style = "minimal",
border = "rounded",
title = " Hunk Preview ",
title_pos = "center",
})
vim.keymap.set("n", "q", function()
vim.api.nvim_win_close(win, true)
end, { buffer = preview_buf })
vim.keymap.set("n", "<Esc>", function()
vim.api.nvim_win_close(win, true)
end, { buffer = preview_buf })
local hunk, ref_text = get_cursor_hunk()
if not hunk then
vim.notify("光标不在变更区域", vim.log.levels.WARN)
return
end
-- 构建预览内容
local lines = {}
table.insert(lines, "Hunk 类型: " .. hunk.type)
table.insert(lines, string.format("Buffer 行: %d-%d", hunk.buf_start, hunk.buf_start + hunk.buf_count - 1))
table.insert(lines, string.format("参考行: %d-%d", hunk.ref_start, hunk.ref_start + hunk.ref_count - 1))
table.insert(lines, "---")
local buf = vim.api.nvim_get_current_buf()
local ref_lines = vim.split(ref_text or "", "\n")
if hunk.type == "delete" then
-- 删除型 hunk显示被删除的内容来自参考文本
table.insert(lines, "被删除的行(来自参考版本):")
for i = hunk.ref_start, hunk.ref_start + hunk.ref_count - 1 do
table.insert(lines, "- " .. (ref_lines[i] or ""))
end
elseif hunk.type == "add" then
-- 新增型 hunk显示 buffer 中新增的内容
table.insert(lines, "新增的行:")
for i = hunk.buf_start, hunk.buf_start + hunk.buf_count - 1 do
table.insert(lines, "+ " .. (vim.api.nvim_buf_get_lines(buf, i - 1, i, false)[1] or ""))
end
else
-- 修改型 hunk显示修改前后的对比
table.insert(lines, "修改前(参考版本):")
for i = hunk.ref_start, hunk.ref_start + hunk.ref_count - 1 do
table.insert(lines, "- " .. (ref_lines[i] or ""))
end
table.insert(lines, "修改后(当前版本):")
for i = hunk.buf_start, hunk.buf_start + hunk.buf_count - 1 do
table.insert(lines, "+ " .. (vim.api.nvim_buf_get_lines(buf, i - 1, i, false)[1] or ""))
end
end
-- 计算浮动窗口尺寸
local width = math.min(80, vim.o.columns - 4)
local height = math.min(#lines + 2, vim.o.lines - 4)
-- 创建预览 buffer
local preview_buf = vim.api.nvim_create_buf(false, true) -- 无文件、可编辑
vim.api.nvim_buf_set_lines(preview_buf, 0, -1, false, lines)
vim.api.nvim_set_option_value("modifiable", false, { buf = preview_buf })
vim.api.nvim_set_option_value("filetype", "diff", { buf = preview_buf }) -- diff 语法高亮
-- 打开浮动窗口
local win = vim.api.nvim_open_win(preview_buf, true, {
relative = "editor",
row = math.floor((vim.o.lines - height) / 2),
col = math.floor((vim.o.columns - width) / 2),
width = width,
height = height,
style = "minimal",
border = "rounded",
title = " Hunk Preview ",
title_pos = "center",
})
-- q / Esc 关闭窗口
vim.keymap.set("n", "q", function()
vim.api.nvim_win_close(win, true)
end, { buffer = preview_buf })
vim.keymap.set("n", "<Esc>", function()
vim.api.nvim_win_close(win, true)
end, { buffer = preview_buf })
end
-- ---------------------------------------------------------------------------
-- Blame Line — 查看当前行的 Git blame 信息
-- ---------------------------------------------------------------------------
-- 执行 git blame --porcelain 获取当前行的详细提交信息,
-- 以通知消息的形式展示提交哈希、作者、时间和提交摘要。
--
-- --porcelain 格式是机器可读的 blame 输出,包含以下字段:
-- <hash> <orig-line> <final-line> [<num-lines>]
-- author <name>
-- author-mail <email>
-- author-time <timestamp>
-- author-tz <timezone>
-- summary <commit-message>
-- ...
local function blame_line()
local file = vim.api.nvim_buf_get_name(0)
if file == "" then
vim.notify("No file name", vim.log.levels.WARN)
return
end
local line = vim.api.nvim_win_get_cursor(0)[1]
local cmd = { "git", "blame", "-L", line .. "," .. line, "--porcelain", file }
local output = vim.fn.system(cmd)
if vim.v.shell_error ~= 0 then
vim.notify("git blame failed", vim.log.levels.ERROR)
return
end
local hash = output:match("^(%x+)%s") or "?"
local author = output:match("author ([^\n]+)") or "?"
local email = output:match("author%-mail ([^\n]+)") or "?"
local time = output:match("author%-time (%d+)")
local summary = output:match("summary ([^\n]+)") or "?"
local time_str = time and os.date("%Y-%m-%d %H:%M", tonumber(time)) or "?"
vim.notify(
string.format("%s | %s %s | %s\n%s", hash:sub(1, 8), author, email, time_str, summary),
vim.log.levels.INFO
)
local file = vim.api.nvim_buf_get_name(0)
if file == "" then
vim.notify("当前 buffer 无文件名", vim.log.levels.WARN)
return
end
local line = vim.api.nvim_win_get_cursor(0)[1]
-- -L 限制 blame 范围为单行,提升性能
local cmd = { "git", "blame", "-L", line .. "," .. line, "--porcelain", file }
local output = vim.fn.system(cmd)
if vim.v.shell_error ~= 0 then
vim.notify("git blame 执行失败", vim.log.levels.ERROR)
return
end
-- 解析 --porcelain 输出
local hash = output:match("^(%x+)%s") or "?"
local author = output:match("author ([^\n]+)") or "?"
local email = output:match("author%-mail ([^\n]+)") or "?"
local time = output:match("author%-time (%d+)")
local summary = output:match("summary ([^\n]+)") or "?"
local time_str = time and os.date("%Y-%m-%d %H:%M", tonumber(time)) or "?"
vim.notify(
string.format("%s | %s %s | %s\n%s", hash:sub(1, 8), author, email, time_str, summary),
vim.log.levels.INFO
)
end
vim.keymap.set("n", "<leader>ghp", preview_hunk, { desc = "Preview hunk" })
vim.keymap.set("n", "<leader>ghb", blame_line, { desc = "Blame line" })
-- ---------------------------------------------------------------------------
-- 键位映射
-- ---------------------------------------------------------------------------
-- <leader>ghp — 预览当前 hunk
vim.keymap.set("n", "<leader>ghp", preview_hunk, { desc = "预览 hunk" })
-- <leader>ghb — 查看当前行 blame
vim.keymap.set("n", "<leader>ghb", blame_line, { desc = "Blame 当前行" })
-- <leader>gB — 整文件 blame使用 fugitive 的 :Git blame
vim.keymap.set("n", "<leader>gB", function()
vim.cmd("Git blame")
end, { desc = "Blame buffer" })
vim.cmd("Git blame")
end, { desc = "Blame 整个文件" })
-- <leader>gD — 查看当前文件的 git 历史
vim.keymap.set("n", "<leader>gD", function()
vim.cmd("Git log -p -- %")
end, { desc = "Git file history" })
vim.cmd("Git log -p -- %")
end, { desc = "查看文件 Git 历史" })

View File

@ -1,84 +1,177 @@
-- =============================================================================
-- 键位映射配置 (lua/keymaps.lua)
-- =============================================================================
-- Leader 键设置为 Space所有以 <leader> 开头的映射均使用空格触发。
--
-- 映射模式说明:
-- n - normal 模式(默认)
-- v - visual / select 模式
-- i - insert 模式
-- t - terminal 模式
-- x - visual 模式(不含 select
--
-- 按键分组:
-- <leader> f* → 查找/文件相关(由 pick.lua 补充)
-- <leader> g* → Git 相关(由 git.lua 补充)
-- <leader> b* → Buffer 管理
-- <leader> y* → 复制/粘贴
-- <leader> t* → Terminal/Tab
-- <C-*> → 窗口导航与保存
-- =============================================================================
-- 设置 Leader 键为空格。必须在任何 <leader> 映射之前设置。
vim.g.mapleader = " "
-- 本地别名,简化映射代码
local map = vim.keymap.set
map("n", "<Esc>", ":nohl<CR>", { desc = "Clear search highlighting", silent = true })
map("v", "<", "<gv", { desc = "Unindent and keep selection" })
map("v", ">", ">gv", { desc = "Indent and keep selection" })
map("n", "J", "mzJ`z", { desc = "Join lines without moving cursor" })
-- ---------------------------------------------------------------------------
-- 基础编辑映射
-- ---------------------------------------------------------------------------
-- 按 Esc 清除搜索高亮(/ ? 搜索后的匹配高亮)
-- silent = true 避免在命令行显示 :nohl 的反馈
map("n", "<Esc>", ":nohl<CR>", { desc = "清除搜索高亮", silent = true })
-- Visual 模式下缩进后保持选区,便于连续多次缩进
map("v", "<", "<gv", { desc = "减少缩进并保持选区" })
map("v", ">", ">gv", { desc = "增加缩进并保持选区" })
-- J合并行时不移动光标。
-- 原理mz 设置标记 zJ 合并行,`z 跳回标记位置。
map("n", "J", "mzJ`z", { desc = "合并行且不移动光标" })
-- ---------------------------------------------------------------------------
-- 搜索与替换
-- ---------------------------------------------------------------------------
-- 快速替换当前光标下的单词(全局替换)。
-- <C-r><C-w> 在命令行插入光标下的单词。
-- 映射展开后形如 :%s/oldword/oldword/gI光标停在末尾可修改替换内容。
map(
"n",
"<leader>ss",
[[:%s/\<<C-r><C-w>\>/<C-r><C-w>/gI<Left><Left><Left>]],
{ desc = "Replace word cursor is on globally" }
[[:%s/<<C-r><C-w>>/<<C-r><C-w>>/gI<Left><Left><Left>]],
{ desc = "全局替换光标下的单词" }
)
map("v", "<leader>ss", ":s/\\%V", { desc = "Search and replace in visual selection" })
-- general
-- Visual 模式下在选区范围内搜索替换
-- \%V 是 Vim 正则中的可视区域限定符,确保替换只在选区内生效
map("v", "<leader>ss", ":s/\\%V", { desc = "在可视选区内搜索替换" })
-- ---------------------------------------------------------------------------
-- 行尾操作符重映射
-- ---------------------------------------------------------------------------
-- 将 $ 映射为 g_行尾最后一个非空白字符
-- 这比 $(真正的行尾,通常包含尾随空格)更符合直觉。
map("n", "$", "g_")
map("v", "$", "g_")
-- 再次映射缩进保持选区(与上方重复,确保可靠性)
map("v", ">", ">gv")
map("v", "<", "<gv")
-- native undotree
-- ---------------------------------------------------------------------------
-- 内置撤销树 (Undotree)
-- ---------------------------------------------------------------------------
-- Neovim 0.12+ 内置了 undotree 插件nvim.undotree
-- 这里通过 packadd 按需加载并打开。
vim.keymap.set("n", "<leader>u", function()
vim.cmd.packadd("nvim.undotree")
require("undotree").open()
end, { desc = "Toggle Builtin Undotree" })
end, { desc = "打开内置撤销树" })
map("n", "<C-s>", "<cmd>w<CR>", { desc = "Save file" })
map("n", "<C-c>", "<cmd>%y+<CR>", { desc = "Copy whole file" })
-- ---------------------------------------------------------------------------
-- 文件操作
-- ---------------------------------------------------------------------------
map("t", "<C-x>", "<c-\\><c-n>", { desc = "Escape termainl" })
map("n", "<leader>tt", ":term<CR>", { desc = "Open new terminal" })
-- <C-s> 保存当前文件normal 模式)
map("n", "<C-s>", "<cmd>w<CR>", { desc = "保存文件" })
-- window navigation
map("n", "<C-h>", "<C-w>h", { desc = "Switch to left window" })
map("n", "<C-j>", "<C-w>j", { desc = "Switch to down window" })
map("n", "<C-k>", "<C-w>k", { desc = "Switch to up window" })
map("n", "<C-l>", "<C-w>l", { desc = "Switch to right window" })
-- <C-c> 复制整行内容到系统剪贴板
map("n", "<C-c>", "<cmd>%y+<CR>", { desc = "复制整个文件内容" })
-- tabs
map("n", "<leader>tc", ":tabclose<CR>", { desc = "Close current tab" })
map("n", "<leader>tn", ":tabnew<CR>", { desc = "New tab" })
map("n", "<leader>]", ":tabnext<CR>", { desc = "Next tab" })
map("n", "<leader>[", ":tabprevious<CR>", { desc = "Previous tab" })
-- ---------------------------------------------------------------------------
-- Terminal 模式
-- ---------------------------------------------------------------------------
-- yank path
-- Terminal 模式下按 <C-x> 返回 Normal 模式
-- <c-><c-n> 是 Vim 内置的终端转义序列
map("t", "<C-x>", "<c-\\><c-n>", { desc = "从终端模式返回普通模式" })
-- 打开新的终端窗口
map("n", "<leader>tt", ":term<CR>", { desc = "打开新终端" })
-- ---------------------------------------------------------------------------
-- 窗口导航
-- ---------------------------------------------------------------------------
-- 使用 <C-h/j/k/l> 在窗口间快速跳转,替代 <C-w> h/j/k/l 的繁琐操作。
map("n", "<C-h>", "<C-w>h", { desc = "切换到左侧窗口" })
map("n", "<C-j>", "<C-w>j", { desc = "切换到下方窗口" })
map("n", "<C-k>", "<C-w>k", { desc = "切换到上方窗口" })
map("n", "<C-l>", "<C-w>l", { desc = "切换到右侧窗口" })
-- ---------------------------------------------------------------------------
-- Tab 管理
-- ---------------------------------------------------------------------------
map("n", "<leader>tc", ":tabclose<CR>", { desc = "关闭当前标签页" })
map("n", "<leader>tn", ":tabnew<CR>", { desc = "新建标签页" })
map("n", "<leader>]", ":tabnext<CR>", { desc = "下一个标签页" })
map("n", "<leader>[", ":tabprevious<CR>", { desc = "上一个标签页" })
-- ---------------------------------------------------------------------------
-- 复制文件路径
-- ---------------------------------------------------------------------------
-- 复制当前文件的相对路径到系统剪贴板
map("n", "<leader>yp", function()
local path = vim.fn.expand("%")
vim.fn.setreg("+", path)
vim.notify("Copied relative path: " .. path, vim.log.levels.INFO)
end, { desc = "Yank relative file path" })
vim.notify("已复制相对路径: " .. path, vim.log.levels.INFO)
end, { desc = "复制相对文件路径" })
-- 复制当前文件的绝对路径到系统剪贴板
map("n", "<leader>yP", function()
local path = vim.fn.expand("%:p")
vim.fn.setreg("+", path)
vim.notify("Copied absolute path: " .. path, vim.log.levels.INFO)
end, { desc = "Yank absolute file path" })
vim.notify("已复制绝对路径: " .. path, vim.log.levels.INFO)
end, { desc = "复制绝对文件路径" })
-- buffers
-- ---------------------------------------------------------------------------
-- Buffer 管理
-- ---------------------------------------------------------------------------
-- 关闭当前 Buffer有未保存更改时提示确认。
-- 使用 vim.ui.select 提供交互式选择对话框。
map("n", "<leader>x", function()
local buf = vim.api.nvim_get_current_buf()
if vim.bo[buf].modified then
vim.ui.select({ "Yes", "No" }, {
prompt = "Buffer has unsaved changes. Close without saving?",
vim.ui.select({ "", "" }, {
prompt = "Buffer 有未保存的更改,不保存就关闭吗?",
format_item = function(item)
return item
end,
}, function(choice)
if choice == "Yes" then
if choice == "" then
vim.api.nvim_buf_delete(buf, { force = true })
end
end)
else
vim.cmd.bdelete()
end
end, { desc = "Close current buffer" })
map("n", "<leader>bn", "<cmd>enew<CR>", { desc = "Buffer new" })
end, { desc = "关闭当前 Buffer" })
-- 新建空 Buffer
map("n", "<leader>bn", "<cmd>enew<CR>", { desc = "新建 Buffer" })
-- 关闭除当前 Buffer 外的所有 Buffer。
-- 跳过指定文件类型的 Buffer如文件管理器避免误关闭侧边栏。
map("n", "<leader>bo", function()
local current = vim.api.nvim_get_current_buf()
local skipped_ft = { "NvimTree", "oil", "aerial" }
local skipped_ft = { "NvimTree", "oil", "aerial" } -- 跳过的文件类型列表
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
if buf ~= current and vim.api.nvim_buf_is_loaded(buf) then
local ft = vim.bo[buf].filetype
@ -87,4 +180,4 @@ map("n", "<leader>bo", function()
end
end
end
end, { desc = "Close other buffers" })
end, { desc = "关闭其他 Buffer" })

View File

@ -1,7 +1,38 @@
-- =============================================================================
-- 自定义懒加载框架 (lua/lazy.lua)
-- =============================================================================
-- 本模块与 lazy.nvim 插件管理器完全无关,
-- 是一个约 35 行的轻量级懒加载原语封装,用于延迟加载重型插件模块。
--
-- 核心设计:
-- - 通过 _loaded 表跟踪已加载模块,避免重复初始化
-- - on_event通过 autocmd 在特定事件(如 InsertEnter、BufReadPost触发加载
-- - on_keys通过键位映射触发加载首次按键时初始化插件
-- - load直接加载用于手动触发或内部调用
-- =============================================================================
local M = {}
-- 已加载模块的标记表。
-- 键为模块标识名(如 "treesitter", "lsp", "completion"
-- 值为 true 表示该模块已完成初始化。
-- 此表用于防止重复加载和重复执行 setup()。
M._loaded = {}
-- ---------------------------------------------------------------------------
-- 直接加载模块
-- ---------------------------------------------------------------------------
-- 参数:
-- name - 模块标识名(自定义字符串,用于 _loaded 去重)
-- fn - 可选的初始化函数,在首次加载时执行
--
-- 行为:
-- 1. 检查 _loaded[name] 是否为真,是则直接返回(幂等)
-- 2. 标记为已加载
-- 3. 如果有 fn执行 fn() 完成插件 setup
--
-- 使用场景:
-- 在按键回调中手动触发加载,或在 on_event 回调中延迟初始化。
M.load = function(name, fn)
if M._loaded[name] then
return
@ -12,6 +43,24 @@ M.load = function(name, fn)
end
end
-- ---------------------------------------------------------------------------
-- 事件触发懒加载
-- ---------------------------------------------------------------------------
-- 参数:
-- name - 模块标识名
-- event - Neovim autocmd 事件名(如 "InsertEnter", "BufReadPost", "VimEnter"
-- pattern - autocmd 匹配模式,默认为 "*"
-- fn - 初始化函数
--
-- 原理:
-- 创建一个一次性的 autocmdonce = true
-- 当指定事件首次触发时,调用 M.load(name, fn) 完成初始化。
-- 由于 once = trueautocmd 在触发后自动销毁,不会重复执行。
--
-- 典型用法:
-- lazy.on_event("completion", "InsertEnter", "*", function()
-- require("mini.completion").setup({ ... })
-- end)
M.on_event = function(name, event, pattern, fn)
vim.api.nvim_create_autocmd(event, {
pattern = pattern or "*",
@ -22,6 +71,26 @@ M.on_event = function(name, event, pattern, fn)
})
end
-- ---------------------------------------------------------------------------
-- 按键触发懒加载
-- ---------------------------------------------------------------------------
-- 参数:
-- name - 模块标识名
-- keys - 键位序列(如 "<leader>ff", "<leader>gg"
-- mode - 映射模式,默认为 "n"normal
-- fn - 初始化函数,在首次按键时执行
-- action - 可选的额外动作函数,在 fn 之后执行
-- opts - 键位映射选项desc, buffer 等)
--
-- 原理:
-- 创建一个键位映射,首次按下时:
-- 1. 调用 M.load(name, fn) 完成插件初始化
-- 2. 如果提供了 action执行 action()
-- 3. 后续按键直接执行 action因为 _loaded 已标记)
--
-- 注意:
-- 此设计使得首次按键稍慢(需要 setup后续按键与直接映射无异。
-- 适合重型插件如 mini.pick、vim-fugitive。
M.on_keys = function(name, keys, mode, fn, action, opts)
mode = mode or "n"
opts = opts or {}

View File

@ -1,126 +1,207 @@
-- =============================================================================
-- LSP、格式化与诊断配置 (lua/lsp.lua)
-- =============================================================================
-- 本文件配置 Neovim 的 LSP 客户端、代码格式化conform.nvim和诊断导航。
--
-- 加载方式:
-- 由 pack.lua 通过 lazy.on_event("lsp", "VimEnter", "*", ...) 延迟加载,
-- 在 VimEnter 事件触发后初始化,避免阻塞 startup。
--
-- 依赖加载顺序:
-- 1. conform.nvimBufWritePre 时按需 packadd 加载)
-- 2. nvim-lspconfig直接 packadd
-- 3. mason.nvim直接 setup
-- =============================================================================
local lazy = require("lazy")
-- conform.nvim - BufWritePre 懒加载
local function setup_conform()
vim.cmd.packadd("conform.nvim")
local function biome_or_prettier()
if vim.fs.find({ "biome.json", "biome.jsonc" }, { upward = true, stop = vim.uv.os_homedir() })[1] then
return { "biome-check", "biome", stop_after_first = true }
end
return { "prettier" }
end
-- ---------------------------------------------------------------------------
-- conform.nvim — 代码格式化BufWritePre 懒加载)
-- ---------------------------------------------------------------------------
-- conform.nvim 是一个轻量级格式化器包装器,支持保存时自动格式化。
-- 由于格式化器通常不是立即需要的,通过 lazy.on_event 在 BufWritePre 时
-- 才进行 packadd 和 setup。
require("conform").setup({
formatters_by_ft = {
lua = { "stylua" },
go = { "gofumpt", "goimports" },
javascript = biome_or_prettier,
typescript = biome_or_prettier,
javascriptreact = biome_or_prettier,
typescriptreact = biome_or_prettier,
json = biome_or_prettier,
css = { "prettier" },
html = { "prettier" },
markdown = { "prettier" },
toml = { "taplo" },
},
format_on_save = function(bufnr)
if vim.b[bufnr].autoformat == false or vim.g.autoformat == false then
return nil
end
return { timeout_ms = 500, lsp_fallback = true }
end,
})
local function setup_conform()
-- 通过 packadd 加载 conform.nvim因为 vim.pack.add 时设置了 load = false
vim.cmd.packadd("conform.nvim")
-- 动态判断使用 Biome 还是 Prettier。
-- 从当前文件向上搜索 biome.json / biome.jsonc如果找到则使用 Biome
-- 否则回退到 Prettier。stop 参数限制搜索到用户主目录,避免向上搜索到根目录。
local function biome_or_prettier()
if vim.fs.find({ "biome.json", "biome.jsonc" }, { upward = true, stop = vim.uv.os_homedir() })[1] then
return { "biome-check", "biome", stop_after_first = true }
end
return { "prettier" }
end
require("conform").setup({
-- 按文件类型配置格式化器
formatters_by_ft = {
lua = { "stylua" }, -- Lua 格式化
go = { "gofumpt", "goimports" }, -- Go先 gofumpt 格式化,再 goimports 整理导入
javascript = biome_or_prettier, -- JSBiome 或 Prettier
typescript = biome_or_prettier, -- TSBiome 或 Prettier
javascriptreact = biome_or_prettier, -- JSXBiome 或 Prettier
typescriptreact = biome_or_prettier, -- TSXBiome 或 Prettier
json = biome_or_prettier, -- JSONBiome 或 Prettier
css = { "prettier" }, -- CSSPrettier
html = { "prettier" }, -- HTMLPrettier
markdown = { "prettier" }, -- MarkdownPrettier
toml = { "taplo" }, -- TOMLtaplo
},
-- 保存时自动格式化回调
-- 可通过 vim.b[bufnr].autoformat = false 关闭当前 buffer 的自动格式化
-- 或通过 vim.g.autoformat = false 全局关闭
format_on_save = function(bufnr)
if vim.b[bufnr].autoformat == false or vim.g.autoformat == false then
return nil -- 返回 nil 表示不格式化
end
return { timeout_ms = 500, lsp_fallback = true }
end,
})
end
-- BufWritePre 时触发 conform 加载。
-- 注意conform 的 setup 内部会注册自己的 BufWritePre autocmd
-- 但新创建的 autocmd 不会在同一次事件中触发。
-- 因此这里手动调用 format 补偿第一次保存(确保首次保存也能格式化)。
lazy.on_event("conform", "BufWritePre", "*", function()
setup_conform()
-- conform 的 setup 注册了 BufWritePre但新 autocmd 不会在同一次事件中触发
-- 所以这里手动调用 format 补偿第一次保存
local bufnr = vim.api.nvim_get_current_buf()
if vim.b[bufnr].autoformat ~= false and vim.g.autoformat ~= false then
require("conform").format({ bufnr = bufnr, timeout_ms = 500, lsp_fallback = true })
end
setup_conform()
local bufnr = vim.api.nvim_get_current_buf()
if vim.b[bufnr].autoformat ~= false and vim.g.autoformat ~= false then
require("conform").format({ bufnr = bufnr, timeout_ms = 500, lsp_fallback = true })
end
end)
-- mason + LSP 配置
-- ---------------------------------------------------------------------------
-- Mason + LSP 配置
-- ---------------------------------------------------------------------------
-- Mason 是 LSP 服务器、DAP 适配器和格式化工具的安装管理器。
-- nvim-lspconfig 提供常见 LSP 服务器的预设配置。
vim.cmd.packadd("nvim-lspconfig")
require("mason").setup()
-- 构建 LSP 客户端 capabilities能力声明告知服务器客户端支持的功能。
-- 先获取 Neovim 默认 capabilities再与 mini.completion 的 LSP 补全能力合并。
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities = vim.tbl_deep_extend("force", capabilities, require("mini.completion").get_lsp_capabilities())
-- 为所有 LSP 服务器设置默认 capabilities
vim.lsp.config("*", { capabilities = capabilities })
-- Lua 语言服务器特殊配置:将 "vim" 声明为全局变量,避免 "Undefined global" 诊断
vim.lsp.config("lua_ls", {
settings = {
Lua = {
diagnostics = { globals = { "vim" } },
},
},
settings = {
Lua = {
diagnostics = { globals = { "vim" } },
},
},
})
-- Installed
-- ◍ biome
-- ◍ css-lsp
-- ◍ gofumpt
-- ◍ goimports
-- ◍ golangci-lint
-- ◍ gopls
-- ◍ html-lsp
-- ◍ kotlin-lsp
-- ◍ lua-language-server
-- ◍ prettier
-- ◍ rust-analyzer
-- ◍ stylua
-- ◍ svelte-language-server
-- ◍ vtsls
-- ---------------------------------------------------------------------------
-- 已安装的 Mason 工具清单(通过 :Mason 查看)
-- ---------------------------------------------------------------------------
-- 以下是通过 Mason 安装的 LSP 服务器和工具:
-- ◍ biome - JS/TS/JSON 格式化和 lint
-- ◍ css-lsp - CSS 语言服务器
-- ◍ gofumpt - Go 格式化stricter than gofmt
-- ◍ goimports - Go 导入整理
-- ◍ golangci-lint - Go linter
-- ◍ gopls - Go 语言服务器
-- ◍ html-lsp - HTML 语言服务器
-- ◍ kotlin-lsp - Kotlin 语言服务器
-- ◍ lua-language-server- Lua 语言服务器
-- ◍ prettier - 通用代码格式化器
-- ◍ rust-analyzer - Rust 语言服务器
-- ◍ stylua - Lua 格式化器
-- ◍ svelte-language-server - Svelte 语言服务器
-- ◍ taplo - TOML 工具
-- ◍ vtsls - TypeScript 语言服务器VS Code 的 TS 服务端移植)
-- 启用指定的 LSP 服务器
vim.lsp.enable({
"html",
"cssls",
"gopls",
"vtsls",
"rust_analyzer",
"lua_ls",
"taplo",
"svelte",
"dartls",
"kotlin_lsp",
"html", -- HTML 语言支持
"cssls", -- CSS/SCSS/Less 语言支持
"gopls", -- Go 语言支持
"vtsls", -- TypeScript/JavaScript 支持(推荐替代 tsserver
"rust_analyzer", -- Rust 语言支持
"lua_ls", -- Lua 语言支持
"taplo", -- TOML 支持
"svelte", -- Svelte 框架支持
"dartls", -- Dart/Flutter 支持
"kotlin_lsp", -- Kotlin 支持
})
-- LSP keymaps用 function 包装延迟 vim.lsp/vim.diagnostic 模块加载)
vim.keymap.set("n", "gd", function()
vim.lsp.buf.definition()
end, { desc = "Go to definition" })
vim.keymap.set("n", "gh", function()
vim.lsp.buf.hover()
end, { desc = "Hover" })
vim.keymap.set("n", "<leader>fm", function()
lazy.load("conform", setup_conform)
require("conform").format({ lsp_fallback = true })
end, { desc = "Format buffer" })
vim.keymap.set("n", "df", function()
vim.diagnostic.open_float()
end, { desc = "Show line diagnostics" })
vim.keymap.set("n", "<leader>ca", function()
vim.lsp.buf.code_action()
end, { desc = "Code action" })
-- ---------------------------------------------------------------------------
-- LSP 键位映射
-- ---------------------------------------------------------------------------
-- 使用函数包装器包裹 LSP 调用,延迟加载 vim.lsp / vim.diagnostic 模块,
-- 避免在启动时预加载这些重型模块。
-- gd - Go to Definition跳转到定义位置
vim.keymap.set("n", "gd", function()
vim.lsp.buf.definition()
end, { desc = "跳转到定义" })
-- gh - Hover显示光标下符号的文档悬浮窗
vim.keymap.set("n", "gh", function()
vim.lsp.buf.hover()
end, { desc = "悬停查看文档" })
-- <leader>fm - 手动格式化当前 buffer
vim.keymap.set("n", "<leader>fm", function()
lazy.load("conform", setup_conform)
require("conform").format({ lsp_fallback = true })
end, { desc = "格式化当前 Buffer" })
-- df - 显示当前行的诊断浮动窗口
vim.keymap.set("n", "df", function()
vim.diagnostic.open_float()
end, { desc = "显示行诊断" })
-- <leader>ca - Code Action显示可用的代码操作如自动修复、重构
vim.keymap.set("n", "<leader>ca", function()
vim.lsp.buf.code_action()
end, { desc = "代码操作" })
-- ---------------------------------------------------------------------------
-- 诊断导航
-- ---------------------------------------------------------------------------
-- 辅助函数:创建诊断跳转的闭包。
-- 参数:
-- next - true 表示向后跳false 表示向前跳
-- severity - 可选的严重性过滤("ERROR", "WARN", "INFO", "HINT"
--
-- vim.v.count1 是计数前缀的默认值(无数字时为 1
-- 支持 3]d 跳转到第 3 个诊断。
local diagnostic_goto = function(next, severity)
return function()
vim.diagnostic.jump({
count = (next and 1 or -1) * vim.v.count1,
severity = severity and vim.diagnostic.severity[severity] or nil,
float = true,
})
end
return function()
vim.diagnostic.jump({
count = (next and 1 or -1) * vim.v.count1,
severity = severity and vim.diagnostic.severity[severity] or nil,
float = true, -- 跳转时显示诊断浮动窗口
})
end
end
vim.keymap.set("n", "]d", diagnostic_goto(true), { desc = "Next Diagnostic" })
vim.keymap.set("n", "[d", diagnostic_goto(false), { desc = "Prev Diagnostic" })
vim.keymap.set("n", "]e", diagnostic_goto(true, "ERROR"), { desc = "Next Error" })
vim.keymap.set("n", "[e", diagnostic_goto(false, "ERROR"), { desc = "Prev Error" })
vim.keymap.set("n", "]w", diagnostic_goto(true, "WARN"), { desc = "Next Warning" })
vim.keymap.set("n", "[w", diagnostic_goto(false, "WARN"), { desc = "Prev Warning" })
-- ]d / [d - 下一个 / 上一个诊断(所有级别)
vim.keymap.set("n", "]d", diagnostic_goto(true), { desc = "下一个诊断" })
vim.keymap.set("n", "[d", diagnostic_goto(false), { desc = "上一个诊断" })
-- ]e / [e - 下一个 / 上一个错误
vim.keymap.set("n", "]e", diagnostic_goto(true, "ERROR"), { desc = "下一个错误" })
vim.keymap.set("n", "[e", diagnostic_goto(false, "ERROR"), { desc = "上一个错误" })
-- ]w / [w - 下一个 / 上一个警告
vim.keymap.set("n", "]w", diagnostic_goto(true, "WARN"), { desc = "下一个警告" })
vim.keymap.set("n", "[w", diagnostic_goto(false, "WARN"), { desc = "上一个警告" })
-- ---------------------------------------------------------------------------
-- 诊断显示配置
-- ---------------------------------------------------------------------------
-- 在代码行右侧显示诊断文本virtual text
vim.diagnostic.config({ virtual_text = true })

View File

@ -1,53 +1,151 @@
-- =============================================================================
-- 全局选项配置 (lua/options.lua)
-- =============================================================================
-- 本文件集中设置所有影响编辑器行为的全局选项。
-- 选项分为以下几类:
-- 1. 界面显示(行号、光标、颜色)
-- 2. 缩进与格式化tab、空格、智能缩进
-- 3. 搜索行为(大小写敏感)
-- 4. 窗口与分割
-- 5. 文件持久化swap、backup、undo
-- 6. 补全与消息
-- 7. 剪贴板(延迟初始化,避免启动阻塞)
-- 8. 折叠与 diff 高亮
-- =============================================================================
-- 禁用 netrw 横幅netrw 本身已通过 loaded_netrw 禁用,此选项作为保险)
vim.g.netrw_banner = 0
vim.opt.nu = true
vim.opt.relativenumber = true
vim.opt.cursorline = true
vim.opt.cursorlineopt = "both"
vim.opt.autoread = true
-- ---------------------------------------------------------------------------
-- 1. 界面显示
-- ---------------------------------------------------------------------------
vim.opt.tabstop = 4
vim.opt.softtabstop = 4
vim.opt.shiftwidth = 4
vim.opt.expandtab = true
vim.opt.nu = true -- 显示绝对行号
vim.opt.relativenumber = true -- 显示相对行号(便于配合数字 + j/k 跳转)
vim.opt.cursorline = true -- 高亮当前行
vim.opt.cursorlineopt = "both" -- 高亮当前行的行号和文本行number + line
vim.opt.autoread = true -- 当文件被外部修改时自动重新读取
vim.opt.smartindent = true
vim.opt.inccommand = "split"
-- ---------------------------------------------------------------------------
-- 2. 缩进与格式化
-- ---------------------------------------------------------------------------
-- 使用 4 空格缩进,适用于大多数语言。
-- 特定语言的缩进设置(如 JS/TS 的 2 空格)应在 ftplugin 中覆盖。
vim.opt.splitbelow = true
vim.opt.splitright = true
vim.opt.tabstop = 4 -- Tab 键显示的宽度(字符数)
vim.opt.softtabstop = 4 -- 插入模式下 Tab/BS 的行为宽度
vim.opt.shiftwidth = 4 -- 自动缩进和 >/< 操作的宽度
vim.opt.expandtab = true -- 将 Tab 键转换为空格
vim.opt.ignorecase = true
vim.opt.smartcase = true
vim.opt.laststatus = 3
vim.opt.smartindent = true -- 基于语法的智能缩进C-style 语言效果最佳)
vim.opt.inccommand = "split" -- 实时预览替换效果(:s 命令在分屏中显示预览)
vim.opt.swapfile = false
vim.opt.backup = false
vim.opt.undodir = vim.fn.stdpath("data") .. "/undodir"
vim.opt.undofile = true
-- ---------------------------------------------------------------------------
-- 3. 窗口与分割
-- ---------------------------------------------------------------------------
vim.opt.splitbelow = true -- 水平分割时新窗口在下方
vim.opt.splitright = true -- 垂直分割时新窗口在右侧
-- ---------------------------------------------------------------------------
-- 4. 搜索行为
-- ---------------------------------------------------------------------------
vim.opt.ignorecase = true -- 搜索时忽略大小写
vim.opt.smartcase = true -- 如果搜索包含大写字母,则区分大小写
-- 与 ignorecase 配合:全小写时忽略大小写,
-- 包含大写时精确匹配
-- ---------------------------------------------------------------------------
-- 5. 状态栏
-- ---------------------------------------------------------------------------
vim.opt.laststatus = 3 -- 全局状态栏(所有窗口共享一个状态栏)
-- 值为 2 时每个窗口有独立状态栏
-- ---------------------------------------------------------------------------
-- 6. 文件持久化
-- ---------------------------------------------------------------------------
-- 禁用 swap 和 backup 文件,使用 undofile 实现跨会话的撤销历史。
vim.opt.swapfile = false -- 禁用交换文件(.swp
vim.opt.backup = false -- 禁用备份文件(~ 后缀)
vim.opt.undodir = vim.fn.stdpath("data") .. "/undodir" -- 撤销历史存放目录
vim.opt.undofile = true -- 启用持久化撤销(关闭文件后仍能撤销)
-- ---------------------------------------------------------------------------
-- 7. 补全与消息
-- ---------------------------------------------------------------------------
-- 补全选项:
-- menuone - 即使只有一个匹配项也显示菜单
-- noselect - 不自动选择第一项(需手动选择)
-- fuzzy - 启用模糊匹配Neovim 0.11+
-- nosort - 保持原始顺序,不按字母排序
vim.opt.completeopt = "menuone,noselect,fuzzy,nosort"
-- shortmess: 缩短各种消息提示
-- c - 补全相关消息的缩短(如 "match 1 of 5" → "1/5"
vim.opt.shortmess:append("c")
-- 延迟初始化 clipboard避免启动时 provider 检测阻塞SSH 环境尤其明显)
-- ---------------------------------------------------------------------------
-- 8. 剪贴板(延迟初始化)
-- ---------------------------------------------------------------------------
-- 剪贴板集成unnamedplus会在启动时检测外部剪贴板提供者如 xclip、wl-copy
-- 在 SSH 远程环境中,此检测可能阻塞数秒。
-- 通过 vim.schedule() 将剪贴板设置推迟到启动事件循环之后,
-- 避免阻塞 startup 的关键路径。
vim.schedule(function()
vim.opt.clipboard:append("unnamedplus")
vim.opt.clipboard:append("unnamedplus")
end)
-- ---------------------------------------------------------------------------
-- 9. 其他杂项
-- ---------------------------------------------------------------------------
-- 将 @ 和 - 视为文件名的一部分(用于 gf/gx 等命令)
vim.opt.isfname:append("@-@")
-- 保持光标与屏幕边缘的最小距离(行数),确保上下文可见
vim.opt.scrolloff = 8
-- 禁用 colorcolumn右侧参考线
vim.opt.colorcolumn = "0"
-- 始终显示符号列用于诊断、git diff 等标记),避免文本左右跳动
vim.opt.signcolumn = "yes"
-- 启用真彩色支持24-bit RGBcolorscheme 需要此选项才能正确渲染
vim.opt.termguicolors = true
vim.api.nvim_set_hl(0, "MiniDiffSignAdd", { link = "DiffAdd" })
vim.api.nvim_set_hl(0, "MiniDiffSignChange", { link = "DiffChange" })
vim.api.nvim_set_hl(0, "MiniDiffSignDelete", { link = "DiffDelete" })
-- ---------------------------------------------------------------------------
-- 10. 高亮组链接
-- ---------------------------------------------------------------------------
-- 将 mini.diff 的符号高亮组链接到标准 diff 高亮组,
-- 使 git diff 的添加/修改/删除标记使用 colorscheme 定义的配色。
vim.api.nvim_set_hl(0, "MiniDiffSignAdd", { link = "DiffAdd" }) -- 添加的行
vim.api.nvim_set_hl(0, "MiniDiffSignChange", { link = "DiffChange" }) -- 修改的行
vim.api.nvim_set_hl(0, "MiniDiffSignDelete", { link = "DiffDelete" }) -- 删除的行
-- ---------------------------------------------------------------------------
-- 11. Yank 高亮
-- ---------------------------------------------------------------------------
-- 复制文本时短暂高亮被复制的区域,提供视觉反馈。
-- vim.hl.on_yank() 是 Neovim 0.11+ 的内置函数,
-- 旧版使用 vim.highlight.on_yank()。
vim.api.nvim_create_autocmd("TextYankPost", {
desc = "Highlight when yanking (copying) text",
callback = function()
vim.hl.on_yank()
end,
desc = "复制文本时高亮被复制的区域",
callback = function()
vim.hl.on_yank()
end,
})
vim.opt.foldlevel = 99 -- 打开文件时默认展开所有折叠
-- ---------------------------------------------------------------------------
-- 12. 折叠设置
-- ---------------------------------------------------------------------------
-- foldlevel = 99 表示默认展开所有折叠。
-- 实际的 foldmethod 和 foldexpr 在 autocmds.lua 中通过 BufEnter 延迟设置,
-- 避免启动时加载 treesitter 模块。
vim.opt.foldlevel = 99

View File

@ -1,231 +1,376 @@
-- =============================================================================
-- 插件管理与懒加载配置 (lua/pack.lua)
-- =============================================================================
-- 本文件使用 Neovim 0.12+ 内置的 vim.pack 管理插件,
-- 并结合自定义懒加载框架 (lua/lazy.lua) 延迟初始化重型插件。
--
-- 插件列表:
-- mini.nvim - 单体插件集starter、pick、extra、files、icons、
-- notify、cmdline、completion、snippets、diff、surround
-- friendly-snippets - 社区代码片段集合
-- nvim-treesitter - 语法树解析与高亮
-- nvim-lspconfig - LSP 客户端配置
-- mason.nvim - LSP/DAP/格式化工具安装管理器
-- conform.nvim - 代码格式化(保存时自动格式化)
-- vim-fugitive - Git 集成
-- grug-far.nvim - 搜索与替换
--
-- 懒加载策略:
-- InsertEnter → completion, snippets
-- BufReadPost → diff, surround
-- VimEnter → treesitter, lsp
-- 按键触发 → pick, fugitive, files, grugfar
-- BufWritePre → conform
-- =============================================================================
local lazy = require("lazy")
local pick = require("pick")
require("git")
-- 缓存启动耗时。pack.lua 在 init.lua 末尾加载,此时核心配置已全部就绪,
-- 计算出的时间就是真正的启动耗时。避免 starter 页脚在后续渲染时数值持续增长。
local startup_ms = ((vim.uv or vim.loop).hrtime() - _G.nvim_start_time) / 1e6
-- ---------------------------------------------------------------------------
-- 插件安装声明
-- ---------------------------------------------------------------------------
-- vim.pack.add(urls, { load = false }) 将插件下载到 pack 目录,
-- 但不自动加载load = false。后续通过 packadd 或 require 按需加载。
-- 所有插件的状态锁定在 nvim-pack-lock.json 中。
vim.pack.add({
"https://github.com/nvim-mini/mini.nvim",
"https://github.com/rafamadriz/friendly-snippets",
{ src = "https://github.com/nvim-treesitter/nvim-treesitter", branch = "main" },
"https://github.com/neovim/nvim-lspconfig",
"https://github.com/mason-org/mason.nvim",
"https://github.com/stevearc/conform.nvim",
"https://github.com/tpope/vim-fugitive",
"https://github.com/MagicDuck/grug-far.nvim",
"https://github.com/nvim-mini/mini.nvim", -- 核心 UI/功能插件集
"https://github.com/rafamadriz/friendly-snippets", -- 代码片段库
{ src = "https://github.com/nvim-treesitter/nvim-treesitter", branch = "main" }, -- 语法树
"https://github.com/neovim/nvim-lspconfig", -- LSP 配置
"https://github.com/mason-org/mason.nvim", -- 工具安装器
"https://github.com/stevearc/conform.nvim", -- 格式化
"https://github.com/tpope/vim-fugitive", -- Git 集成
"https://github.com/MagicDuck/grug-far.nvim", -- 搜索替换
}, { load = false })
-- mini.starter 启动页
-- ---------------------------------------------------------------------------
-- mini.starter — 启动页
-- ---------------------------------------------------------------------------
-- 每次打开 Neovim无文件参数时显示的欢迎页面
-- 展示 Neovim ASCII Logo 和启动耗时统计。
local starter = require("mini.starter")
-- 统计已安装插件数量(用于页脚显示 "Loaded X/Y plugins"
-- 遍历 stdpath("data")/site/pack/core/opt/ 目录下的所有子目录
local function get_total_plugins()
local pack_dir = vim.fn.stdpath("data") .. "/site/pack/core/opt"
local paths = vim.fn.glob(pack_dir .. "/*", false, true)
local count = 0
for _, path in ipairs(paths) do
if vim.fn.isdirectory(path) == 1 then
count = count + 1
end
end
return count
local pack_dir = vim.fn.stdpath("data") .. "/site/pack/core/opt"
local paths = vim.fn.glob(pack_dir .. "/*", false, true)
local count = 0
for _, path in ipairs(paths) do
if vim.fn.isdirectory(path) == 1 then
count = count + 1
end
end
return count
end
-- Neovim ASCII Art Logo使用 Unicode 方块字符绘制)
local header_lines = {
"",
"███╗ ██╗███████╗ ██████╗ ██╗ ██╗██╗███╗ ███╗",
"████╗ ██║██╔════╝██╔═══██╗██║ ██║██║████╗ ████║",
"██╔██╗ ██║█████╗ ██║ ██║██║ ██║██║██╔████╔██║",
"██║╚██╗██║██╔══╝ ██║ ██║╚██╗ ██╔╝██║██║╚██╔╝██║",
"██║ ╚████║███████╗╚██████╔╝ ╚████╔╝ ██║██║ ╚═╝ ██║",
"╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═══╝ ╚═╝╚═╝ ╚═╝",
"",
"",
"███╗ ██╗███████╗ ██████╗ ██╗ ██╗██╗███╗ ███╗",
"████╗ ██║██╔════╝██╔═══██╗██║ ██║██║████╗ ████║",
"██╔██╗ ██║█████╗ ██║ ██║██║ ██║██║██╔████╔██║",
"██║╚██╗██║██╔══╝ ██║ ██║╚██╗ ██╔╝██║██║╚██╔╝██║",
"██║ ╚████║███████╗╚██████╔╝ ╚████╔╝ ██║██║ ╚═╝ ██║",
"╚═╝ ╚═══╝╚══════╝ ╚═════╝ ╚═══╝ ╚═╝╚═╝ ╚═╝",
"",
}
-- 计算 Logo 的最大显示宽度,用于页脚居中计算
local max_header_width = 0
for _, line in ipairs(header_lines) do
max_header_width = math.max(max_header_width, vim.fn.strdisplaywidth(line))
max_header_width = math.max(max_header_width, vim.fn.strdisplaywidth(line))
end
starter.setup({
autoopen = true,
evaluate_single = false,
items = { { name = " ", action = "", section = "" } },
header = table.concat(header_lines, "\n"),
footer = function()
local uv = vim.uv or vim.loop
local ms = (uv:hrtime() - _G.nvim_start_time) / 1e6
local loaded = vim.tbl_count(require("lazy")._loaded)
local total = get_total_plugins()
local text = string.format(" Loaded %d/%d plugins in %.0f ms", loaded, total, ms)
local text_width = vim.fn.strdisplaywidth(text)
local pad = math.floor((max_header_width - text_width) / 2)
return string.rep(" ", pad) .. text
end,
content_hooks = {
starter.gen_hook.aligning("center", "center"),
},
autoopen = true, -- 无参数启动时自动打开
evaluate_single = false, -- 只有一个选项时不自动执行
items = { { name = " ", action = "", section = "" } }, -- 空选项(仅展示页眉页脚)
header = table.concat(header_lines, "\n"), -- Logo 文本
-- 页脚函数:显示启动耗时和插件加载统计
footer = function()
-- 已加载的懒加载模块数(动态统计)
local loaded = vim.tbl_count(require("lazy")._loaded)
local total = get_total_plugins()
-- startup_ms 在 pack.lua 加载时一次性计算,避免后续渲染时数值变化
local text = string.format(" Loaded %d/%d plugins in %.0f ms", loaded, total, startup_ms)
local text_width = vim.fn.strdisplaywidth(text)
-- 计算左填充空格数以实现居中
local pad = math.floor((max_header_width - text_width) / 2)
return string.rep(" ", pad) .. text
end,
-- 内容钩子:水平和垂直居中
content_hooks = {
starter.gen_hook.aligning("center", "center"),
},
})
-- mini.files - 按键触发
lazy.on_keys("files", "<leader>-", "n", function()
require("mini.files").setup({
mappings = {
go_in = "<CR>",
go_in_plus = "L",
go_out = "_",
go_out_plus = "H",
},
})
end, function()
require("mini.files").open()
end, { desc = "Toggle mini file explorer" })
-- ---------------------------------------------------------------------------
-- mini.files — 文件浏览器(按键触发懒加载)
-- ---------------------------------------------------------------------------
-- 提供悬浮文件浏览器,支持目录导航和文件操作。
lazy.on_keys("files", "-", "n", function()
require("mini.files").setup({
mappings = {
go_in = "<CR>",
go_in_plus = "L",
go_out = "_",
go_out_plus = "H",
},
})
end, function()
local MiniFiles = require("mini.files")
MiniFiles.open(vim.api.nvim_buf_get_name(0), false)
MiniFiles.reveal_cwd()
end, { desc = "Toggle into currently opened file" })
-- mini.icons - 首次需要时加载VimEnter 后延迟)
vim.api.nvim_create_autocmd("VimEnter", {
once = true,
callback = function()
require("mini.icons").setup()
lazy._loaded["icons"] = true
end,
})
-- mini.notify - 首次 vim.notify 调用时加载
vim.notify = function(msg, level, opts)
require("mini.notify").setup({
content = {
format = function(notif)
return notif.msg
end,
},
})
vim.notify = require("mini.notify").make_notify()
return vim.notify(msg, level, opts)
-- mini.files setup 配置(复用)
local function setup_mini_files()
require("mini.files").setup({
mappings = {
go_in = "<CR>", -- 回车进入目录或打开文件
go_in_plus = "L", -- L 进入并同步光标
go_out = "_", -- _ 返回上级目录
go_out_plus = "H", -- H 返回上级并同步光标
},
})
end
-- mini.cmdline - 首次按 : 时加载
-- <leader>- :在当前工作目录打开文件浏览器
lazy.on_keys("files", "<leader>-", "n", setup_mini_files, function()
require("mini.files").open()
end, { desc = "打开文件浏览器(工作目录)" })
-- - :在当前文件所在目录打开文件浏览器
lazy.on_keys("files", "-", "n", setup_mini_files, function()
local MiniFiles = require("mini.files")
-- 打开当前文件所在目录,并定位到当前文件
MiniFiles.open(vim.api.nvim_buf_get_name(0), false)
MiniFiles.reveal_cwd()
end, { desc = "打开文件浏览器(当前文件目录)" })
-- 在启动页mini.starter中也绑定 - / <leader>-
-- 因为 starter buffer 可能拦截全局映射
vim.api.nvim_create_autocmd("User", {
pattern = "MiniStarterOpened",
callback = function(args)
vim.keymap.set("n", "<leader>-", function()
require("lazy").load("files", setup_mini_files)
require("mini.files").open()
end, { buffer = args.buf, desc = "打开文件浏览器(工作目录)" })
vim.keymap.set("n", "-", function()
require("lazy").load("files", setup_mini_files)
local MiniFiles = require("mini.files")
-- starter 无当前文件,打开工作目录
MiniFiles.open(vim.fn.getcwd())
end, { buffer = args.buf, desc = "打开文件浏览器(工作目录)" })
end,
})
-- ---------------------------------------------------------------------------
-- mini.icons — 图标支持VimEnter 后延迟加载)
-- ---------------------------------------------------------------------------
-- 提供文件类型图标,供 mini.pick、mini.files 等使用。
-- 在 VimEnter 后 setup确保 UI 已初始化。
vim.api.nvim_create_autocmd("VimEnter", {
once = true,
callback = function()
require("mini.icons").setup()
lazy._loaded["icons"] = true
end,
})
-- ---------------------------------------------------------------------------
-- mini.notify — 通知消息
-- ---------------------------------------------------------------------------
-- 替换默认的 vim.notify提供美观的浮动通知窗口。
-- 直接初始化(轻量,不阻塞启动)。
require("mini.notify").setup({
content = {
-- 简化格式:只显示消息内容,不附加时间戳等元信息
format = function(notif)
return notif.msg
end,
},
})
-- 将 vim.notify 重定向到 mini.notify
vim.notify = require("mini.notify").make_notify()
-- ---------------------------------------------------------------------------
-- mini.cmdline — 增强命令行
-- ---------------------------------------------------------------------------
-- 提供美观的命令行界面,首次按 : 时加载。
-- 使用 expr 映射:返回 ":" 让 Vim 继续处理命令行输入。
vim.keymap.set("n", ":", function()
vim.keymap.del("n", ":")
require("mini.cmdline").setup({
autocorrect = { enable = false },
})
return ":"
-- 首次按下 : 时删除此映射,避免后续重复触发 setup
vim.keymap.del("n", ":")
require("mini.cmdline").setup({
autocorrect = { enable = false }, -- 禁用自动纠正
})
return ":"
end, { expr = true, noremap = true })
-- 诊断位置列表Quickfix 的本地化版本)
vim.keymap.set("n", "<leader>ds", function()
vim.diagnostic.setloclist()
end, { desc = "LSP diagnostic loclist" })
vim.diagnostic.setloclist()
end, { desc = "诊断位置列表" })
-- finders
-- ---------------------------------------------------------------------------
-- mini.pick / mini.extra — 文件查找器(按键触发懒加载)
-- ---------------------------------------------------------------------------
-- 所有 pick 相关映射共用 pick.load_pick() 初始化,确保 mini.pick 和 mini.extra
-- 只在首次使用时 setup 一次。
-- <leader>ff :文件查找
lazy.on_keys("pick", "<leader>ff", "n", pick.load_pick, function()
require("mini.pick").builtin.files()
end, { desc = "Mini File Picker" })
require("mini.pick").builtin.files()
end, { desc = "文件查找" })
-- <leader>fw :实时 grep在项目中搜索文本
lazy.on_keys("pick", "<leader>fw", "n", pick.load_pick, function()
require("mini.pick").builtin.grep_live()
end, { desc = "Live grep in project" })
require("mini.pick").builtin.grep_live()
end, { desc = "项目内实时搜索" })
-- <leader>sk :键位映射搜索
lazy.on_keys("pick", "<leader>sk", "n", pick.load_pick, function()
require("mini.extra").pickers.keymaps()
end, { desc = "Search keymaps" })
require("mini.extra").pickers.keymaps()
end, { desc = "搜索键位映射" })
-- <leader>fa :查找所有文件(含隐藏和忽略的文件)
lazy.on_keys("pick", "<leader>fa", "n", pick.load_pick, function()
require("mini.pick").builtin.cli({ command = { "rg", "--files", "--hidden", "--no-ignore", "--color=never" } })
end, { desc = "Find all files (including hidden/ignored)" })
require("mini.pick").builtin.cli({
command = { "rg", "--files", "--hidden", "--no-ignore", "--color=never" }
})
end, { desc = "查找所有文件(含隐藏/忽略)" })
-- <leader>fh :帮助标签搜索
lazy.on_keys("pick", "<leader>fh", "n", pick.load_pick, function()
require("mini.pick").builtin.help()
end, { desc = "Search help tags" })
require("mini.pick").builtin.help()
end, { desc = "搜索帮助标签" })
-- <leader>fo :最近打开的文件
lazy.on_keys("pick", "<leader>fo", "n", pick.load_pick, function()
require("mini.extra").pickers.oldfiles()
end, { desc = "Search oldfiles" })
require("mini.extra").pickers.oldfiles()
end, { desc = "最近打开的文件" })
-- <leader>fz :当前缓冲区行内搜索
lazy.on_keys("pick", "<leader>fz", "n", pick.load_pick, function()
require("mini.extra").pickers.buf_lines({ scope = "current" })
end, { desc = "Search in current buffer" })
require("mini.extra").pickers.buf_lines({ scope = "current" })
end, { desc = "当前缓冲区行内搜索" })
-- ---------------------------------------------------------------------------
-- vim-fugitive — Git 集成(按键触发懒加载)
-- ---------------------------------------------------------------------------
-- tpope 的 Git 包装插件,提供 :Git 等命令。
-- vim-fugitive - 按键触发
local function load_fugitive()
vim.cmd.packadd("vim-fugitive")
vim.cmd.packadd("vim-fugitive")
end
-- <leader>gg :在新标签页中打开 Fugitive 全屏
lazy.on_keys("fugitive", "<leader>gg", "n", load_fugitive, function()
vim.cmd("tabnew | Git | only")
end, { desc = "Fugitive Full Page New Tab" })
vim.cmd("tabnew | Git | only")
end, { desc = "Fugitive 全屏新标签" })
-- <leader>gd Git diff 垂直分割
lazy.on_keys("fugitive", "<leader>gd", "n", load_fugitive, function()
vim.cmd("Gvdiffsplit")
end, { desc = "Git diff split" })
vim.cmd("Gvdiffsplit")
end, { desc = "Git diff 分割" })
-- ---------------------------------------------------------------------------
-- mini.completion — 自动补全InsertEnter 懒加载)
-- ---------------------------------------------------------------------------
-- 轻量级补全引擎,支持 LSP 补全、buffer 单词、路径补全。
-- mini.completion - InsertEnter
lazy.on_event("completion", "InsertEnter", "*", function()
require("mini.completion").setup({
lsp_completion = {
auto_setup = true,
},
})
require("mini.completion").setup({
lsp_completion = {
auto_setup = true, -- 自动配置 LSP 补全源
},
})
end)
-- mini.snippets - InsertEnter
-- ---------------------------------------------------------------------------
-- mini.snippets — 代码片段InsertEnter 懒加载)
-- ---------------------------------------------------------------------------
-- 代码片段引擎,支持 LSP 片段扩展。
lazy.on_event("snippets", "InsertEnter", "*", function()
local MiniSnippets = require("mini.snippets")
MiniSnippets.setup({
snippets = {
MiniSnippets.gen_loader.from_lang(),
},
})
MiniSnippets.start_lsp_server({ match = false })
local MiniSnippets = require("mini.snippets")
MiniSnippets.setup({
snippets = {
-- 从 friendly-snippets 加载对应语言的片段
MiniSnippets.gen_loader.from_lang(),
},
})
-- 启动 LSP 片段服务器match = false 表示不匹配时不自动触发)
MiniSnippets.start_lsp_server({ match = false })
end)
-- mini.diff - BufReadPost
-- ---------------------------------------------------------------------------
-- mini.diff — Git diff 标记BufReadPost 懒加载)
-- ---------------------------------------------------------------------------
-- 在 signcolumn 中显示当前 buffer 相对于 git HEAD 的变更标记。
lazy.on_event("diff", "BufReadPost", "*", function()
require("mini.diff").setup({
source = require("mini.diff").gen_source.git({ index = false }),
view = {
style = "sign",
signs = { add = "", change = "", delete = "" },
},
mappings = {
apply = "gs",
textobject = "",
},
})
require("mini.diff").setup({
-- 使用 git 作为 diff 源index = false 表示对比工作区 vs HEAD不含暂存区
source = require("mini.diff").gen_source.git({ index = false }),
view = {
style = "sign", -- 以符号形式显示(在 signcolumn 中)
signs = { add = "", change = "", delete = "" }, -- 统一的竖线符号
},
mappings = {
apply = "gs", -- gs 应用当前 hunk
textobject = "", -- 禁用文本对象映射
},
})
end)
-- mini.surround - BufReadPost
-- ---------------------------------------------------------------------------
-- mini.surround — 环绕文本操作BufReadPost 懒加载)
-- ---------------------------------------------------------------------------
-- 快速添加、删除、修改包围符号如括号、引号、HTML 标签等)。
lazy.on_event("surround", "BufReadPost", "*", function()
require("mini.surround").setup()
require("mini.surround").setup()
end)
-- 延迟加载 heavy 模块
-- ---------------------------------------------------------------------------
-- 重型模块延迟加载VimEnter 事件)
-- ---------------------------------------------------------------------------
-- treesitter 和 lsp 是启动时最耗时的模块,
-- 延迟到 VimEnter 事件触发后加载,让编辑器界面先渲染出来。
lazy.on_event("treesitter", "VimEnter", "*", function()
require("treesitter").setup()
end)
lazy.on_event("lsp", "VimEnter", "*", function()
require("lsp")
require("treesitter").setup()
end)
-- grug-far - <leader>sr 触发
lazy.on_event("lsp", "VimEnter", "*", function()
require("lsp")
end)
-- ---------------------------------------------------------------------------
-- grug-far.nvim — 搜索与替换(按键触发懒加载)
-- ---------------------------------------------------------------------------
-- 提供类似 VS Code 的查找替换界面,支持正则和文件过滤。
local function load_grug_far()
vim.cmd.packadd("grug-far.nvim")
require("grug-far").setup({ headerMaxWidth = 80 })
vim.cmd.packadd("grug-far.nvim")
require("grug-far").setup({ headerMaxWidth = 80 })
end
local function open_grug_far()
local grug = require("grug-far")
local ext = vim.bo.buftype == "" and vim.fn.expand("%:e")
grug.open({
transient = true,
prefills = {
filesFilter = ext and ext ~= "" and "*." .. ext or nil,
},
})
local grug = require("grug-far")
-- 根据当前文件扩展名预填充文件过滤器
local ext = vim.bo.buftype == "" and vim.fn.expand("%:e")
grug.open({
transient = true, -- 关闭后自动销毁 buffer
prefills = {
filesFilter = ext and ext ~= "" and "*." .. ext or nil,
},
})
end
lazy.on_keys("grugfar", "<leader>sr", "n", load_grug_far, open_grug_far, { desc = "Search and Replace" })
lazy.on_keys("grugfar", "<leader>sr", "x", load_grug_far, open_grug_far, { desc = "Search and Replace" })
-- Normal 模式和 Visual 模式均绑定 <leader>sr
lazy.on_keys("grugfar", "<leader>sr", "n", load_grug_far, open_grug_far, { desc = "搜索并替换" })
lazy.on_keys("grugfar", "<leader>sr", "x", load_grug_far, open_grug_far, { desc = "搜索并替换" })

View File

@ -1,83 +1,125 @@
-- =============================================================================
-- mini.pick 封装 (lua/pick.lua)
-- =============================================================================
-- 本模块封装 mini.pick 和 mini.extra 的初始化逻辑,
-- 提供统一的懒加载入口和自定义 picker。
--
-- 主要功能:
-- 1. load_pick() — 懒加载 mini.pick 和 mini.extra
-- 2. Buffer picker<leader><leader>)— 自定义 buffer 列表,支持 <C-d> 删除
-- 3. Filetype picker<leader>ft— 快速切换文件类型
-- =============================================================================
local lazy = require("lazy")
local M = {}
-- ---------------------------------------------------------------------------
-- 懒加载入口
-- ---------------------------------------------------------------------------
-- 通过 lazy.load 确保 mini.pick 和 mini.extra 只初始化一次。
-- 此函数被 pack.lua 中所有 pick 相关的 on_keys 映射共用。
M.load_pick = function()
lazy.load("pick", function()
require("mini.pick").setup()
end)
lazy.load("extra", function()
require("mini.extra").setup()
end)
lazy.load("pick", function()
require("mini.pick").setup()
end)
lazy.load("extra", function()
require("mini.extra").setup()
end)
end
-- Buffer picker (<leader><leader>) — supports <C-d> to delete buffer
-- ---------------------------------------------------------------------------
-- Buffer Picker<leader><leader>
-- ---------------------------------------------------------------------------
-- 显示当前打开的 buffer 列表,按最近使用时间排序。
-- 特殊功能:
-- - <C-d>:删除当前选中的 buffer带图标过滤
-- - 支持 mini.icons 图标显示
vim.keymap.set("n", "<leader><leader>", function()
M.load_pick()
local MiniPick = require("mini.pick")
M.load_pick()
local MiniPick = require("mini.pick")
local delete_buf = function()
local matches = MiniPick.get_picker_matches()
local item = matches and matches.current
if not item or not item.bufnr then
return
end
local bufnr = item.bufnr
-- 删除当前选中 buffer 的回调函数
local delete_buf = function()
local matches = MiniPick.get_picker_matches()
local item = matches and matches.current
if not item or not item.bufnr then
return
end
local bufnr = item.bufnr
pcall(vim.api.nvim_buf_delete, bufnr, { force = true })
-- 安全删除 bufferforce = true 跳过未保存确认)
pcall(vim.api.nvim_buf_delete, bufnr, { force = true })
if MiniPick.is_picker_active() then
local items = vim.tbl_filter(function(i)
return i.bufnr and vim.api.nvim_buf_is_valid(i.bufnr)
end, MiniPick.get_picker_items() or {})
MiniPick.set_picker_items(items)
end
end
-- 如果 picker 仍活跃,刷新列表以移除已删除的 buffer
if MiniPick.is_picker_active() then
local items = vim.tbl_filter(function(i)
return i.bufnr and vim.api.nvim_buf_is_valid(i.bufnr)
end, MiniPick.get_picker_items() or {})
MiniPick.set_picker_items(items)
end
end
local bufs = vim.tbl_filter(function(b)
return vim.bo[b.bufnr].buftype == "" and b.listed == 1
end, vim.fn.getbufinfo())
table.sort(bufs, function(a, b)
return a.lastused > b.lastused
end)
-- 获取所有普通 buffer排除特殊 buffer 如终端、quickfix
local bufs = vim.tbl_filter(function(b)
return vim.bo[b.bufnr].buftype == "" and b.listed == 1
end, vim.fn.getbufinfo())
local items = {}
for _, info in ipairs(bufs) do
local name = info.name ~= "" and vim.fn.fnamemodify(info.name, ":.") or "[No Name]"
table.insert(items, {
text = name,
bufnr = info.bufnr,
})
end
-- 按最后使用时间降序排列(最近使用的在前)
table.sort(bufs, function(a, b)
return a.lastused > b.lastused
end)
MiniPick.start({
source = {
name = "Buffers",
items = items,
show = function(buf_id, items_arr, query)
MiniPick.default_show(buf_id, items_arr, query, { show_icons = true })
end,
},
mappings = {
delete_buffer = { char = "<C-d>", func = delete_buf },
},
})
end, { desc = "Buffers" })
-- 构建 picker items
local items = {}
for _, info in ipairs(bufs) do
-- 相对路径显示(当前工作目录为基准)
local name = info.name ~= "" and vim.fn.fnamemodify(info.name, ":.") or "[No Name]"
table.insert(items, {
text = name,
bufnr = info.bufnr,
})
end
-- 启动 picker
MiniPick.start({
source = {
name = "Buffers",
items = items,
show = function(buf_id, items_arr, query)
-- 使用默认显示函数,启用图标
MiniPick.default_show(buf_id, items_arr, query, { show_icons = true })
end,
},
mappings = {
delete_buffer = { char = "<C-d>", func = delete_buf },
},
})
end, { desc = "Buffer 列表" })
-- ---------------------------------------------------------------------------
-- Filetype Picker<leader>ft
-- ---------------------------------------------------------------------------
-- 显示所有可用的文件类型列表,选择后设置当前 buffer 的 filetype。
-- 常用于打开无扩展名文件或纠正错误的文件类型检测。
-- Filetype picker (<leader>ft)
vim.keymap.set("n", "<leader>ft", function()
M.load_pick()
local MiniPick = require("mini.pick")
local filetypes = vim.fn.getcompletion("", "filetype")
MiniPick.start({
source = {
name = "Filetypes",
items = filetypes,
choose = function(item)
vim.bo.filetype = item
end,
},
})
end, { desc = "Change filetype" })
M.load_pick()
local MiniPick = require("mini.pick")
-- 获取所有可用的文件类型名称
local filetypes = vim.fn.getcompletion("", "filetype")
MiniPick.start({
source = {
name = "Filetypes",
items = filetypes,
choose = function(item)
-- 选择后将当前 buffer 的 filetype 设为选中值
vim.bo.filetype = item
end,
},
})
end, { desc = "切换文件类型" })
return M

View File

@ -1,56 +1,90 @@
-- =============================================================================
-- Treesitter 配置 (lua/treesitter.lua)
-- =============================================================================
-- 本模块配置 Neovim 的 Treesitter 集成,提供语法树驱动的语法高亮、
-- 代码折叠和文本对象支持。
--
-- 加载方式:
-- 由 pack.lua 通过 lazy.on_event("treesitter", "VimEnter", "*", ...) 延迟加载。
--
-- 设计要点:
-- 1. Parser 安装延迟 100ms 执行,避免阻塞 startup
-- 2. 高亮按 buffer 动态附加FileType autocmd只安装需要的 parser
-- =============================================================================
local M = {}
-- ---------------------------------------------------------------------------
-- 预安装的 parser 列表
-- ---------------------------------------------------------------------------
-- 这些 parser 会在首次启动后延迟安装。
-- 如果某个 parser 未在此列表中,打开对应文件类型时仍可通过 nvim-treesitter
-- 的 :TSInstall 手动安装。
local ensure_installed = {
"lua",
"vim",
"vimdoc",
-- Web
-- Web 前端
"javascript",
"typescript",
"tsx",
"jsdoc",
"tsx", -- TypeScript JSX
"jsdoc", -- JSDoc 注释
"json",
"html",
"css",
"yaml",
-- Backend
-- 后端
"c",
"rust",
"toml",
"go",
"gomod",
"gosum",
"gowork",
-- Infra
"gomod", -- Go Modules
"gosum", -- Go Sum
"gowork", -- Go Workspaces
-- 基础设施
"dockerfile",
"make",
}
-- ---------------------------------------------------------------------------
-- 模块初始化
-- ---------------------------------------------------------------------------
M.setup = function()
-- 加载 nvim-treesitter通过 packadd 激活 opt 插件)
vim.cmd.packadd("nvim-treesitter")
local treesitter = require("nvim-treesitter")
-- 延迟安装 parser避免阻塞
-- 延迟安装 parser在 startup 完成 100ms 后后台安装,
-- 避免在安装过程中阻塞编辑器。
vim.defer_fn(function()
treesitter.install(ensure_installed)
end, 100)
-- 按文件类型动态启用 treesitter 高亮。
-- 当文件的 filetype 被设置时BufRead、:setfiletype 等),
-- 尝试查找并加载对应的 parser成功后启用高亮。
vim.api.nvim_create_autocmd("FileType", {
pattern = "*",
pattern = "*", -- 匹配所有文件类型
callback = function(args)
local buf = args.buf
local ft = vim.bo[buf].filetype
-- 将文件类型映射到 treesitter 语言名
-- 例如 "javascriptreact" → "javascript"
local lang = vim.treesitter.language.get_lang(ft)
if not lang then
return
return -- 无对应语言,跳过
end
-- 尝试注册语言(如果 parser 已安装)
-- pcall 用于安全调用parser 未安装时不会报错
local ok_add = pcall(vim.treesitter.language.add, lang)
if not ok_add then
return
return -- parser 未安装,跳过
end
-- 启用 treesitter 高亮
pcall(vim.treesitter.start, buf, lang)
end,
})

View File

@ -1,21 +1,72 @@
-- =============================================================================
-- 用户自定义命令 (lua/usercmds.lua)
-- =============================================================================
-- 定义 Neovim 命令行可用的自定义命令,封装 vim.pack API 的日常操作。
--
-- Neovim 0.12+ 内置 vim.pack 插件管理,提供:
-- vim.pack.add(urls, opts) - 添加/安装插件
-- vim.pack.del(names) - 删除插件
-- vim.pack.update(names?) - 更新插件
--
-- 以下命令是对这些 API 的友好封装,支持命令行参数解析。
-- =============================================================================
-- ---------------------------------------------------------------------------
-- :PackAdd — 添加插件
-- ---------------------------------------------------------------------------
-- 用法::PackAdd user/repo1 user/repo2 ...
-- 示例::PackAdd stevearc/conform.nvim tpope/vim-fugitive
--
-- 原理:
-- 将命令行参数opts.fargs已按空格分割的字符串数组
-- 直接传递给 vim.pack.add()。
-- vim.pack 会从 GitHub 下载插件到 stdpath("data")/site/pack/core/opt/。
--
-- nargs = "+" 表示至少需要 1 个参数。
vim.api.nvim_create_user_command("PackAdd", function(opts)
vim.pack.add(opts.fargs)
end, { nargs = "+", desc = "Add plugins (:PackAdd user/repo1 user/repo2)" })
end, { nargs = "+", desc = "添加插件 (:PackAdd user/repo1 user/repo2)" })
-- ---------------------------------------------------------------------------
-- :PackDel — 删除插件
-- ---------------------------------------------------------------------------
-- 用法::PackDel plugin1 plugin2 ...
-- 示例::PackDel conform.nvim vim-fugitive
--
-- 注意:
-- 参数是插件目录名(即 repo 名),不是完整 URL。
-- Neovim 0.13 Nightly 已内置此命令,这里为 0.12 提供兼容。
--
-- nargs = "+" 表示至少需要 1 个参数。
-- Pack Delete and Update cmds are built-in on Nightly 0.13
vim.api.nvim_create_user_command("PackDel", function(opts)
vim.pack.del(opts.fargs)
end, { nargs = "+", desc = "Delete plugins (:PackDel plugin1 plugin2)" })
end, { nargs = "+", desc = "删除插件 (:PackDel plugin1 plugin2)" })
-- ---------------------------------------------------------------------------
-- :PackUpdate — 更新插件
-- ---------------------------------------------------------------------------
-- 用法:
-- :PackUpdate → 更新所有插件
-- :PackUpdate name1 name2 ... → 更新指定插件
--
-- 原理:
-- 检查 opts.args 是否包含非空白字符(%S 匹配任意非空白)。
-- 如果有参数,按空白分割为数组后逐个更新;
-- 如果无参数,调用无参版本更新所有插件。
--
-- nargs = "*" 表示接受 0 个或多个参数。
vim.api.nvim_create_user_command("PackUpdate", function(opts)
-- checks if any argument is passed
-- 检查是否有传入任何参数(包含非空白字符)
if opts.args:match("%S") then
-- update specific plugins
-- 按空白字符分割参数trimempty 去除空字符串
local plugins = vim.split(opts.args, "%s+", { trimempty = true })
-- update only specified plugins
-- 仅更新指定的插件
vim.pack.update(plugins)
else
-- update all
-- 无参数,更新所有插件
vim.pack.update()
end
end, { nargs = "*", desc = "Update all plugins or specific ones" })
end, { nargs = "*", desc = "更新所有插件或指定插件" })