[mirror] Make your go dev experience better github.com/olexsmir/gopher.nvim
neovim golang
4
fork

Configure Feed

Select the types of activity you want to include in your feed.

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

authored by

Smirnov Oleksandr and committed by
GitHub
6016ca57 837897a7

+169 -126
+6 -1
.github/workflows/tests.yml
··· 47 47 key: ${{ runner.os }}-tests-${{ hashFiles('${{ github.workspace }}/.tests') }} 48 48 49 49 - name: Install Go bins 50 - run: nvim --headless -u "./scripts/minimal_init.lua" -c "GoInstallDeps" -c "qa!" 50 + run: | 51 + # TODO: install with :GoInstallDeps 52 + go install github.com/fatih/gomodifytags@latest 53 + go install github.com/josharian/impl@latest 54 + go install github.com/cweill/gotests/...@latest 55 + go install github.com/koron/iferr@latest 51 56 52 57 - name: Run Tests 53 58 run: |
+2 -4
README.md
··· 13 13 Requirements: 14 14 15 15 - **Neovim 0.10** or later 16 - - `go` treesitter parser, install by `:TSInstall go` 16 + - Treesitter `go` parser(`:TSInstall go`) 17 17 - [Go](https://github.com/golang/go) installed (tested on 1.23) 18 18 19 19 ```lua 20 20 { 21 21 "olexsmir/gopher.nvim", 22 22 ft = "go", 23 - -- branch = "develop", -- if you want develop branch 24 - -- keep in mind, it might break everything 23 + -- branch = "develop" 25 24 dependencies = { 26 - "nvim-lua/plenary.nvim", 27 25 "nvim-treesitter/nvim-treesitter", 28 26 }, 29 27 -- (optional) will update plugin's deps on every update
+20 -1
lua/gopher/_utils/init.lua
··· 3 3 local utils = {} 4 4 5 5 ---@param msg string 6 - ---@param lvl number 6 + ---@param lvl? number 7 7 function utils.deferred_notify(msg, lvl) 8 + lvl = lvl or vim.log.levels.INFO 8 9 vim.defer_fn(function() 9 10 vim.notify(msg, lvl, { 10 11 title = c.___plugin_name, ··· 21 22 title = c.___plugin_name, 22 23 }) 23 24 log.debug(msg) 25 + end 26 + 27 + ---@param path string 28 + ---@return string 29 + function utils.readfile_joined(path) 30 + return table.concat(vim.fn.readfile(path), "\n") 31 + end 32 + 33 + ---@param t string[] 34 + ---@return string[] 35 + function utils.remove_empty_lines(t) 36 + local res = {} 37 + for _, line in ipairs(t) do 38 + if line ~= "" then 39 + table.insert(res, line) 40 + end 41 + end 42 + return res 24 43 end 25 44 26 45 return utils
+31 -25
lua/gopher/_utils/runner/init.lua
··· 1 - local Job = require "plenary.job" 1 + local c = require "gopher.config" 2 2 local runner = {} 3 3 4 4 ---@class gopher.RunnerOpts 5 - ---@field args? string[] 6 - ---@field cwd? string? 7 - ---@field on_exit? fun(data:string, status:number) 5 + ---@field cwd? string 6 + ---@field timeout? number 7 + ---@field stdin? boolean|string|string[] 8 + ---@field text? boolean 8 9 9 - ---@param cmd string 10 - ---@param opts gopher.RunnerOpts 11 - ---@return string[]|nil 10 + ---@param cmd (string|number)[] 11 + ---@param on_exit fun(out:vim.SystemCompleted) 12 + ---@param opts? gopher.RunnerOpts 13 + ---@return vim.SystemObj 14 + function runner.async(cmd, on_exit, opts) 15 + opts = opts or {} 16 + return vim.system(cmd, { 17 + cwd = opts.cwd or nil, 18 + timeout = opts.timeout or c.timeout, 19 + stdin = opts.stdin or nil, 20 + text = opts.text or true, 21 + }, on_exit) 22 + end 23 + 24 + ---@param cmd (string|number)[] 25 + ---@param opts? gopher.RunnerOpts 26 + ---@return vim.SystemCompleted 12 27 function runner.sync(cmd, opts) 13 - local output 14 - Job:new({ 15 - command = cmd, 16 - args = opts.args, 17 - cwd = opts.cwd, 18 - on_stderr = function(_, data) 19 - vim.print(data) 20 - end, 21 - on_exit = function(data, status) 22 - output = data:result() 23 - vim.schedule(function() 24 - if opts.on_exit then 25 - opts.on_exit(output, status) 26 - end 27 - end) 28 - end, 29 - }):sync(60000 --[[1 min]]) 30 - return output 28 + opts = opts or {} 29 + return vim 30 + .system(cmd, { 31 + cwd = opts.cwd or nil, 32 + timeout = opts.timeout or c.timeout, 33 + stdin = opts.stdin or nil, 34 + text = opts.text or true, 35 + }) 36 + :wait() 31 37 end 32 38 33 39 return runner
+4
lua/gopher/config.lua
··· 33 33 ---@type number 34 34 log_level = vim.log.levels.INFO, 35 35 36 + -- timeout for running commands 37 + ---@type number 38 + timeout = 2000, 39 + 36 40 -- user specified paths to binaries 37 41 ---@class gopher.ConfigCommand 38 42 commands = {
+5 -9
lua/gopher/gotests.lua
··· 67 67 68 68 log.debug("generating tests with args: ", args) 69 69 70 - return r.sync(c.commands.gotests, { 71 - args = args, 72 - on_exit = function(data, status) 73 - if not status == 0 then 74 - error("gotests failed: " .. data) 75 - end 70 + local rs = r.sync { c.commands.gotests, unpack(args) } 71 + if rs.code ~= 0 then 72 + error("gotests failed: " .. rs.stderr) 73 + end 76 74 77 - u.notify "unit test(s) generated" 78 - end, 79 - }) 75 + u.notify "unit test(s) generated" 80 76 end 81 77 82 78 -- generate unit test for one function
+7 -13
lua/gopher/health.lua
··· 4 4 5 5 local deps = { 6 6 plugin = { 7 - { lib = "dap", msg = "required for `gopher.dap`", optional = true }, 8 - { lib = "plenary", msg = "required for everything in gopher.nvim", optional = false }, 9 - { lib = "nvim-treesitter", msg = "required for everything in gopher.nvim", optional = false }, 7 + { lib = "nvim-treesitter", msg = "required for everything in gopher.nvim" }, 10 8 }, 11 9 bin = { 12 10 { ··· 14 12 msg = "required for `:GoGet`, `:GoMod`, `:GoGenerate`, `:GoWork`, `:GoInstallDeps`", 15 13 optional = false, 16 14 }, 17 - { bin = cmd.gomodifytags, msg = "required for `:GoTagAdd`, `:GoTagRm`", optional = false }, 18 - { bin = cmd.impl, msg = "required for `:GoImpl`", optional = false }, 19 - { bin = cmd.iferr, msg = "required for `:GoIfErr`", optional = false }, 15 + { bin = cmd.gomodifytags, msg = "required for `:GoTagAdd`, `:GoTagRm`", optional = true }, 16 + { bin = cmd.impl, msg = "required for `:GoImpl`", optional = true }, 17 + { bin = cmd.iferr, msg = "required for `:GoIfErr`", optional = true }, 20 18 { 21 19 bin = cmd.gotests, 22 20 msg = "required for `:GoTestAdd`, `:GoTestsAll`, `:GoTestsExp`", 23 - optional = false, 21 + optional = true, 24 22 }, 25 23 }, 26 24 treesitter = { 27 - { parser = "go", msg = "required for `gopher.nvim`", optional = false }, 25 + { parser = "go", msg = "required for `gopher.nvim`" }, 28 26 }, 29 27 } 30 28 ··· 34 32 if u.is_lualib_found(plugin.lib) then 35 33 u.ok(plugin.lib .. " installed") 36 34 else 37 - if plugin.optional then 38 - u.warn(plugin.lib .. " not found, " .. plugin.msg) 39 - else 40 - u.error(plugin.lib .. " not found, " .. plugin.msg) 41 - end 35 + u.error(plugin.lib .. " not found, " .. plugin.msg) 42 36 end 43 37 end 44 38
+16 -10
lua/gopher/iferr.lua
··· 4 4 ---@usage Execute `:GoIfErr` near any `err` variable to insert the check 5 5 6 6 local c = require "gopher.config" 7 + local u = require "gopher._utils" 8 + local r = require "gopher._utils.runner" 7 9 local log = require "gopher._utils.log" 8 10 local iferr = {} 9 11 10 - -- That's Lua implementation: github.com/koron/iferr 12 + -- That's Lua implementation: https://github.com/koron/iferr 11 13 function iferr.iferr() 12 - local boff = vim.fn.wordcount().cursor_bytes 14 + local curb = vim.fn.wordcount().cursor_bytes 13 15 local pos = vim.fn.getcurpos()[2] 16 + local fpath = vim.fn.expand "%" 14 17 15 - local data = vim.fn.systemlist((c.commands.iferr .. " -pos " .. boff), vim.fn.bufnr "%") 16 - if vim.v.shell_error ~= 0 then 17 - if string.find(data[1], "no functions at") then 18 - vim.print "no function found" 19 - log.warn("iferr: no function at " .. boff) 18 + local rs = r.sync({ c.commands.iferr, "-pos", curb }, { 19 + stdin = u.readfile_joined(fpath), 20 + }) 21 + 22 + if rs.code ~= 0 then 23 + if string.find(rs.stderr, "no functions at") then 24 + u.notify("iferr: no function at " .. curb, vim.log.levels.ERROR) 25 + log.warn("iferr: no function at " .. curb) 20 26 return 21 27 end 22 28 23 - log.error("failed. output: " .. vim.inspect(data)) 24 - error("iferr failed: " .. vim.inspect(data)) 29 + log.error("ferr: failed. output: " .. rs.stderr) 30 + error("iferr failed: " .. rs.stderr) 25 31 end 26 32 27 - vim.fn.append(pos, data) 33 + vim.fn.append(pos, u.remove_empty_lines(vim.split(rs.stdout, "\n"))) 28 34 vim.cmd [[silent normal! j=2j]] 29 35 vim.fn.setpos(".", pos) 30 36 end
+7 -14
lua/gopher/impl.lua
··· 43 43 local function get_struct() 44 44 local ns = ts_utils.get_struct_node_at_pos(unpack(vim.api.nvim_win_get_cursor(0))) 45 45 if ns == nil then 46 - u.deferred_notify("put cursor on a struct or specify a receiver", vim.log.levels.INFO) 46 + u.notify "put cursor on a struct or specify a receiver" 47 47 return "" 48 48 end 49 49 ··· 82 82 recv = string.format("%s %s", recv_name, recv) 83 83 end 84 84 85 - local output = r.sync(c.impl, { 86 - args = { 87 - "-dir", 88 - vim.fn.fnameescape(vim.fn.expand "%:p:h" --[[@as string]]), 89 - recv, 90 - iface, 91 - }, 92 - on_exit = function(data, status) 93 - if not status == 0 then 94 - error("impl failed: " .. data) 95 - end 96 - end, 97 - }) 85 + local rs = r.sync { c.impl, "-dir", vim.fn.fnameescape(vim.fn.expand "%:p:h"), recv, iface } 86 + if rs.code ~= 0 then 87 + error("failed to implement interface: " .. rs.stderr) 88 + end 98 89 99 90 local pos = vim.fn.getcurpos()[2] 91 + local output = u.remove_empty_lines(vim.split(rs.stdout, "\n")) 92 + 100 93 table.insert(output, 1, "") 101 94 vim.fn.append(pos, output) 102 95 end
+33 -15
lua/gopher/installer.lua
··· 1 1 local c = require("gopher.config").commands 2 2 local r = require "gopher._utils.runner" 3 3 local u = require "gopher._utils" 4 + local log = require "gopher._utils.log" 4 5 local installer = {} 5 6 6 7 local urls = { ··· 10 11 iferr = "github.com/koron/iferr", 11 12 } 12 13 13 - ---@param pkg string 14 - local function install(pkg) 15 - local url = urls[pkg] .. "@latest" 16 - r.sync(c.go, { 17 - args = { "install", url }, 18 - on_exit = function(data, status) 19 - if not status == 0 then 20 - error("go install failed: " .. data) 21 - return 22 - end 23 - u.notify("installed: " .. url) 24 - end, 25 - }) 14 + ---@param opt vim.SystemCompleted 15 + ---@param url string 16 + local function handle_intall_exit(opt, url) 17 + if opt.code ~= 0 then 18 + u.deferred_notify("go install failed: " .. url) 19 + log.error("go install failed:", "url", url, "opt", vim.inspect(opt)) 20 + return 21 + end 22 + 23 + u.deferred_notify("go install-ed: " .. url) 24 + end 25 + 26 + ---@param url string 27 + local function install(url) 28 + r.async({ c.go, "install", url }, function(opt) 29 + handle_intall_exit(opt, url) 30 + end) 31 + end 32 + 33 + ---@param url string 34 + local function install_sync(url) 35 + local rs = r.sync { c.go, "install", url } 36 + handle_intall_exit(rs, url) 26 37 end 27 38 28 39 ---Install required go deps 29 - function installer.install_deps() 40 + ---@param opts? {sync:boolean} 41 + function installer.install_deps(opts) 42 + opts = opts or {} 30 43 for pkg, _ in pairs(urls) do 31 - install(pkg) 44 + local url = urls[pkg] .. "@latest" 45 + if opts.sync then 46 + install_sync(url) 47 + else 48 + install(url) 49 + end 32 50 end 33 51 end 34 52
+17 -17
lua/gopher/struct_tags.lua
··· 38 38 return 39 39 end 40 40 41 - -- stylua: ignore 42 - local cmd_args = { 43 - "-transform", c.gotag.transform, 44 - "-format", "json", 45 - "-file", fpath, 46 - "-w" 47 - } 48 - 49 41 -- by struct name of line pos 42 + local cmd_args = {} 50 43 if ns.name == nil then 51 44 local _, csrow, _, _ = unpack(vim.fn.getpos ".") 52 45 table.insert(cmd_args, "-line") ··· 67 60 table.insert(cmd_args, "json") 68 61 end 69 62 70 - local output = r.sync(c.commands.gomodifytags, { 71 - args = cmd_args, 72 - on_exit = function(data, status) 73 - if not status == 0 then 74 - error("gotag failed: " .. data) 75 - end 76 - end, 77 - }) 63 + local rs = r.sync { 64 + c.commands.gomodifytags, 65 + "-transform", 66 + c.gotag.transform, 67 + "-format", 68 + "json", 69 + "-w", 70 + "-file", 71 + fpath, 72 + unpack(cmd_args), 73 + } 74 + 75 + if rs.code ~= 0 then 76 + error("failed to set tags " .. rs.stderr) 77 + end 78 78 79 79 -- decode value 80 - local tagged = vim.json.decode(table.concat(output)) 80 + local tagged = vim.json.decode(rs.stdout) 81 81 if 82 82 tagged.errors ~= nil 83 83 or tagged.lines == nil
+1
plugin/gopher.vim
··· 11 11 command! GoCmt :lua require"gopher".comment() 12 12 command! GoIfErr :lua require"gopher".iferr() 13 13 command! GoInstallDeps :lua require"gopher".install_deps() 14 + command! GoInstallDepsSync :lua require"gopher".install_deps({ sync = true }) 14 15 command! GopherLog :lua vim.cmd("tabnew " .. require("gopher._utils.log").get_outfile())
+20 -14
scripts/minimal_init.lua
··· 8 8 local package_root = root ".tests/site/pack/deps/start/" 9 9 if not vim.uv.fs_stat(package_root .. name) then 10 10 print("Installing " .. plugin) 11 - vim.fn.mkdir(package_root, "p") 12 - vim.fn.system { 13 - "git", 14 - "clone", 15 - "--depth=1", 16 - "https://github.com/" .. plugin .. ".git", 17 - package_root .. "/" .. name, 18 - } 11 + vim 12 + .system({ 13 + "git", 14 + "clone", 15 + "--depth=1", 16 + "https://github.com/" .. plugin .. ".git", 17 + package_root .. "/" .. name, 18 + }) 19 + :wait() 19 20 end 20 21 end 21 22 23 + install_plug "nvim-lua/plenary.nvim" 24 + install_plug "nvim-treesitter/nvim-treesitter" 25 + install_plug "echasnovski/mini.doc" -- used for docs generation 26 + install_plug "echasnovski/mini.test" 27 + 22 28 vim.env.XDG_CONFIG_HOME = root ".tests/config" 23 29 vim.env.XDG_DATA_HOME = root ".tests/data" 24 30 vim.env.XDG_STATE_HOME = root ".tests/state" ··· 27 33 vim.cmd [[set runtimepath=$VIMRUNTIME]] 28 34 vim.opt.runtimepath:append(root()) 29 35 vim.opt.packpath = { root ".tests/site" } 30 - vim.notify = print 31 - 32 - install_plug "nvim-lua/plenary.nvim" 33 - install_plug "nvim-treesitter/nvim-treesitter" 34 - install_plug "echasnovski/mini.doc" -- used for docs generation 35 - install_plug "echasnovski/mini.test" 36 + vim.notify = vim.print 36 37 37 38 -- install go treesitter parse 38 39 require("nvim-treesitter.install").ensure_installed_sync "go" 40 + 41 + require("gopher").setup { 42 + log_level = vim.log.levels.OFF, 43 + timeout = 4000, 44 + } 39 45 40 46 -- setup mini.test only when running headless nvim 41 47 if #vim.api.nvim_list_uis() == 0 then
-1
spec/fixtures/impl/closer_output.go
··· 5 5 func (closertest *CloserTest2) Close() error { 6 6 panic("not implemented") // TODO: Implement 7 7 } 8 -
-1
spec/fixtures/impl/reader_output.go
··· 4 4 panic("not implemented") // TODO: Implement 5 5 } 6 6 7 - 8 7 type Read2 struct{}
-1
spec/fixtures/impl/writer_output.go
··· 5 5 func (w *WriterTest2) Write(p []byte) (n int, err error) { 6 6 panic("not implemented") // TODO: Implement 7 7 } 8 -