diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 8564381..c82730b 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -47,7 +47,12 @@ jobs: key: ${{ runner.os }}-tests-${{ hashFiles('${{ github.workspace }}/.tests') }} - name: Install Go bins - run: nvim --headless -u "./scripts/minimal_init.lua" -c "GoInstallDeps" -c "qa!" + run: | + # TODO: install with :GoInstallDeps + go install github.com/fatih/gomodifytags@latest + go install github.com/josharian/impl@latest + go install github.com/cweill/gotests/...@latest + go install github.com/koron/iferr@latest - name: Run Tests run: | diff --git a/README.md b/README.md index 49cb8ed..a6f59db 100644 --- a/README.md +++ b/README.md @@ -13,17 +13,15 @@ It's **NOT** an LSP tool, the main goal of this plugin is to add go tooling supp Requirements: - **Neovim 0.10** or later -- `go` treesitter parser, install by `:TSInstall go` +- Treesitter `go` parser(`:TSInstall go`) - [Go](https://github.com/golang/go) installed (tested on 1.23) ```lua { "olexsmir/gopher.nvim", ft = "go", - -- branch = "develop", -- if you want develop branch - -- keep in mind, it might break everything + -- branch = "develop" dependencies = { - "nvim-lua/plenary.nvim", "nvim-treesitter/nvim-treesitter", }, -- (optional) will update plugin's deps on every update diff --git a/lua/gopher/_utils/init.lua b/lua/gopher/_utils/init.lua index 96a9134..1ff0f28 100644 --- a/lua/gopher/_utils/init.lua +++ b/lua/gopher/_utils/init.lua @@ -3,8 +3,9 @@ local log = require "gopher._utils.log" local utils = {} ---@param msg string ----@param lvl number +---@param lvl? number function utils.deferred_notify(msg, lvl) + lvl = lvl or vim.log.levels.INFO vim.defer_fn(function() vim.notify(msg, lvl, { title = c.___plugin_name, @@ -23,4 +24,22 @@ function utils.notify(msg, lvl) log.debug(msg) end +---@param path string +---@return string +function utils.readfile_joined(path) + return table.concat(vim.fn.readfile(path), "\n") +end + +---@param t string[] +---@return string[] +function utils.remove_empty_lines(t) + local res = {} + for _, line in ipairs(t) do + if line ~= "" then + table.insert(res, line) + end + end + return res +end + return utils diff --git a/lua/gopher/_utils/runner/init.lua b/lua/gopher/_utils/runner/init.lua index 3c08f7f..15eaa42 100644 --- a/lua/gopher/_utils/runner/init.lua +++ b/lua/gopher/_utils/runner/init.lua @@ -1,33 +1,39 @@ -local Job = require "plenary.job" +local c = require "gopher.config" local runner = {} ---@class gopher.RunnerOpts ----@field args? string[] ----@field cwd? string? ----@field on_exit? fun(data:string, status:number) +---@field cwd? string +---@field timeout? number +---@field stdin? boolean|string|string[] +---@field text? boolean ----@param cmd string ----@param opts gopher.RunnerOpts ----@return string[]|nil +---@param cmd (string|number)[] +---@param on_exit fun(out:vim.SystemCompleted) +---@param opts? gopher.RunnerOpts +---@return vim.SystemObj +function runner.async(cmd, on_exit, opts) + opts = opts or {} + return vim.system(cmd, { + cwd = opts.cwd or nil, + timeout = opts.timeout or c.timeout, + stdin = opts.stdin or nil, + text = opts.text or true, + }, on_exit) +end + +---@param cmd (string|number)[] +---@param opts? gopher.RunnerOpts +---@return vim.SystemCompleted function runner.sync(cmd, opts) - local output - Job:new({ - command = cmd, - args = opts.args, - cwd = opts.cwd, - on_stderr = function(_, data) - vim.print(data) - end, - on_exit = function(data, status) - output = data:result() - vim.schedule(function() - if opts.on_exit then - opts.on_exit(output, status) - end - end) - end, - }):sync(60000 --[[1 min]]) - return output + opts = opts or {} + return vim + .system(cmd, { + cwd = opts.cwd or nil, + timeout = opts.timeout or c.timeout, + stdin = opts.stdin or nil, + text = opts.text or true, + }) + :wait() end return runner diff --git a/lua/gopher/config.lua b/lua/gopher/config.lua index 18c08c3..94dbab9 100644 --- a/lua/gopher/config.lua +++ b/lua/gopher/config.lua @@ -33,6 +33,10 @@ local default_config = { ---@type number log_level = vim.log.levels.INFO, + -- timeout for running commands + ---@type number + timeout = 2000, + -- user specified paths to binaries ---@class gopher.ConfigCommand commands = { diff --git a/lua/gopher/gotests.lua b/lua/gopher/gotests.lua index 1c177b6..e97f26c 100644 --- a/lua/gopher/gotests.lua +++ b/lua/gopher/gotests.lua @@ -67,16 +67,12 @@ local function add_test(args) log.debug("generating tests with args: ", args) - return r.sync(c.commands.gotests, { - args = args, - on_exit = function(data, status) - if not status == 0 then - error("gotests failed: " .. data) - end + local rs = r.sync { c.commands.gotests, unpack(args) } + if rs.code ~= 0 then + error("gotests failed: " .. rs.stderr) + end - u.notify "unit test(s) generated" - end, - }) + u.notify "unit test(s) generated" end -- generate unit test for one function diff --git a/lua/gopher/health.lua b/lua/gopher/health.lua index 9fa0bed..4438c8a 100644 --- a/lua/gopher/health.lua +++ b/lua/gopher/health.lua @@ -4,9 +4,7 @@ local u = require "gopher._utils.health_util" local deps = { plugin = { - { lib = "dap", msg = "required for `gopher.dap`", optional = true }, - { lib = "plenary", msg = "required for everything in gopher.nvim", optional = false }, - { lib = "nvim-treesitter", msg = "required for everything in gopher.nvim", optional = false }, + { lib = "nvim-treesitter", msg = "required for everything in gopher.nvim" }, }, bin = { { @@ -14,17 +12,17 @@ local deps = { msg = "required for `:GoGet`, `:GoMod`, `:GoGenerate`, `:GoWork`, `:GoInstallDeps`", optional = false, }, - { bin = cmd.gomodifytags, msg = "required for `:GoTagAdd`, `:GoTagRm`", optional = false }, - { bin = cmd.impl, msg = "required for `:GoImpl`", optional = false }, - { bin = cmd.iferr, msg = "required for `:GoIfErr`", optional = false }, + { bin = cmd.gomodifytags, msg = "required for `:GoTagAdd`, `:GoTagRm`", optional = true }, + { bin = cmd.impl, msg = "required for `:GoImpl`", optional = true }, + { bin = cmd.iferr, msg = "required for `:GoIfErr`", optional = true }, { bin = cmd.gotests, msg = "required for `:GoTestAdd`, `:GoTestsAll`, `:GoTestsExp`", - optional = false, + optional = true, }, }, treesitter = { - { parser = "go", msg = "required for `gopher.nvim`", optional = false }, + { parser = "go", msg = "required for `gopher.nvim`" }, }, } @@ -34,11 +32,7 @@ function health.check() if u.is_lualib_found(plugin.lib) then u.ok(plugin.lib .. " installed") else - if plugin.optional then - u.warn(plugin.lib .. " not found, " .. plugin.msg) - else - u.error(plugin.lib .. " not found, " .. plugin.msg) - end + u.error(plugin.lib .. " not found, " .. plugin.msg) end end diff --git a/lua/gopher/iferr.lua b/lua/gopher/iferr.lua index a3a193e..3258bd8 100644 --- a/lua/gopher/iferr.lua +++ b/lua/gopher/iferr.lua @@ -4,27 +4,33 @@ ---@usage Execute `:GoIfErr` near any `err` variable to insert the check local c = require "gopher.config" +local u = require "gopher._utils" +local r = require "gopher._utils.runner" local log = require "gopher._utils.log" local iferr = {} --- That's Lua implementation: github.com/koron/iferr +-- That's Lua implementation: https://github.com/koron/iferr function iferr.iferr() - local boff = vim.fn.wordcount().cursor_bytes + local curb = vim.fn.wordcount().cursor_bytes local pos = vim.fn.getcurpos()[2] + local fpath = vim.fn.expand "%" - local data = vim.fn.systemlist((c.commands.iferr .. " -pos " .. boff), vim.fn.bufnr "%") - if vim.v.shell_error ~= 0 then - if string.find(data[1], "no functions at") then - vim.print "no function found" - log.warn("iferr: no function at " .. boff) + local rs = r.sync({ c.commands.iferr, "-pos", curb }, { + stdin = u.readfile_joined(fpath), + }) + + if rs.code ~= 0 then + if string.find(rs.stderr, "no functions at") then + u.notify("iferr: no function at " .. curb, vim.log.levels.ERROR) + log.warn("iferr: no function at " .. curb) return end - log.error("failed. output: " .. vim.inspect(data)) - error("iferr failed: " .. vim.inspect(data)) + log.error("ferr: failed. output: " .. rs.stderr) + error("iferr failed: " .. rs.stderr) end - vim.fn.append(pos, data) + vim.fn.append(pos, u.remove_empty_lines(vim.split(rs.stdout, "\n"))) vim.cmd [[silent normal! j=2j]] vim.fn.setpos(".", pos) end diff --git a/lua/gopher/impl.lua b/lua/gopher/impl.lua index 4e1ec9a..0ed3e76 100644 --- a/lua/gopher/impl.lua +++ b/lua/gopher/impl.lua @@ -43,7 +43,7 @@ local impl = {} local function get_struct() local ns = ts_utils.get_struct_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0))) if ns == nil then - u.deferred_notify("put cursor on a struct or specify a receiver", vim.log.levels.INFO) + u.notify "put cursor on a struct or specify a receiver" return "" end @@ -82,21 +82,14 @@ function impl.impl(...) recv = string.format("%s %s", recv_name, recv) end - local output = r.sync(c.impl, { - args = { - "-dir", - vim.fn.fnameescape(vim.fn.expand "%:p:h" --[[@as string]]), - recv, - iface, - }, - on_exit = function(data, status) - if not status == 0 then - error("impl failed: " .. data) - end - end, - }) + local rs = r.sync { c.impl, "-dir", vim.fn.fnameescape(vim.fn.expand "%:p:h"), recv, iface } + if rs.code ~= 0 then + error("failed to implement interface: " .. rs.stderr) + end local pos = vim.fn.getcurpos()[2] + local output = u.remove_empty_lines(vim.split(rs.stdout, "\n")) + table.insert(output, 1, "") vim.fn.append(pos, output) end diff --git a/lua/gopher/installer.lua b/lua/gopher/installer.lua index ac60c03..526e688 100644 --- a/lua/gopher/installer.lua +++ b/lua/gopher/installer.lua @@ -1,6 +1,7 @@ local c = require("gopher.config").commands local r = require "gopher._utils.runner" local u = require "gopher._utils" +local log = require "gopher._utils.log" local installer = {} local urls = { @@ -10,25 +11,42 @@ local urls = { iferr = "github.com/koron/iferr", } ----@param pkg string -local function install(pkg) - local url = urls[pkg] .. "@latest" - r.sync(c.go, { - args = { "install", url }, - on_exit = function(data, status) - if not status == 0 then - error("go install failed: " .. data) - return - end - u.notify("installed: " .. url) - end, - }) +---@param opt vim.SystemCompleted +---@param url string +local function handle_intall_exit(opt, url) + if opt.code ~= 0 then + u.deferred_notify("go install failed: " .. url) + log.error("go install failed:", "url", url, "opt", vim.inspect(opt)) + return + end + + u.deferred_notify("go install-ed: " .. url) +end + +---@param url string +local function install(url) + r.async({ c.go, "install", url }, function(opt) + handle_intall_exit(opt, url) + end) +end + +---@param url string +local function install_sync(url) + local rs = r.sync { c.go, "install", url } + handle_intall_exit(rs, url) end ---Install required go deps -function installer.install_deps() +---@param opts? {sync:boolean} +function installer.install_deps(opts) + opts = opts or {} for pkg, _ in pairs(urls) do - install(pkg) + local url = urls[pkg] .. "@latest" + if opts.sync then + install_sync(url) + else + install(url) + end end end diff --git a/lua/gopher/struct_tags.lua b/lua/gopher/struct_tags.lua index b3c9dc7..17e0d78 100644 --- a/lua/gopher/struct_tags.lua +++ b/lua/gopher/struct_tags.lua @@ -38,15 +38,8 @@ local function modify(...) return end - -- stylua: ignore - local cmd_args = { - "-transform", c.gotag.transform, - "-format", "json", - "-file", fpath, - "-w" - } - -- by struct name of line pos + local cmd_args = {} if ns.name == nil then local _, csrow, _, _ = unpack(vim.fn.getpos ".") table.insert(cmd_args, "-line") @@ -67,17 +60,24 @@ local function modify(...) table.insert(cmd_args, "json") end - local output = r.sync(c.commands.gomodifytags, { - args = cmd_args, - on_exit = function(data, status) - if not status == 0 then - error("gotag failed: " .. data) - end - end, - }) + local rs = r.sync { + c.commands.gomodifytags, + "-transform", + c.gotag.transform, + "-format", + "json", + "-w", + "-file", + fpath, + unpack(cmd_args), + } + + if rs.code ~= 0 then + error("failed to set tags " .. rs.stderr) + end -- decode value - local tagged = vim.json.decode(table.concat(output)) + local tagged = vim.json.decode(rs.stdout) if tagged.errors ~= nil or tagged.lines == nil diff --git a/plugin/gopher.vim b/plugin/gopher.vim index a219a1c..b61ed6e 100644 --- a/plugin/gopher.vim +++ b/plugin/gopher.vim @@ -11,4 +11,5 @@ command! -nargs=* GoGenerate :lua require"gopher".generate() command! GoCmt :lua require"gopher".comment() command! GoIfErr :lua require"gopher".iferr() command! GoInstallDeps :lua require"gopher".install_deps() +command! GoInstallDepsSync :lua require"gopher".install_deps({ sync = true }) command! GopherLog :lua vim.cmd("tabnew " .. require("gopher._utils.log").get_outfile()) diff --git a/scripts/minimal_init.lua b/scripts/minimal_init.lua index e59f06a..503bf49 100644 --- a/scripts/minimal_init.lua +++ b/scripts/minimal_init.lua @@ -8,17 +8,23 @@ local function install_plug(plugin) local package_root = root ".tests/site/pack/deps/start/" if not vim.uv.fs_stat(package_root .. name) then print("Installing " .. plugin) - vim.fn.mkdir(package_root, "p") - vim.fn.system { - "git", - "clone", - "--depth=1", - "https://github.com/" .. plugin .. ".git", - package_root .. "/" .. name, - } + vim + .system({ + "git", + "clone", + "--depth=1", + "https://github.com/" .. plugin .. ".git", + package_root .. "/" .. name, + }) + :wait() end end +install_plug "nvim-lua/plenary.nvim" +install_plug "nvim-treesitter/nvim-treesitter" +install_plug "echasnovski/mini.doc" -- used for docs generation +install_plug "echasnovski/mini.test" + vim.env.XDG_CONFIG_HOME = root ".tests/config" vim.env.XDG_DATA_HOME = root ".tests/data" vim.env.XDG_STATE_HOME = root ".tests/state" @@ -27,16 +33,16 @@ vim.env.XDG_CACHE_HOME = root ".tests/cache" vim.cmd [[set runtimepath=$VIMRUNTIME]] vim.opt.runtimepath:append(root()) vim.opt.packpath = { root ".tests/site" } -vim.notify = print - -install_plug "nvim-lua/plenary.nvim" -install_plug "nvim-treesitter/nvim-treesitter" -install_plug "echasnovski/mini.doc" -- used for docs generation -install_plug "echasnovski/mini.test" +vim.notify = vim.print -- install go treesitter parse require("nvim-treesitter.install").ensure_installed_sync "go" +require("gopher").setup { + log_level = vim.log.levels.OFF, + timeout = 4000, +} + -- setup mini.test only when running headless nvim if #vim.api.nvim_list_uis() == 0 then require("mini.test").setup { diff --git a/spec/fixtures/impl/closer_output.go b/spec/fixtures/impl/closer_output.go index 5e976f3..4f077f4 100644 --- a/spec/fixtures/impl/closer_output.go +++ b/spec/fixtures/impl/closer_output.go @@ -5,4 +5,3 @@ type CloserTest2 struct{} func (closertest *CloserTest2) Close() error { panic("not implemented") // TODO: Implement } - diff --git a/spec/fixtures/impl/reader_output.go b/spec/fixtures/impl/reader_output.go index 26df873..c384b23 100644 --- a/spec/fixtures/impl/reader_output.go +++ b/spec/fixtures/impl/reader_output.go @@ -4,5 +4,4 @@ func (r Read2) Read(p []byte) (n int, err error) { panic("not implemented") // TODO: Implement } - type Read2 struct{} diff --git a/spec/fixtures/impl/writer_output.go b/spec/fixtures/impl/writer_output.go index 19e8f6e..b69a70c 100644 --- a/spec/fixtures/impl/writer_output.go +++ b/spec/fixtures/impl/writer_output.go @@ -5,4 +5,3 @@ type WriterTest2 struct{} func (w *WriterTest2) Write(p []byte) (n int, err error) { panic("not implemented") // TODO: Implement } -