nvim/lua/plugins/lsp.lua
xfy 00b13a18f5 feat(lsp): 自定义诊断 sign column 标志符号
覆盖 Neovim 0.12 默认的 E/W/I/H 字母标志,使用更直观的符号
✖/▲/i/h,同时保留行尾 virtual_text 显示。
2026-06-16 09:46:22 +08:00

223 lines
9.3 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

-- =============================================================================
-- LSP、格式化与诊断配置 (lua/plugins/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 懒加载)
-- ---------------------------------------------------------------------------
-- conform.nvim 是一个轻量级格式化器包装器,支持保存时自动格式化。
-- 由于格式化器通常不是立即需要的,通过 lazy.on_event 在 BufWritePre 时
-- 才进行 packadd 和 setup。
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()
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 服务器、DAP 适配器和格式化工具的安装管理器。
-- nvim-lspconfig 提供常见 LSP 服务器的预设配置。
vim.cmd.packadd("nvim-lspconfig")
lazy.track("lspconfig")
require("mason").setup()
lazy.track("mason")
-- 构建 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" } },
},
},
})
-- ---------------------------------------------------------------------------
-- 已安装的 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", -- HTML 语言支持
"cssls", -- CSS/SCSS/Less 语言支持
"gopls", -- Go 语言支持
"vtsls", -- TypeScript/JavaScript 支持(推荐替代 tsserver
"rust_analyzer", -- Rust 语言支持
"lua_ls", -- Lua 语言支持
"taplo", -- TOML 支持
"svelte", -- Svelte 框架支持
"kotlin_lsp", -- Kotlin 支持
})
-- ---------------------------------------------------------------------------
-- 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 = "代码操作" })
-- <leader>cr - Rename重命名光标下的符号
vim.keymap.set("n", "<leader>cr", function()
vim.lsp.buf.rename()
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
end
-- ]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: 代码行右侧显示诊断文本
-- - signs: 左侧 sign column 的诊断标志0.12 默认为 E/W/I/H 字母)
vim.diagnostic.config({
virtual_text = true,
signs = {
text = {
[vim.diagnostic.severity.ERROR] = "",
[vim.diagnostic.severity.WARN] = "",
[vim.diagnostic.severity.INFO] = "i",
[vim.diagnostic.severity.HINT] = "h",
},
},
})