格式化与导入整理
第九天把保存动作变成可靠的清理入口。Go 的格式化不只是缩进,它还包括导入排序、删除未使用 import、统一代码风格。
今天你要明确一件事:当前格式化到底来自 gopls,还是来自 goimports / gofumpt 这样的外部工具。
今日验收
今日目标
绑定保存时 gofmt / goimports / gofumpt;确认格式化来自 LSP 还是外部 formatter;建立可排错的保存格式化流程。
完成标准
故意写乱 import 和缩进后,保存 Go 文件会自动整理;go test ./... 通过;你能说出今天启用的是 vim.lsp.buf.format 还是外部命令。
1. 先理解三个工具
Go 格式化有层次。先知道每个工具负责什么,再决定保存时调用谁。
| 工具 | 负责什么 | 今天建议 |
|---|---|---|
gofmt | Go 官方标准格式化:缩进、换行、对齐。 | 永远可信,是最低基线。 |
goimports | 在 gofmt 基础上整理 import,添加缺失 import,删除未使用 import。 | 日常开发最实用,推荐作为外部 formatter。 |
gofumpt | 比 gofmt 更严格的一套格式规则。 | 团队明确采用时再开;个人学习期先知道它存在。 |
gopls | 通过 LSP 提供 format 和 organize imports code action。 | 适合统一走 LSP,但要清楚它背后仍依赖 Go 工具链能力。 |
$ go install golang.org/x/tools/cmd/goimports@latest
$ go install mvdan.cc/gofumpt@latest
$ goimports -h
$ gofumpt -h
gofmt。2. 路线 A:使用 LSP 格式化
LSP 路线的优点是统一:格式化、诊断、代码动作都由 gopls 提供。先把按键放进 Day06 的 on_attach。
map("<leader>lf", function()
vim.lsp.buf.format({ async = false })
end, "Format current buffer")再加保存时自动格式化。把它放进 lua/lsp/init.lua 的 on_attach 内部,确保只在 LSP attach 后启用。
local group = vim.api.nvim_create_augroup("GoLspFormat", { clear = false })
vim.api.nvim_clear_autocmds({ group = group, buffer = bufnr })
vim.api.nvim_create_autocmd("BufWritePre", {
group = group,
buffer = bufnr,
callback = function()
vim.lsp.buf.format({ async = false, bufnr = bufnr })
end,
})async = false。否则保存可能先发生,格式化结果晚一步回来,文件内容和磁盘内容会错位。3. 路线 B:使用外部 formatter
外部路线的优点是可解释:你明确知道保存时调用了哪个二进制。今天推荐先用 goimports,因为它会整理 import。
local function goimports()
local file = vim.api.nvim_buf_get_name(0)
if file == "" then
return
end
vim.cmd.write()
vim.system({ "goimports", "-w", file }, { text = true }, function(result)
vim.schedule(function()
if result.code ~= 0 then
vim.notify(result.stderr, vim.log.levels.ERROR)
return
end
vim.cmd.edit()
end)
end)
end
vim.keymap.set("n", "<leader>gf", goimports, { desc = "Format Go file with goimports" })4. 推荐配置:LSP 格式化 + organize imports
如果你已经在 Day06 接好了 gopls,推荐优先用 LSP:保存前先执行 organize imports,再 format。这样逻辑都留在 gopls 这条链路里。
local function organize_imports(bufnr)
local params = vim.lsp.util.make_range_params(nil, "utf-16")
params.context = { only = { "source.organizeImports" } }
local clients = vim.lsp.get_clients({ bufnr = bufnr, name = "gopls" })
for _, client in ipairs(clients) do
local result = client.request_sync("textDocument/codeAction", params, 1000, bufnr)
for _, action in ipairs(result and result.result or {}) do
if action.edit then
vim.lsp.util.apply_workspace_edit(action.edit, client.offset_encoding)
end
end
end
end
local function format_go(bufnr)
organize_imports(bufnr)
vim.lsp.buf.format({
bufnr = bufnr,
async = false,
filter = function(client)
return client.name == "gopls"
end,
})
end然后在 on_attach 中绑定保存前动作:
local group = vim.api.nvim_create_augroup("GoLspFormat", { clear = false })
vim.api.nvim_clear_autocmds({ group = group, buffer = bufnr })
vim.api.nvim_create_autocmd("BufWritePre", {
group = group,
buffer = bufnr,
callback = function()
format_go(bufnr)
end,
})filter 很重要。它避免两个工具抢着格式化同一个 buffer。5. 制造一个需要整理的文件
回到 tasknote,故意写乱 import、缩进和未使用包。保存时观察它们是否被整理。
package main
import (
"fmt"
"strings"
"os"
)
func DebugTask(title string) string {
task, err := NewTask(strings.TrimSpace(title))
if err != nil {
return fmt.Sprintf("error: %v", err)
}
return FormatTask(task)
}| 问题 | 格式化后应该怎样 | 谁负责 |
|---|---|---|
| 缩进乱 | 函数体缩进恢复为 Go 标准格式。 | gofmt / LSP format。 |
| import 顺序乱 | 标准库 import 按组和字母顺序整理。 | goimports 或 gopls organize imports。 |
os 未使用 | 未使用 import 被删除。 | goimports 或 gopls organize imports。 |
$ gofmt -w *.go
$ goimports -w *.go
6. 确认格式化来源
不要只说“保存会格式化”。要能说出是哪条链路触发了格式化。
| 检查 | 命令 | 怎么看 |
|---|---|---|
| 有哪些保存钩子 | :autocmd BufWritePre | 看是否有 GoLspFormat 或你自己创建的 group。 |
| LSP 是否支持格式化 | :lua vim.print(vim.lsp.get_clients()[1].server_capabilities.documentFormattingProvider) | 如果是 true,gopls 可以处理 format 请求。 |
| 当前客户端是谁 | :lua vim.print(vim.lsp.get_clients()) | 确认是否只有 gopls,还是有多个客户端。 |
| 外部工具是否可用 | :echo executable("goimports") | 输出 1 表示 Nvim 能找到外部命令。 |
7. 写 Day09 日志并提交
今天的日志重点记录你选择了哪条格式化路线。以后团队项目里最容易争论的就是格式化来源。
# Day09 formatting and imports log ## Tools - gofmt: - goimports: - gofumpt: ## Format source - LSP or external: - BufWritePre group: - command used to inspect: ## Import cleanup - broken file: - before save: - after save: ## Decision - my default formatter: - when I would use gofumpt:
$ git add lua/lsp/init.lua
$ git commit -m "day09 format go files on save"
$ cd ~/code/lab/tasknote
$ go test ./...
离开前自测
保存一个 Go 文件前,先说出会触发哪个 autocmd、调用哪个客户端或工具、是否整理 import。说得出来,配置才算可维护。