go.nvim/lua/go/asyncmake.lua

429 lines
11 KiB
Lua
Raw Normal View History

-- inspired by https://phelipetls.github.io/posts/async-make-in-nvim-with-lua/
2021-04-19 06:10:06 +00:00
local M = {}
2023-09-07 08:12:43 +00:00
local util = require('go.utils')
local log = util.log
local trace = util.trace
2023-09-07 08:12:43 +00:00
local getopt = require('go.alt_getopt')
2022-01-17 06:33:00 +00:00
local os_name = vim.loop.os_uname().sysname
local is_windows = os_name == 'Windows' or os_name == 'Windows_NT'
2023-09-07 08:12:43 +00:00
local is_git_shell = is_windows
and (vim.fn.exists('$SHELL') and vim.fn.expand('$SHELL'):find('bash.exe') ~= nil)
2022-01-17 06:33:00 +00:00
local function compile_efm()
local efm = [[%-G#\ %.%#]]
efm = efm .. [[,%-G%.%#panic:\ %m]]
efm = efm .. [[,%Ecan\'t\ load\ package:\ %m]]
efm = efm .. [[,%A%\\%%\(%[%^:]%\\+:\ %\\)%\\?%f:%l:%c:\ %m]]
efm = efm .. [[,%A%\\%%\(%[%^:]%\\+:\ %\\)%\\?%f:%l:\ %m]]
efm = efm .. [[,%C%*\\s%m]]
efm = efm .. [[,%-G%.%#]]
return efm
end
local extract_filepath = util.extract_filepath
2022-05-16 23:47:45 +00:00
local long_opts = {
2023-09-07 08:12:43 +00:00
verbose = 'v',
compile = 'c',
debug = 'g', -- build for debugging
coverprofile = 'C',
2023-09-07 08:12:43 +00:00
tags = 't',
args = 'a',
count = 'n',
build = 'b',
2023-09-07 08:12:43 +00:00
run = 'r',
floaterm = 'F',
fuzz = 'f',
2022-05-16 23:47:45 +00:00
}
local short_opts = 'a:ct:b:Fg'
2023-09-07 08:12:43 +00:00
local bench_opts = { '-benchmem', '-cpuprofile', 'profile.out' }
2022-05-16 23:47:45 +00:00
2021-07-16 14:37:41 +00:00
function M.make(...)
2021-12-10 00:43:59 +00:00
local args = { ... }
2021-04-19 06:10:06 +00:00
local winnr = vim.fn.win_getid()
local bufnr = vim.api.nvim_win_get_buf(winnr)
2023-09-07 08:12:43 +00:00
local makeprg = vim.api.nvim_buf_get_option(bufnr, 'makeprg')
2021-12-10 00:43:59 +00:00
2022-06-01 11:29:13 +00:00
local optarg, _, reminder = getopt.get_opts(args, short_opts, long_opts)
log(makeprg, args, short_opts, optarg, reminder)
if reminder and #reminder > 0 then
-- expand % to current file
for i, arg in ipairs(reminder) do
2023-09-07 08:12:43 +00:00
if arg:find('%%') then
if arg == '%' then
reminder[i] = vim.fn.expand('%')
elseif arg == '%:h' then
reminder[i] = './' .. vim.fn.expand('%:h') .. '/...'
else
reminder[i] = vim.fn.expand(arg)
end
-- reminder[i] = arg:gsub("%%", vim.fn.expand("%"))
end
end
end
2023-01-31 21:28:06 +00:00
if vim.fn.empty(makeprg) == 0 and args[1] == 'go' then
2023-09-07 08:12:43 +00:00
vim.notify(
'makeprg is already set to ' .. makeprg .. ' args: ' .. vim.inspect(args),
vim.log.levels.WARN
)
2023-01-31 21:28:06 +00:00
end
2022-06-01 11:29:13 +00:00
-- local indent = "%\\%( %\\)"
2021-07-16 14:37:41 +00:00
if not makeprg then
2023-09-07 08:12:43 +00:00
log('makeprog not setup')
2021-07-16 14:37:41 +00:00
return
end
2021-04-19 06:10:06 +00:00
2023-09-07 08:12:43 +00:00
local runner = vim.split(makeprg, ' ')[1]
2023-09-07 08:12:43 +00:00
if not require('go.install').install(runner) then
util.warn('please wait for ' .. runner .. ' to be installed and re-run the command')
return
end
local efm = [[%-G#\ %.%#]]
2023-09-07 08:12:43 +00:00
if makeprg:find('go build') then
2021-12-10 00:43:59 +00:00
vim.cmd([[setl errorformat=%-G#\ %.%#]])
-- if makeprg:find("go build") then
2022-01-17 06:33:00 +00:00
efm = compile_efm()
2023-09-07 08:12:43 +00:00
if optarg['g'] then
makeprg = makeprg .. ' -gcflags="all=-N -l"'
end
2021-07-16 14:37:41 +00:00
end
-- end
2021-12-10 00:43:59 +00:00
2023-09-07 08:12:43 +00:00
local runner = 'golangci-lint'
if makeprg:find('golangci%-lint') then
2021-04-19 06:10:06 +00:00
-- lint
efm = efm .. [[,%A%\\%%(%[%^:]%\\+:\ %\\)%\\?%f:%l:%c:\ %m]]
efm = efm .. [[,%A%\\%%(%[%^:]%\\+:\ %\\)%\\?%f:%l:\ %m]]
2021-12-16 07:15:21 +00:00
local pwd = util.work_path()
2023-09-07 08:12:43 +00:00
local cfg = pwd .. '.golangci.yml'
if util.file_exists(cfg) then
makeprg = makeprg .. [[\ -c\ ]] .. cfg
-- vim.api.nvim_buf_set_option(bufnr, "makeprg", makeprg)
end
2021-04-19 06:10:06 +00:00
end
2022-01-17 06:33:00 +00:00
local compile_test = false
2022-09-05 00:56:42 +00:00
2023-09-07 08:12:43 +00:00
if makeprg:find('go run') then
runner = 'go run'
if args == nil or #args == 0 or (#args == 1 and args[1] == '-F') then
makeprg = makeprg .. ' .'
end
efm = efm .. [[,%A%\\t%#%f:%l\ +0x%[0-9A-Fa-f]%\\+]]
2023-09-07 08:12:43 +00:00
log('go run', makeprg)
end
2023-09-07 08:12:43 +00:00
if makeprg:find('go vet') then
runner = 'go vet'
if args == nil or #args == 0 then
2023-09-07 08:12:43 +00:00
makeprg = makeprg .. ' .'
end
2022-01-17 06:33:00 +00:00
efm = compile_efm()
efm = efm .. [[,%-Gexit\ status\ %\\d%\\+]]
2021-07-16 14:37:41 +00:00
end
2021-04-19 06:10:06 +00:00
if makeprg:find('generate') then
if args == nil or #args == 0 then
2023-09-07 08:12:43 +00:00
makeprg = makeprg .. ' ' .. vim.fn.expand('%')
end
end
2023-09-07 08:12:43 +00:00
local cmd = vim.fn.split(makeprg, ' ')
if optarg['t'] then
local tag = optarg['t']
local f = tag:find('=')
2022-05-30 14:04:52 +00:00
if not f then
2023-09-07 08:12:43 +00:00
table.insert(cmd, '-tags=' .. tag)
2022-05-30 14:04:52 +00:00
else
2023-09-07 08:12:43 +00:00
table.insert(cmd, '-tags=' .. tag:sub(f + 1))
2022-05-30 14:04:52 +00:00
end
end
2022-09-05 00:56:42 +00:00
local bench = false
2023-09-07 08:12:43 +00:00
if makeprg:find('go test') then
-- log('go test')
-- runner = 'go test'
-- efm = compile_efm()
-- if optarg['c'] then
-- log('compile test')
-- compile_test = true
-- end
-- for _, arg in ipairs(args) do
-- --check if it is bench test
-- if arg:find('-bench') then
-- bench = true
-- end
-- end
-- if optarg['v'] then
-- table.insert(cmd, '-v')
-- end
-- if optarg['C'] then
-- table.insert(cmd, '-coverprofile=' .. optarg['C'])
-- end
-- if optarg['f'] then
-- log('fuzz test')
-- table.insert(cmd, '-fuzz')
-- end
-- if optarg['n'] then
-- table.insert(cmd, '-count=' .. optarg['n'])
-- end
-- if not bench and compile_test then
-- table.insert(cmd, '-c')
-- end
-- if optarg['r'] then
-- log('run test', optarg['r'])
-- table.insert(cmd, '-run')
-- table.insert(cmd, optarg['r'])
-- end
-- if optarg['b'] then
-- log('build test flags', optarg['b'])
-- table.insert(cmd, optarg['b'])
-- end
2022-05-24 07:23:53 +00:00
end
2021-07-16 14:37:41 +00:00
2022-09-05 00:56:42 +00:00
if bench then
cmd = vim.list_extend(cmd, args)
elseif args and #args > 0 then
2022-05-16 23:47:45 +00:00
cmd = vim.list_extend(cmd, reminder)
2021-07-16 14:37:41 +00:00
end
2021-09-03 02:13:15 +00:00
2023-09-07 08:12:43 +00:00
if optarg['a'] then
2023-09-22 10:32:02 +00:00
if runner == 'go run' then
table.insert(cmd, optarg['a'])
else
table.insert(cmd, '-args')
table.insert(cmd, optarg['a'])
end
end
2023-09-07 08:12:43 +00:00
if _GO_NVIM_CFG.run_in_floaterm or optarg['F'] then
local term = require('go.term').run
local cmdstr = table.concat(cmd, ' ')
term({ cmd = cmdstr, autoclose = false })
return cmd
end
return M.runjob(cmd, runner, efm, args)
end
local function handle_color(line)
if tonumber(vim.fn.match(line, '\\%x1b\\[[0-9;]\\+')) < 0 then
return line
end
if type(line) ~= 'string' then
return line
2022-03-23 02:40:15 +00:00
end
line = vim.fn.substitute(line, '\\%x1b\\[[0-9;]\\+[mK]', '', 'g')
log(line)
return line
end
M.runjob = function(cmd, runner, args, efm)
vim.validate({ cmd = { cmd, 't' }, runner = { runner, 's' } })
efm = efm or compile_efm()
local failed = false
local itemn = 1
local lines = {}
local errorlines = {}
local cmdstr = vim.fn.join(cmd, ' ') -- cmd list run without shell, cmd string run with shell
2023-09-07 08:12:43 +00:00
local package_path = (cmd[#cmd] or '')
2022-07-07 22:30:46 +00:00
if package_path ~= nil then
package_path = package_path .. util.sep()
if vim.fn.isdirectory(package_path) == 1 then
2023-09-07 08:12:43 +00:00
package_path = package_path .. '...'
else
2023-09-07 08:12:43 +00:00
package_path = ''
end
2022-07-07 22:30:46 +00:00
else
2023-09-07 08:12:43 +00:00
package_path = ''
2022-07-07 22:30:46 +00:00
end
local Sprite = util.load_plugin('guihua.lua', 'guihua.sprite')
local sprite
if Sprite then
sprite = Sprite:new({
loc = 'top_center',
syntax = 'lua',
2023-09-07 08:12:43 +00:00
rect = { height = 1, width = 30 },
data = { 'Running ' .. cmdstr },
timeout = 20000,
hl_line = 1,
})
else
2023-09-07 08:12:43 +00:00
sprite = { on_close = function() end }
end
2021-04-19 06:10:06 +00:00
local function on_event(job_id, data, event)
2022-01-11 02:50:11 +00:00
-- log("stdout", data, event)
2023-09-07 08:12:43 +00:00
if event == 'stdout' then
2021-04-19 06:10:06 +00:00
if data then
for _, value in ipairs(data) do
2023-09-07 08:12:43 +00:00
if value ~= '' then
if value:find('=== RUN') or value:find('no test file') then
goto continue
end
2022-01-11 02:50:11 +00:00
value = handle_color(value)
2023-09-07 08:12:43 +00:00
if value:find('FAIL') then
failed = true
2023-09-07 08:12:43 +00:00
if value == 'FAIL' then
goto continue
end
end
local changed = false
2023-02-01 21:53:54 +00:00
if vim.fn.empty(vim.fn.glob(args[#args])) == 0 then -- pkg name in args
changed = true
2023-09-07 08:12:43 +00:00
if value:find('FAIL') == nil then
local p, _, _ = extract_filepath(value, package_path)
if p == true then -- path existed, but need to attach the pkg name
-- log(fn, ln, package_path, package_path:gsub('%.%.%.', ''))
-- remove ... in package path
value = package_path:gsub('%.%.%.', '') .. util.ltrim(value)
end
end
else
local p, n = extract_filepath(value)
log(p, n, #lines)
if p == true then
failed = true
2023-09-07 08:12:43 +00:00
value = vim.fs.dirname(n) .. '/' .. util.ltrim(value)
changed = true
log(value)
end
end
table.insert(lines, value)
log(value, #lines)
if itemn == 1 and failed and changed then
itemn = #lines
end
end
::continue::
end
2021-04-19 06:10:06 +00:00
end
_GO_NVIM_CFG.on_stdout(event, data)
2021-04-19 06:10:06 +00:00
end
2023-09-07 08:12:43 +00:00
if event == 'stderr' then
if data then
for _, value in ipairs(data) do
2023-09-07 08:12:43 +00:00
if value ~= '' then
table.insert(errorlines, value)
end
end
end
2023-09-07 08:12:43 +00:00
if next(errorlines) ~= nil and runner == 'golangci-lint' then
2022-01-11 02:50:11 +00:00
efm =
2023-09-07 08:12:43 +00:00
[[level=%tarning\ msg="%m:\ [%f:%l:%c:\ %.%#]",level=%tarning\ msg="%m",level=%trror\ msg="%m:\ [%f:%l:%c:\ %.%#]",level=%trror\ msg="%m",%f:%l:%c:\ %m,%f:%l:\ %m,%f:%l\ %m]]
2022-01-11 02:50:11 +00:00
end
sprite.on_close()
_GO_NVIM_CFG.on_stderr(event, data)
end
2023-09-07 08:12:43 +00:00
if event == 'exit' then
log('exit')
sprite.on_close()
local info = cmdstr
local level = vim.log.levels.INFO
if #errorlines > 0 then
if #lines > 0 then
vim.list_extend(errorlines, lines)
end
trace(errorlines)
2023-09-07 08:12:43 +00:00
vim.fn.setqflist({}, ' ', {
title = info,
lines = errorlines,
efm = efm,
})
failed = true
2022-06-01 11:29:13 +00:00
log(errorlines[1], job_id)
vim.schedule(function()
vim.cmd([[echo v:shell_error]])
end)
elseif #lines > 0 then
trace(lines)
local opts = {}
if _GO_NVIM_CFG.test_efm == true then
2023-09-07 08:12:43 +00:00
efm = require('go.gotest').efm()
opts = {
title = info,
lines = lines,
efm = efm,
}
else
opts = {
title = info,
lines = lines,
}
end
2023-09-07 08:12:43 +00:00
vim.fn.setqflist({}, ' ', opts)
end
if tonumber(data) ~= 0 then
failed = true
info = info .. ' exited with code: ' .. tostring(data) .. vim.inspect(errorlines)
level = vim.log.levels.ERROR
end
_GO_NVIM_CFG.job_id = nil
if failed then
info = info .. ' go test failed'
level = vim.log.levels.WARN
2022-07-30 13:05:27 +00:00
util.quickfix('botright copen')
end
itemn = 1
2022-05-30 14:32:26 +00:00
if failed then
2023-09-07 08:12:43 +00:00
vim.notify(info .. ' failed', level)
2022-05-31 06:16:09 +00:00
else
2023-09-07 08:12:43 +00:00
vim.notify(info .. ' succeed', level)
vim.notify(table.concat(lines, '\n\r'), level)
2022-05-30 14:32:26 +00:00
end
failed = false
_GO_NVIM_CFG.on_exit(event, data)
2021-04-19 06:10:06 +00:00
end
end
2022-05-30 14:04:52 +00:00
-- releative dir does not work without shell
2023-09-07 08:12:43 +00:00
log('cmd ', cmdstr)
local runcmd = cmdstr
if is_windows then -- gitshell & cmd.exe prefer list
runcmd = cmd
end
_GO_NVIM_CFG.job_id = vim.fn.jobstart(runcmd, {
2021-07-08 16:16:22 +00:00
on_stderr = on_event,
on_stdout = on_event,
on_exit = on_event,
stdout_buffered = true,
2021-12-10 00:43:59 +00:00
stderr_buffered = true,
2021-07-08 16:16:22 +00:00
})
_GO_NVIM_CFG.on_jobstart(runcmd)
2022-05-24 07:23:53 +00:00
return cmd
2021-04-19 06:10:06 +00:00
end
M.stopjob = function(id)
id = id or _GO_NVIM_CFG.job_id
if id == nil then
return
end
local r = vim.fn.jobstop(id)
if r == 1 then
_GO_NVIM_CFG.job_id = nil
else
2023-09-07 08:12:43 +00:00
util.warn('failed to stop job ' .. tostring(id))
end
end
M.compile_efm = compile_efm
2021-04-19 06:10:06 +00:00
return M