local log = require('go.utils').log local coverage = {} local api = vim.api local M = {} local visable = false _GO_NVIM_CFG = _GO_NVIM_CFG local sign_define_cache = {} M.sign_map = {covered = 'goCoverageCovered', uncover = 'goCoverageUncover'} local ns = 'gocoverage_ns' 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 local s = vim.fn.sign_getdefined(name) 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_win_bufnr() local winids = {} local bufnrl = {} for i = 1, vim.fn.tabpagenr('$') do for j = 1, vim.fn.tabpagewinnr(i, '$') do local winid = vim.fn.win_getid(j, i) local bufnr = vim.fn.winbufnr(winid) if vim.fn.buflisted(bufnr) then local fn = vim.fn.bufname(bufnr) local ext = string.sub(fn, #fn - 2) if ext == '.go' then table.insert(winids, winid) table.insert(bufnrl, bufnr) end end end end return winids, bufnrl end function M.define(bufnr, name, opts, redefine) if sign_define_cache[bufnr] == nil then sign_define_cache[bufnr] = {} end -- log(bufnr, name, opts, redefine) -- print(bufnr, name, opts, redefine) if redefine then sign_define_cache[bufnr][name] = nil vim.fn.sign_undefine(name) vim.fn.sign_define(name, opts) elseif not sign_get(name) then -- log("define sign", name, vim.inspect(opts)) vim.fn.sign_define(name, opts) end -- vim.cmd([[sign list]]) end function M.remove(bufnr, lnum) if bufnr == nil then bufnr = vim.fn.bufnr('$') end vim.fn.sign_unplace(ns, {buffer = bufnr, id = lnum}) end local function remove_all() local _, bufnrs = all_win_bufnr() for _, bid in pairs(bufnrs) do M.remove(bid) end end function M.add(bufnr, signs) local to_place = {} for _, s in pairs(signs or {}) do local count = s.cnt local stype = "goCoverageCovered" if count == 0 then stype = "goCoverageUncover" end M.define(bufnr, stype, {text = _GO_NVIM_CFG.gocoverage_sign, texthl = stype}) for lnum = s.range.start.line, s.range['end'].line + 1 do to_place[#to_place + 1] = { id = lnum, group = ns, name = stype, buffer = bufnr, lnum = lnum, priority = _GO_NVIM_CFG.gocoverage_sign_priority } end end -- log("placing", to_place) vim.fn.sign_placelist(to_place) return to_place -- for testing end M.highlight = function() if vim.o.background == 'dark' then vim.cmd([[hi! goCoverageCovered guifg=#107040 ctermbg=28]]) vim.cmd([[hi! goCoverageUncover guifg=#A03040 ctermbg=52]]) else vim.cmd([[hi! goCoverageCovered guifg=#70f0d0 ctermbg=120]]) vim.cmd([[hi! goCoverageUncover guifg=#f040d0 ctermbg=223]]) end end local function augroup() vim.cmd([[ augroup gopher.vim-coverage ]]) vim.cmd([[ au! ]]) vim.cmd([[ au ColorScheme * lua require'go.coverage'.highlight() ]]) vim.cmd([[ au BufWinLeave *.go lua require'go.coverage'remove() ]]) vim.cmd([[ au BufWinEnter *.go lua require'go.coverage'enable_all() ]]) vim.cmd([[ augroup end ]]) end local function enable_all() local _, bufnrs = all_win_bufnr() for _, bufnr in pairs(bufnrs) do -- enable -- local bufnr = vim.fn.winbufnr(id) local fn = vim.fn.bufname(bufnr) local filename = vim.fn.fnamemodify(fn, ":t") if coverage[fn] ~= nil then M.add(bufnr, coverage[fn]) end end end M.toggle = function(show) if (show == nil and visable == true) or show == false then -- hide visable = false remove_all() return end visable = true enable_all() -- end end local function parse_line(line) local m = vim.fn.matchlist(line, [[\v([^:]+):(\d+)\.(\d+),(\d+)\.(\d+) (\d+) (\d+)]]) if m == nil or #m == 0 then return {} end local path = m[2] local filename = vim.fn.fnamemodify(m[2], ":t") return { file = path, filename = filename, range = { start = {line = tonumber(m[3]), character = tonumber(m[4])}, ['end'] = {line = tonumber(m[5]), character = tonumber(m[6])} }, num = tonumber(m[7]), cnt = tonumber(m[8]) } end if vim.tbl_isempty(vim.fn.sign_getdefined(sign_covered)) then vim.fn.sign_define(sign_covered, {text = _GO_NVIM_CFG.gocoverage_sign, texthl = "goCoverageCovered"}) end if vim.tbl_isempty(vim.fn.sign_getdefined(sign_uncover)) then vim.fn.sign_define(sign_uncover, {text = _GO_NVIM_CFG.gocoverage_sign, texthl = "goCoverageUncover"}) end M.read_cov = function(covfn) local cov = vim.fn.readfile(covfn) -- log(vim.inspect(cov)) for _, line in pairs(cov) do local cl = parse_line(line) if cl.file == nil or cl.range == nil then goto continue end -- log("cl", vim.inspect(cl)) if coverage[cl.filename] == nil then coverage[cl.filename] = {} end table.insert(coverage[cl.filename], cl) ::continue:: end local _, bufnrs = all_win_bufnr() log("buffers", bufnrs) for _, bid in pairs(bufnrs) do local bufnr = vim.fn.winbufnr(bid) local fn = vim.fn.bufname(bufnr) fn = vim.fn.fnamemodify(fn, ":t") M.add(bid, coverage[fn]) visable = true end return coverage end M.run = function(tag) -- local cov = vim.fn.tempname() local cov = vim.fn.expand("%:p:h") .. "/cover.cov" local cmd = {'go', 'test', '-coverprofile', cov} if tag ~= nil then table.insert(cmd, tag) end local lines = {""} coverage = {} table.insert(cmd, "./" .. vim.fn.expand('%:.:h')) log("run coverage", cmd) local j = vim.fn.jobstart(cmd, { on_stdout = function(jobid, data, event) log("go coverage " .. vim.inspect(data)) vim.list_extend(lines, data) end, on_stderr = function(job_id, data, event) print("go coverage finished with message: " .. vim.inspect(tag) .. "error: " .. vim.inspect(data) .. "job" .. tostring(job_id) .. "ev" .. event) end, on_exit = function(job_id, data, event) if event ~= "exit" then print(job_id, event, vim.inspect(data)) end log("test finished") coverage = M.read_cov(cov) -- log("coverage", coverage) vim.fn.delete(cov) vim.fn.setqflist({}, " ", { title = cmd, lines = lines -- efm = vim.api.nvim_buf_get_option(bufnr, "errorformat") }) vim.api.nvim_command("doautocmd QuickFixCmdPost") vim.cmd([[copen]]) end }) end return M