all repos

gopher.nvim @ 6016ca5

Minimalistic plugin for Go development
16 files changed, 169 insertions(+), 126 deletions(-)
refactor: use vim.system instead of pleanry (#85)

* refactor!: migrate to vim.system

* refactor(gotests): use vim.system

* refactor(iferr): use vim.system

* refactor(impl): use vim.system

* refactor(installer): use vim.system and add sync mode

* test: fix gotests' tests

* refactor(struct_tags): use vim.system

* chore(ci): install all deps explicitly

* refactor(installer)!: add sync as an option

* docs: update readme
Author: Smirnov Oleksandr ss2316544@gmail.com
Committed by: GitHub noreply@github.com
Committed at: 2025-03-02 16:31:50 +0200
Parent: 837897a
M .github/workflows/tests.yml

@@ -47,7 +47,12 @@ ~/go/pkg/mod

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: |
M README.md

@@ -13,17 +13,15 @@

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
M 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,

@@ -21,6 +22,24 @@ vim.notify(msg, lvl, {

title = c.___plugin_name, }) 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
M 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
M lua/gopher/config.lua

@@ -33,6 +33,10 @@ -- log level, you might consider using DEBUG or TRACE for debugging the plugin

---@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 = {
M lua/gopher/gotests.lua

@@ -67,16 +67,12 @@ table.insert(args, vim.fn.expand "%")

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
M 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 @@ bin = cmd.go,

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 @@ for _, plugin in ipairs(deps.plugin) do

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
M lua/gopher/iferr.lua

@@ -4,27 +4,33 @@ ---@text If you're using `iferr` tool, this module provides a way to automatically insert `if err != nil` check.

---@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
M lua/gopher/impl.lua

@@ -43,7 +43,7 @@ ---@private

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 @@ recv_name = select(#args - 2, ...)

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
M 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 @@ gotests = "github.com/cweill/gotests/...",

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
M lua/gopher/struct_tags.lua

@@ -38,15 +38,8 @@ if ns == nil then

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 @@ if #arg == 1 and arg[1] ~= "-clear-tags" then

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
M plugin/gopher.vim

@@ -11,4 +11,5 @@ command! -nargs=* GoGenerate :lua require"gopher".generate(<f-args>)

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())
M scripts/minimal_init.lua

@@ -8,17 +8,23 @@ local name = plugin:match ".*/(.*)"

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,15 +33,15 @@

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
M spec/fixtures/impl/closer_output.go

@@ -5,4 +5,3 @@

func (closertest *CloserTest2) Close() error { panic("not implemented") // TODO: Implement } -
M 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{}
M spec/fixtures/impl/writer_output.go

@@ -5,4 +5,3 @@

func (w *WriterTest2) Write(p []byte) (n int, err error) { panic("not implemented") // TODO: Implement } -