折腾在 Vim 里执行测试用例
曦子缘起
日常用 neovim 开发, 在写单元测试的时候发现我的配置执行单元测试要把光标放在函数名上才行看了下源码是直接获取光标所在的文本提取函数名, 在修改测试函数体的时候哪里能忍.
历程
查看源码发现, 获取函数名的方法是正则表达式匹配
local line = vim.fn.search([[func \(Test\|Example\)]], 'bcnW')
if line == 0 then
output.show_error(
prefix,
string.format('Test func not found: %s', func_name)
)
return
end
这里要想办法只要光标在测试函数体内就能获取函数名, 有以下方案
- 依然使用正则匹配但是一直向前搜索, 直到搜索到一个可用的函数名
- 可用但是不怎么优雅
- 调用 lsp ( golang 的官方 lsp 是 gopls - 发音 go please) 来获取所在函数的函数名
考量了下精准和优雅性采用了后者.
首先找到 gopls 的文档, 看这里
我们对 lsp 构建如下请求, 其实就是发送光标位置给它.
划重点了, 用 inspect 可以打印对象的内容!!! 调试的时候很有用
-- Define the request parameters
local params = {
textDocument = vim.lsp.util.make_text_document_params(),
position = { line = cursor_pos[1] - 1, character = cursor_pos[2] - 1 }
}
resp = vim.lsp.buf_request_sync(bufnr, 'textDocument/documentSymbol', params, 100)
print(vim.inspect(resp))
从代码中可以看到我们的光标在 11 行.
我们收到的响应如下:
{ {
result = { {
detail = "func(t *testing.T)",
kind = 12,
name = "TestFoo",
range = {
["end"] = {
character = 1,
line = 13
},
start = {
character = 0,
line = 7
}
},
selectionRange = {
["end"] = {
character = 12,
line = 7
},
start = {
character = 5,
line = 7
}
}
}, {
detail = "func(t *testing.T)",
kind = 12,
name = "TestBar",
range = {
["end"] = {
character = 1,
line = 21
},
start = {
character = 0,
line = 15
}
},
selectionRange = {
["end"] = {
character = 12,
line = 15
},
start = {
character = 5,
line = 15
}
}
} }
} }
可以看到 lsp 返回了该文件所有的函数信息, 我们重点关注 name
, range
, 这里有我们需要的函数名, 还有函数的起始和结束位置.
想必大家可以轻松想到, 只要遍历这个结构体并且对比行号是不是在起始和结束中间就能拿到响应的函数名.
local function_symbol
for _, xsymbol in pairs(resp or {}) do
for _, ysymbol in pairs(xsymbol or {}) do
for _, symbol in pairs(ysymbol or {}) do
-- print(vim.inspect(symbol.range["end"]))
if symbol.kind == 12 and symbol.range.start.line < cursor_pos[1] and symbol.range["end"].line > cursor_pos[1] then
return symbol.name
end
end
end
end
获取函数名后那下面的操作就顺风顺水了, 只要通过 lua 来构建测试用例函数就可以了.
local cmd = {
'go',
'test',
'-run',
vim.fn.shellescape(string.format('^%s$', func_name)),
}
相当于手动执行了 go test -run TestFoo
总结
这里不得不吐槽下 lua 的语法, 同时支持 range.start
和 range["end"]
。