go.nvim/lua/go/coverage.lua

437 lines
11 KiB
Lua
Raw Normal View History

2022-07-30 13:05:27 +00:00
local utils = require('go.utils')
2021-11-23 23:13:40 +00:00
local log = utils.log
2021-08-25 15:42:17 +00:00
local coverage = {}
local api = vim.api
2022-06-01 11:29:13 +00:00
local vfn = vim.fn
local empty = utils.empty
2021-08-25 15:42:17 +00:00
local M = {}
2022-08-05 00:48:08 +00:00
local visible = false
2022-06-01 11:29:13 +00:00
-- _GO_NVIM_CFG = _GO_NVIM_CFG or {}
2021-08-25 15:42:17 +00:00
local sign_define_cache = {}
2022-11-22 22:14:39 +00:00
M.sign_map = { covered = 'goCoverageCovered', uncover = 'goCoverageUncovered' }
2021-08-25 15:42:17 +00:00
2022-07-30 13:05:27 +00:00
local ns = 'gocoverage_ns'
2021-08-25 15:42:17 +00:00
local sign_covered = M.sign_map.covered
local sign_uncover = M.sign_map.uncover
local function sign_get(bufnr, name)
if sign_define_cache[bufnr] == nil then
sign_define_cache[bufnr] = {}
end
if not sign_define_cache[bufnr][name] then
2022-06-01 11:29:13 +00:00
local s = vfn.sign_getdefined(name)
2021-08-25 15:42:17 +00:00
if not vim.tbl_isempty(s) then
sign_define_cache[bufnr][name] = s
end
end
return sign_define_cache[bufnr][name]
end
-- all windows and buffers
local function all_bufnr()
2021-08-25 15:42:17 +00:00
local bufnrl = {}
2022-06-01 11:29:13 +00:00
local buffers = vfn.getbufinfo({ bufloaded = 1, buflisted = 1 })
for _, b in pairs(buffers) do
2022-06-01 11:29:13 +00:00
if not (vfn.empty(b.name) == 1 or b.hidden == 1) then
local name = b.name
local ext = string.sub(name, #name - 2)
2022-07-30 13:05:27 +00:00
if ext == '.go' then
table.insert(bufnrl, b.bufnr)
2021-08-25 15:42:17 +00:00
end
end
end
return bufnrl
end -- log(bufnr, name, opts, redefine)
2021-08-25 15:42:17 +00:00
function M.define(bufnr, name, opts, redefine)
if sign_define_cache[bufnr] == nil then
sign_define_cache[bufnr] = {}
end
-- vim.notify(bufnr .. name .. opts .. redefine, vim.lsp.log_levels.DEBUG)
2021-08-25 15:42:17 +00:00
if redefine then
sign_define_cache[bufnr][name] = nil
2022-06-01 11:29:13 +00:00
vfn.sign_undefine(name)
vfn.sign_define(name, opts)
2021-08-25 15:42:17 +00:00
elseif not sign_get(name) then
-- log("define sign", name, vim.inspect(opts))
2022-06-01 11:29:13 +00:00
vfn.sign_define(name, opts)
2021-08-25 15:42:17 +00:00
end
-- vim.cmd([[sign list]])
end
function M.remove(bufnr, lnum)
if bufnr == nil then
2022-07-30 13:05:27 +00:00
bufnr = vfn.bufnr('$')
2021-08-25 15:42:17 +00:00
end
2022-06-01 11:29:13 +00:00
vfn.sign_unplace(ns, { buffer = bufnr, id = lnum })
2021-08-25 15:42:17 +00:00
end
local function remove_all()
local bufnrs = all_bufnr()
2021-08-25 15:42:17 +00:00
for _, bid in pairs(bufnrs) do
M.remove(bid)
end
end
function M.add(bufnr, signs)
local to_place = {}
for _, s in ipairs(signs or {}) do
2021-08-25 15:42:17 +00:00
local count = s.cnt
2022-07-30 13:05:27 +00:00
local stype = 'goCoverageCovered'
2021-08-25 15:42:17 +00:00
if count == 0 then
2022-11-22 22:14:39 +00:00
stype = 'goCoverageUncovered'
2021-08-25 15:42:17 +00:00
end
2021-12-27 08:47:59 +00:00
M.define(bufnr, stype, { text = _GO_NVIM_CFG.gocoverage_sign, texthl = stype })
2022-07-30 13:05:27 +00:00
for lnum = s.range.start.line, s.range['end'].line + 1 do
log(lnum, bufnr)
2021-08-25 15:42:17 +00:00
to_place[#to_place + 1] = {
id = lnum,
group = ns,
name = stype,
buffer = bufnr,
lnum = lnum,
priority = _GO_NVIM_CFG.sign_priority,
2021-08-25 15:42:17 +00:00
}
end
end
-- log("placing", to_place)
2022-06-01 11:29:13 +00:00
vfn.sign_placelist(to_place)
2021-08-25 15:42:17 +00:00
return to_place -- for testing
end
M.highlight = function()
vim.api.nvim_set_hl(0, 'goCoverageCovered', { link = _GO_NVIM_CFG.sign_covered_hl, default = true })
vim.api.nvim_set_hl(0, 'goCoverageUncovered', { link = _GO_NVIM_CFG.sign_uncovered_hl, default = true })
2021-08-25 15:42:17 +00:00
end
2022-08-05 10:26:40 +00:00
local function enable_all()
local bufnrs = all_bufnr()
for _, bufnr in pairs(bufnrs) do
local fn = vfn.bufname(bufnr)
if coverage[fn] ~= nil then
M.add(bufnr, coverage[fn])
end
end
end
2021-08-25 15:42:17 +00:00
local function augroup()
local aug = vim.api.nvim_create_augroup('gonvim__coverage', {})
local pat = { '*.go', '*.mod' }
vim.api.nvim_create_autocmd({ 'ColorScheme' }, {
group = aug,
pattern = pat,
callback = function()
require('go.coverage').highlight()
end,
})
vim.api.nvim_create_autocmd({ 'BufWinLeave' }, {
group = aug,
pattern = pat,
callback = function()
require('go.coverage').remove()
end,
})
vim.api.nvim_create_autocmd({ 'BufWinEnter' }, {
group = aug,
pattern = pat,
callback = function()
2022-08-05 10:26:40 +00:00
enable_all()
end,
})
2021-08-25 15:42:17 +00:00
end
M.toggle = function(show)
2022-08-05 00:48:08 +00:00
if (show == nil and visible == true) or show == false then
2021-08-25 15:42:17 +00:00
-- hide
2022-08-05 00:48:08 +00:00
log('toggle remove coverage')
visible = false
return remove_all()
2021-08-25 15:42:17 +00:00
end
2022-08-05 00:48:08 +00:00
local pwd = vfn.getcwd()
local cov = pwd .. utils.sep() .. 'cover.cov'
M.read_cov(cov)
visible = true
2021-08-25 15:42:17 +00:00
enable_all()
-- end
end
local function parse_line(line)
2022-06-01 11:29:13 +00:00
local m = vfn.matchlist(line, [[\v([^:]+):(\d+)\.(\d+),(\d+)\.(\d+) (\d+) (\d+)]])
2021-08-25 15:42:17 +00:00
if empty(m) then
2021-08-25 15:42:17 +00:00
return {}
end
local path = m[2]
2022-07-30 13:05:27 +00:00
local filename = vfn.fnamemodify(m[2], ':t')
2021-08-25 15:42:17 +00:00
return {
file = path,
filename = filename,
range = {
2021-12-27 08:47:59 +00:00
start = { line = tonumber(m[3]), character = tonumber(m[4]) },
2022-07-30 13:05:27 +00:00
['end'] = { line = tonumber(m[5]), character = tonumber(m[6]) },
2021-08-25 15:42:17 +00:00
},
num = tonumber(m[7]),
2021-12-27 08:47:59 +00:00
cnt = tonumber(m[8]),
2021-08-25 15:42:17 +00:00
}
end
2022-06-01 11:29:13 +00:00
if vim.tbl_isempty(vfn.sign_getdefined(sign_covered)) then
vfn.sign_define(sign_covered, {
2021-11-23 23:13:40 +00:00
text = _GO_NVIM_CFG.gocoverage_sign,
2022-07-30 13:05:27 +00:00
texthl = 'goCoverageCovered',
2021-11-23 23:13:40 +00:00
})
2021-08-25 15:42:17 +00:00
end
2022-06-01 11:29:13 +00:00
if vim.tbl_isempty(vfn.sign_getdefined(sign_uncover)) then
vfn.sign_define(sign_uncover, {
2021-11-23 23:13:40 +00:00
text = _GO_NVIM_CFG.gocoverage_sign,
2022-11-22 22:14:39 +00:00
texthl = 'goCoverageUncovered',
2021-11-23 23:13:40 +00:00
})
2021-08-25 15:42:17 +00:00
end
M.read_cov = function(covfn)
local total_lines = 0
local total_covered = 0
2022-06-01 11:29:13 +00:00
if vfn.filereadable(covfn) == 0 then
2022-07-30 13:05:27 +00:00
vim.notify(string.format('cov file not exist: %s please run cover test first', covfn), vim.lsp.log_levels.WARN)
return
end
2022-06-01 11:29:13 +00:00
local cov = vfn.readfile(covfn)
2021-08-25 15:42:17 +00:00
-- log(vim.inspect(cov))
for _, line in pairs(cov) do
local cl = parse_line(line)
local file_lines = 0
local file_covered = 0
2021-12-27 08:47:59 +00:00
if cl.filename ~= nil or cl.range ~= nil then
total_lines = total_lines + cl.num
2021-12-27 08:47:59 +00:00
if coverage[cl.filename] == nil then
coverage[cl.filename] = {}
end
coverage[cl.filename].file_lines = (coverage[cl.filename].file_lines or 0) + cl.num
file_lines = file_lines + cl.num
if cl.cnt > 0 then
coverage[cl.filename].file_covered = (coverage[cl.filename].file_covered or 0) + cl.num
total_covered = total_covered + cl.num
end
2021-12-27 08:47:59 +00:00
table.insert(coverage[cl.filename], cl)
2021-08-25 15:42:17 +00:00
end
end
coverage.total_lines = total_lines
coverage.total_covered = total_covered
local bufnrs = all_bufnr()
2022-07-30 13:05:27 +00:00
log('buffers', bufnrs)
local added = {}
2021-08-25 15:42:17 +00:00
for _, bid in pairs(bufnrs) do
-- if added[bid] == nil then
2022-06-01 11:29:13 +00:00
local fn = vfn.bufname(bid)
2022-07-30 13:05:27 +00:00
fn = vfn.fnamemodify(fn, ':t')
log(bid, fn)
2021-08-25 15:42:17 +00:00
M.add(bid, coverage[fn])
2022-08-05 00:48:08 +00:00
visible = true
added[bid] = true
-- end
2021-08-25 15:42:17 +00:00
end
return coverage
end
2022-06-22 03:53:26 +00:00
M.show_func = function()
2022-07-30 13:05:27 +00:00
local setup = { 'go', 'tool', 'cover', '-func=cover.cov' }
local result = {}
vfn.jobstart(setup, {
on_stdout = function(_, data, _)
data = utils.handle_job_data(data)
if not data then
return
end
for _, val in ipairs(data) do
-- first strip the filename
2022-07-30 13:05:27 +00:00
local l = vim.fn.split(val, ':')
local fname = l[1]
if vim.fn.filereadable(fname) == 0 then
local parts = vim.fn.split(fname, utils.sep())
for _ = 1, #parts do
table.remove(parts, 1)
fname = vim.fn.join(parts, utils.sep())
2022-07-30 13:05:27 +00:00
log('fname', fname)
if vim.fn.filereadable(fname) == 1 then
l[1] = fname
2022-07-30 13:05:27 +00:00
local d = vim.fn.join(l, ':')
log('putback ', d)
val = d
end
end
end
table.insert(result, val)
end
end,
on_exit = function(_, data, _)
if data ~= 0 then
2022-07-30 13:05:27 +00:00
vim.notify('no coverage data', vim.lsp.log_levels.WARN)
return
end
2022-07-30 13:05:27 +00:00
vim.fn.setqflist({}, ' ', {
title = 'go coverage',
lines = result,
})
2022-07-30 13:05:27 +00:00
utils.quickfix('copen')
end,
})
end
2021-11-23 23:13:40 +00:00
M.run = function(...)
2022-07-30 13:05:27 +00:00
local get_build_tags = require('go.gotest').get_build_tags
2022-06-01 11:29:13 +00:00
-- local cov = vfn.tempname()
local pwd = vfn.getcwd()
2022-07-30 13:05:27 +00:00
local cov = pwd .. utils.sep() .. 'cover.cov'
2021-08-25 15:42:17 +00:00
2021-12-27 08:47:59 +00:00
local args = { ... }
2021-11-23 23:13:40 +00:00
log(args)
2022-08-05 00:48:08 +00:00
local load = select(1, ...)
2022-07-30 13:05:27 +00:00
if load == '-m' then
2022-06-22 03:53:26 +00:00
-- show the func metric
if vim.fn.filereadable(cov) == 1 then
return M.show_func()
end
log(cov .. ' not exist')
2022-06-22 03:53:26 +00:00
end
2022-07-30 13:05:27 +00:00
if load == '-f' then
2022-06-22 03:53:26 +00:00
local covfn = select(2, ...) or cov
if vim.fn.filereadable(covfn) == 0 then
2022-07-30 13:05:27 +00:00
vim.notify('no cov file specified or existed, will rerun coverage test', vim.lsp.log_levels.INFO)
2022-06-22 03:53:26 +00:00
else
2022-07-26 16:21:58 +00:00
local test_coverage = M.read_cov(covfn)
2022-07-30 13:05:27 +00:00
vim.notify(string.format('total coverage: %d%%', test_coverage.total_covered / test_coverage.total_lines * 100))
2022-07-26 16:21:58 +00:00
return test_coverage
2022-06-05 22:24:55 +00:00
end
end
2022-07-30 13:05:27 +00:00
if load == '-t' then
2022-06-05 22:24:55 +00:00
return M.toggle()
end
2022-07-30 13:05:27 +00:00
if load == '-r' then
2022-06-05 22:24:55 +00:00
return M.remove()
end
2022-07-30 13:05:27 +00:00
if load == '-R' then
2022-06-05 22:24:55 +00:00
return M.remove_all()
end
2022-07-30 13:05:27 +00:00
local test_runner = 'go'
if _GO_NVIM_CFG.test_runner ~= 'go' then
2021-11-23 23:13:40 +00:00
test_runner = _GO_NVIM_CFG.test_runner
2022-07-30 13:05:27 +00:00
require('go.install').install(test_runner)
2021-11-23 23:13:40 +00:00
end
2022-07-30 13:05:27 +00:00
local cmd = { test_runner, 'test', '-coverprofile', cov }
local tags = ''
2021-11-23 23:13:40 +00:00
local args2 = {}
if not empty(args) then
2021-11-23 23:13:40 +00:00
tags, args2 = get_build_tags(args)
2022-05-30 14:04:52 +00:00
if tags ~= nil then
table.insert(cmd, tags)
2021-11-23 23:13:40 +00:00
end
end
if not empty(args2) then
log(args2)
cmd = vim.list_extend(cmd, args2)
else
local argsstr
if load == '-p' then
local pkg = require("go.package").pkg_from_path(nil, vim.api.nvim_get_current_buf())
if vfn.empty(pkg) == 1 then
util.log("No package found in current directory.")
return nil
end
argsstr = pkg[1]
else
argsstr = '.' .. utils.sep() .. '...'
end
table.insert(cmd, argsstr)
2021-08-25 15:42:17 +00:00
end
2021-11-23 23:13:40 +00:00
2022-07-30 13:05:27 +00:00
local lines = { '' }
2021-08-25 15:42:17 +00:00
coverage = {}
2021-11-23 23:13:40 +00:00
2022-07-30 13:05:27 +00:00
log('run coverage', cmd)
2021-11-23 23:13:40 +00:00
if _GO_NVIM_CFG.run_in_floaterm then
local cmd_str = table.concat(cmd, ' ')
if empty(args2) then
cmd_str = cmd_str .. '.' .. utils.sep() .. '...'
2021-11-23 23:13:40 +00:00
end
utils.log(cmd_str)
2022-07-30 13:05:27 +00:00
local term = require('go.term').run
term({ cmd = cmd_str, autoclose = false })
2021-11-23 23:13:40 +00:00
return
end
2022-06-01 11:29:13 +00:00
vfn.jobstart(cmd, {
2021-08-25 15:42:17 +00:00
on_stdout = function(jobid, data, event)
2022-07-30 13:05:27 +00:00
log('go coverage ' .. vim.inspect(data), jobid, event)
2021-08-25 15:42:17 +00:00
vim.list_extend(lines, data)
end,
on_stderr = function(job_id, data, event)
2021-12-27 08:47:59 +00:00
data = utils.handle_job_data(data)
if data == nil then
return
end
vim.notify(
2022-07-30 13:05:27 +00:00
'go coverage finished with message: '
2021-12-27 08:47:59 +00:00
.. vim.inspect(cmd)
2022-07-30 13:05:27 +00:00
.. 'error: '
2021-12-27 08:47:59 +00:00
.. vim.inspect(data)
2022-07-30 13:05:27 +00:00
.. 'job '
2021-12-27 08:47:59 +00:00
.. tostring(job_id)
2022-07-30 13:05:27 +00:00
.. 'ev '
2021-12-27 08:47:59 +00:00
.. event,
vim.lsp.log_levels.ERROR
)
2021-08-25 15:42:17 +00:00
end,
on_exit = function(job_id, data, event)
2022-07-30 13:05:27 +00:00
if event ~= 'exit' then
vim.notify(string.format('%s %s %s', job_id, event, vim.inspect(data)), vim.lsp.log_levels.ERROR)
2021-08-25 15:42:17 +00:00
end
2022-07-30 13:05:27 +00:00
local lp = table.concat(lines, '\n')
vim.notify(string.format('test finished:\n %s', lp), vim.lsp.log_levels.INFO)
coverage = M.read_cov(cov)
2022-07-30 13:05:27 +00:00
if load == '-m' then
M.toggle(true)
2022-06-22 03:53:26 +00:00
return M.show_func()
end
2022-07-30 13:05:27 +00:00
vfn.setqflist({}, ' ', {
2021-08-25 15:42:17 +00:00
title = cmd,
2021-12-27 08:47:59 +00:00
lines = lines,
2022-07-30 13:05:27 +00:00
efm = vim.o.efm .. [[,]] .. require('go.gotest').efm(),
2021-08-25 15:42:17 +00:00
})
2022-07-30 13:05:27 +00:00
api.nvim_command('doautocmd QuickFixCmdPost')
-- vfn.delete(cov) -- maybe keep the file for other commands
2021-12-27 08:47:59 +00:00
end,
2021-08-25 15:42:17 +00:00
})
end
M.setup = function()
M.highlight()
augroup()
end
2021-08-25 15:42:17 +00:00
return M