diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ed96bf8..25e53de 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,10 +18,6 @@ jobs: url: https://github.com/neovim/neovim/releases/download/v0.7.2/nvim-linux64.tar.gz manager: sudo snap packages: go - - os: ubuntu-20.04 - url: https://github.com/neovim/neovim/releases/download/v0.6.1/nvim-linux64.tar.gz - manager: sudo snap - packages: go steps: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 diff --git a/README.md b/README.md index b706d2d..326d349 100644 --- a/README.md +++ b/README.md @@ -314,8 +314,8 @@ gofmt) | command | Description | | --------------------- | --------------------------- | -| GoFmt {opts} | goline + gofumpt | -| GoImport | goline + goimport + gofumpt | +| GoFmt {opts} | default: gofumpt | +| GoImport | default: goimport | | GoImport package_path | gopls add_import package | {opts} : ``-a`` format all buffers @@ -378,6 +378,19 @@ Notes: | GoAltS / GoAltS! | open alternative go file in split | | GoAltV / GoAltV! | open alternative go file in vertical split | +## Go Mock + +go mock with mockgen is supported +| command | Description | +| ---------------- | ------------------------------------------------------- | +| GoMockGen | default: generate mocks for current file | + options: + -s source mode(default) + -i interface mode, provide interface name or put cursor on interface + -p package name default: mocks + -d destination directory, default: ./mocks + + ## Comments and Doc Auto doc (to suppress golang-lint warning), generate comments by treesitter parsing result @@ -541,7 +554,7 @@ require('go').setup({ goimport='gopls', -- goimport command, can be gopls[default] or goimport fillstruct = 'gopls', -- can be nil (use fillstruct, slower) and gopls gofmt = 'gofumpt', --gofmt cmd, - max_line_len = 120, -- max line length in goline format + max_line_len = 128, -- max line length in golines format, Target maximum line length for golines tag_transform = false, -- can be transform option("snakecase", "camelcase", etc) check gomodifytags for details and more options gotests_template = "", -- sets gotests -template parameter (check gotests for details) gotests_template_dir = "", -- sets gotests -template_dir parameter (check gotests for details) diff --git a/doc/go.txt b/doc/go.txt index cf3c6b3..ec58205 100644 --- a/doc/go.txt +++ b/doc/go.txt @@ -297,6 +297,14 @@ COMMANDS *go-nvim-commands* :GoDoc {options} *:GoDoc* e.g. GoDoc fmt.Println +:GoMockGen {options} *:GoDoc* + Generate mock with go mock + options: + -s source mode(default) + -i interface mode, provide interface name or put cursor on interface + -p package name default: mocks + -d destination directory, default: ./mocks + :GoPkgOutline {options} *:GoPkgOutline* show symbols inside a specific package in side panel/loclist options: -f (floating win), -p package_name diff --git a/lua/go.lua b/lua/go.lua index 139e84b..7f988ce 100644 --- a/lua/go.lua +++ b/lua/go.lua @@ -1,6 +1,8 @@ -- some of commands extracted from gopher.vim local go = {} local vfn = vim.fn +local create_cmd = vim.api.nvim_create_user_command + -- Keep this in sync with README.md -- Keep this in sync with doc/go.txt _GO_NVIM_CFG = { @@ -8,7 +10,7 @@ _GO_NVIM_CFG = { goimport = "gopls", -- if set to 'gopls' will use gopls format, also goimport fillstruct = "gopls", gofmt = "gofumpt", -- if set to gopls will use gopls format - max_line_len = 120, + max_line_len = 128, tag_transform = false, gotests_template = "", -- sets gotests -template parameter (check gotests for details) @@ -209,6 +211,9 @@ function go.setup(cfg) vim.cmd([[command! -bang GoCallstack lua require"go.guru".callstack(-1)]]) vim.cmd([[command! -bang GoChannel lua require"go.guru".channel_peers(-1)]]) + + + if _GO_NVIM_CFG.dap_debug then dap_config() vim.cmd( @@ -231,6 +236,16 @@ function go.setup(cfg) vim.cmd([[command! GoDbgStop lua require'go.dap'.stop(true)]]) vim.cmd([[command! GoDbgContinue lua require'dap'.continue()]]) + create_cmd('GoMockGen', + require"go.mockgen".run, + { + nargs = "*", + -- bang = true, + complete = function(ArgLead, CmdLine, CursorPos) + -- return completion candidates as a list-like table + return { '-p', '-d', '-i', '-s'} + end, + }) end require("go.project").load_project() @@ -257,6 +272,9 @@ function go.setup(cfg) if _GO_NVIM_CFG.textobjects then require("go.ts.textobjects").setup() end + + + require("go.env").setup() end diff --git a/lua/go/dap.lua b/lua/go/dap.lua index 6371663..d10987d 100644 --- a/lua/go/dap.lua +++ b/lua/go/dap.lua @@ -48,10 +48,13 @@ local function setup_telescope() bind.nvim_load_mapping(ts_keys) end +local keymaps_backup local function keybind() if not _GO_NVIM_CFG.dap_debug_keymap then return end + -- TODO: put keymaps back + keymaps_backup = vim.api.nvim_get_keymap("n") keys = { -- DAP -- -- run diff --git a/lua/go/install.lua b/lua/go/install.lua index e851c7e..5d69270 100644 --- a/lua/go/install.lua +++ b/lua/go/install.lua @@ -20,7 +20,8 @@ local url = { dlv = "github.com/go-delve/delve/cmd/dlv", ginkgo = "github.com/onsi/ginkgo/ginkgo", richgo = "github.com/kyoh86/richgo", - gotestsum = "gotest.tools/gotestsum" + gotestsum = "gotest.tools/gotestsum", + mockgen = "github.com/golang/mock" } local tools = {} diff --git a/lua/go/mockgen.lua b/lua/go/mockgen.lua new file mode 100644 index 0000000..28c94f9 --- /dev/null +++ b/lua/go/mockgen.lua @@ -0,0 +1,113 @@ +-- local ts_utils = require 'nvim-treesitter.ts_utils' +local utils = require("go.utils") +local log = utils.log +local vfn = vim.fn +local mockgen = "mockgen" -- GoMock f *Foo io.Writer + +-- use ts to get name +local function get_interface_name() + local row, col = unpack(vim.api.nvim_win_get_cursor(0)) + local name = require("go.ts.go").get_interface_node_at_pos(row, col) + if name == nil then + return nil + end + utils.log(name) + if name == nil then + return "" + end + local node_name = name.name + -- let move the cursor to end of line of struct name + local dim = name.dim.e + -- let move cursor + local r, c = dim.r, dim.c + utils.log("move cusror to ", r, c) + vim.api.nvim_win_set_cursor(0, { r, c }) + return node_name +end + +local run = function(opts) + require("go.install").install(mockgen) + + local long_opts = { + package = "p", + source = "s", + destination = "d", + interface = "i", + } + + local getopt = require("go.alt_getopt") + local short_opts = "p:d:i:s" + local args = opts.fargs or {} + log(args) + + local optarg, _, reminder = getopt.get_opts(args, short_opts, long_opts) + local mockgen_cmd = { mockgen } + utils.log(arg) + + local sep = require("go.utils").sep() + + local ifname = get_interface_name() + + if optarg["i"] ~= nil and #optarg["i"] > 0 then + ifname = optarg["i"] + end + + local fpath = utils.rel_path(true) -- rel/path/only + log(fpath, mockgen_cmd) + local sname = vfn.expand("%:t") -- name.go only + + if fpath ~= "" then + fpath = fpath .. sep + end + + if ifname == "" then + -- source mode default + table.insert(mockgen_cmd, "-source") + table.insert(mockgen_cmd, fpath .. sname) + else + -- need to get the import path + local bufnr = vim.api.nvim_get_current_buf() + + local pkg = require("go.package").pkg_from_path(nil, bufnr) + if pkg ~= nil and type(pkg) == "table" and pkg[1] then + table.insert(mockgen_cmd, pkg[1]) + end + table.insert(mockgen_cmd, ifname) + end + + local pkgname = optarg["p"] or "mocks" + table.insert(mockgen_cmd, "-package") + table.insert(mockgen_cmd, pkgname) + + local dname = fpath .. pkgname .. sep .. "mock_" .. sname + table.insert(mockgen_cmd, "-destination") + table.insert(mockgen_cmd, dname) + + log(mockgen_cmd) + + utils.log(mockgen_cmd) + -- vim.cmd("normal! $%") -- do a bracket match. changed to treesitter + local opts = { + on_exit = function(code, signal, data) + if code ~= 0 or signal ~= 0 then + -- there will be error popup from runner + -- utils.warn("mockgen failed" .. vim.inspect(data)) + return + end + data = vim.split(data, "\n") + data = utils.handle_job_data(data) + if not data then + return + end + -- + vim.schedule(function() + utils.info(vfn.join(mockgen_cmd, " ") .. " finished " .. vfn.join(data, " ")) + end) + end, + } + local runner = require("go.runner") + runner.run(mockgen_cmd, opts) + return mockgen_cmd +end + +return { run = run } diff --git a/lua/go/runner.lua b/lua/go/runner.lua index 279f0a8..753a9d0 100644 --- a/lua/go/runner.lua +++ b/lua/go/runner.lua @@ -35,11 +35,14 @@ local run = function(cmd, opts) local output_buf = "" local function update_chunk_fn(err, chunk) if err then - return vim.notify("error " .. tostring(err) .. vim.inspect(chunk or {}), vim.lsp.log_levels.INFO) + vim.schedule(function() + vim.notify("error " .. tostring(err) .. vim.inspect(chunk or ""), vim.lsp.log_levels.WARN) + end) end if chunk then output_buf = output_buf .. chunk end + log(err, chunk) end local update_chunk = opts.update_chunk or update_chunk_fn @@ -58,47 +61,43 @@ local run = function(cmd, opts) handle:close() log(output_buf) - if code == 0 then - if opts and opts.on_exit then - -- if on_exit hook is on the hook output is what we want to show in loc - -- this avoid show samething in both on_exit and loc - output_buf = opts.on_exit(code, signal, output_buf) - if not output_buf then - return - end - end - if code ~= 0 then - vim.notify(cmd_str .. " failed exit code " .. tostring(code) .. output_buf, - vim.lsp.log_levels.WARN) + if opts and opts.on_exit then + -- if on_exit hook is on the hook output is what we want to show in loc + -- this avoid show samething in both on_exit and loc + output_buf = opts.on_exit(code, signal, output_buf) + if not output_buf then return end - if output_buf ~= "" then - local lines = vim.split(output_buf, "\n", true) - lines = util.handle_job_data(lines) - local locopts = { - title = vim.inspect(cmd), - lines = lines, - } - if opts.efm then - locopts.efm = opts.efm - end - log(locopts) - if #lines > 0 then - vim.schedule(function() - vim.fn.setloclist(0, {}, " ", locopts) - vim.cmd("lopen") - end) - end + end + if code ~= 0 then + log("failed to run", code, output_buf) + + output_buf = output_buf or "" + vim.notify(cmd_str .. " failed exit code " .. tostring(code) .. output_buf, vim.lsp.log_levels.WARN) + end + if output_buf ~= "" then + local lines = vim.split(output_buf, "\n", true) + lines = util.handle_job_data(lines) + local locopts = { + title = vim.inspect(cmd), + lines = lines, + } + if opts.efm then + locopts.efm = opts.efm + end + log(locopts) + if #lines > 0 then + vim.schedule(function() + vim.fn.setloclist(0, {}, " ", locopts) + vim.cmd("lopen") + end) end end end ) uv.read_start(stderr, function(err, data) - assert(not err, err) - if data then - vim.notify(string.format("stderr chunk %s %s", stderr, vim.inspect(data)), vim.lsp.log_levels.DEBUG) - end + update_chunk("stderr: " .. tostring(err), data) end) stdout:read_start(update_chunk) -- stderr:read_start(update_chunk) diff --git a/lua/go/utils.lua b/lua/go/utils.lua index 6044359..b2640f8 100644 --- a/lua/go/utils.lua +++ b/lua/go/utils.lua @@ -250,8 +250,7 @@ util.log = function(...) end end -util.trace = function(...) -end +util.trace = function(...) end local rhs_options = {} @@ -443,6 +442,7 @@ function util.info(msg) end function util.rel_path(folder) + -- maybe expand('%:p:h:t') local mod = "%:p" if folder then mod = "%:p:h" @@ -451,10 +451,18 @@ function util.rel_path(folder) local workfolders = vim.lsp.buf.list_workspace_folders() - if workfolders ~= nil and next(workfolders) then + if fn.empty(workfolders) == 0 then fpath = "." .. fpath:sub(#workfolders[1] + 1) + else + fpath = fn.fnamemodify(fn.expand(mod), ":p:.") + end + + util.log(fpath:sub(#fpath), fpath, util.sep()) + if fpath:sub(#fpath) == util.sep() then + fpath = fpath:sub(1, #fpath - 1) + util.log(fpath) end - return "." .. util.sep() .. fn.fnamemodify(fn.expand(mod), ":.") + return fpath end function util.trim(s) diff --git a/lua/tests/fixtures/fmt/goimports2.go b/lua/tests/fixtures/fmt/goimports2.go index 1c45ecc..ccc3f73 100644 --- a/lua/tests/fixtures/fmt/goimports2.go +++ b/lua/tests/fixtures/fmt/goimports2.go @@ -2,5 +2,5 @@ package main func foo() { fmt.Println("go.nvim") - time.Date(2020, 1, 1, 1, 1, 1, 1, nil) + time.Date(2020, 1, 1, 1, 1, 1, 1, nil) } diff --git a/lua/tests/fixtures/ts/go.mod b/lua/tests/fixtures/ts/go.mod new file mode 100644 index 0000000..6967dfd --- /dev/null +++ b/lua/tests/fixtures/ts/go.mod @@ -0,0 +1,3 @@ +module main + +go 1.18 diff --git a/lua/tests/fixtures/ts/go.sum b/lua/tests/fixtures/ts/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/lua/tests/fixtures/ts/interfaces.go b/lua/tests/fixtures/ts/interfaces.go index f69db13..cde8671 100644 --- a/lua/tests/fixtures/ts/interfaces.go +++ b/lua/tests/fixtures/ts/interfaces.go @@ -3,46 +3,53 @@ package main import ( - "fmt" - "math" + "fmt" + "math" + "reflect" ) type Geometry interface { - Area() float64 - perim() float64 + Area() float64 + perim() float64 } type rect struct { - width, height float64 + width, height float64 } + type circle struct { - radius float64 + radius float64 } func (r rect) Area() float64 { - return r.width * r.height + return r.width * r.height } + func (r rect) perim() float64 { - return 2*r.width + 2*r.height + return 2*r.width + 2*r.height } func (c circle) Area() float64 { - return math.Pi * c.radius * c.radius + return math.Pi * c.radius * c.radius } + func (c circle) perim() float64 { - return 2 * math.Pi * c.radius + return 2 * math.Pi * c.radius } func measure(g Geometry) { - fmt.Println(g) - fmt.Println(g.Area()) - fmt.Println(g.perim()) + fmt.Println(g) + fmt.Println(g.Area()) + fmt.Println(g.perim()) } func main() { - r := rect{width: 3, height: 4} - c := circle{radius: 5} + r := rect{width: 3, height: 4} + c := circle{radius: 5} + + var b Geometry = rect{} + fmt.Println(reflect.TypeOf(b).PkgPath()) - measure(r) - measure(c) + measure(r) + measure(c) } diff --git a/lua/tests/fixtures/ts/pkg/interface.go b/lua/tests/fixtures/ts/pkg/interface.go new file mode 100644 index 0000000..222cd05 --- /dev/null +++ b/lua/tests/fixtures/ts/pkg/interface.go @@ -0,0 +1,43 @@ +package pkg + +import ( + "fmt" + "math" +) + +type Geometry interface { + area() float64 + perim() float64 +} + +type rect struct { + width float64 `-line:"width"` + height float64 `-line:"height"` +} + +func (r rect) area() float64 { + return r.width * r.height +} + +func (r rect) perim() float64 { + return 2*r.width + 2*r.height +} + +type circle struct { + radius float64 +} + +func (c circle) area() float64 { + return math.Pi * c.radius * c.radius +} + +func (c circle) perim() float64 { + return 2 * math.Pi * c.radius +} + +func measure(g Geometry) int { + fmt.Println(g) + fmt.Println(g.area()) + fmt.Println(g.perim()) + return 1 +} diff --git a/lua/tests/fixtures/ts/playlist.go b/lua/tests/fixtures/ts/playlist.go index f58af81..ea06523 100644 --- a/lua/tests/fixtures/ts/playlist.go +++ b/lua/tests/fixtures/ts/playlist.go @@ -69,7 +69,7 @@ func (p *playlist) NextSong() *song { // exported return p.nowPlaying } -func main() { +func play() { playlistName := "myplaylist" myPlaylist := createPlaylist(playlistName) fmt.Println("Created playlist") diff --git a/lua/tests/go_mockgen_spec.lua b/lua/tests/go_mockgen_spec.lua new file mode 100644 index 0000000..f4e01f1 --- /dev/null +++ b/lua/tests/go_mockgen_spec.lua @@ -0,0 +1,45 @@ +local _ = require("plenary/busted") +local fn = vim.fn + +local eq = assert.are.same +local cur_dir = vim.fn.expand("%:p:h") +-- local status = require("plenary.reload").reload_module("go.nvim") +-- status = require("plenary.reload").reload_module("nvim-treesitter") +-- local ulog = require('go.utils').log +describe("should run mockgen", function() + vim.cmd([[packadd go.nvim]]) + vim.cmd([[packadd nvim-treesitter]]) + status = require("plenary.reload").reload_module("go.nvim") + status = require("plenary.reload").reload_module("nvim-treesitter/nvim-treesitter") + + require("go").setup({ verbose = true }) + it("should run mockgen", function() + -- + local path = cur_dir .. "/lua/tests/fixtures/ts/interface.go" -- %:p:h ? %:p + local got = "pkg/mocks/mock_interface.go" + local cmd = " silent exe 'e " .. path .. "'" + vim.cmd(cmd) + vim.cmd("cd lua/tests/fixtures/ts") + local bufn = fn.bufnr("") + + vim.fn.setpos(".", { bufn, 10, 11, 0 }) + + vim.bo.filetype = "go" + + local gomockgen = require("go.mockgen") + local cmd = gomockgen.run({ args = { "-s" } }) + vim.wait(400, function() end) + + local expected_cmd = { + "mockgen", + "-package", + "mocks", + "-source", + "interface.go", + "-destination", + "mocks/mock_interface.go", + } + eq(cmd, expected_cmd) + eq(fn.filereadable(got), 1) + end) +end)