diff --git a/README.md b/README.md index 1f34793..c6ff8f7 100644 --- a/README.md +++ b/README.md @@ -529,6 +529,32 @@ Visual select the json and run `GoJson2Struct youStructName` -bang will put result to register `a` if ["x] specified, will put get json from clipboard +### Generate return value + +* GoGenReturn + +create return value for current function +e.g. if we have +```go +func Foo() (int, error) { + return 1, nil +} +``` +and in your code you cursor on Foo + +```go +Foo() +``` +will generate +```go +i, err := Foo() +if err != nil { + return +} +``` + + + ### Commands diff --git a/doc/go.txt b/doc/go.txt index 1082893..b383602 100644 --- a/doc/go.txt +++ b/doc/go.txt @@ -330,6 +330,9 @@ COMMANDS *go-nvim-commands* Convert json (visual select) to go struct. bang: put result to register \"x : get json from register x + +:GoGenReturn + generate return values for current function ============================================================================== OPTIONS *go-nvim-options* diff --git a/lua/go/commands.lua b/lua/go/commands.lua index 730b256..1f41702 100644 --- a/lua/go/commands.lua +++ b/lua/go/commands.lua @@ -326,6 +326,11 @@ return { create_cmd('GoEnv', function(opts) require('go.env').load_env(unpack(opts.fargs)) end, { nargs = '*' }) + + create_cmd('GoGenReturn', function() + require('go.lsp').hover_returns() + end) + create_cmd('GoJson2Struct', function(opts) require('go.json2struct').run(opts) end, { nargs = '*', bang = true, diff --git a/lua/go/gopls.lua b/lua/go/gopls.lua index 1f6a906..f2314b3 100644 --- a/lua/go/gopls.lua +++ b/lua/go/gopls.lua @@ -1,4 +1,4 @@ -local utils = require("go.utils") +local utils = require('go.utils') local log = utils.log local vfn = vim.fn @@ -8,40 +8,40 @@ local cmds = {} -- "executeCommandProvider":{"commands":["gopls.add_dependency","gopls.add_import","gopls.apply_fix","gopls.check_upgrades","gopls.gc_details","gopls.generate","gopls.generate_gopls_mod","gopls.go_get_package","gopls.list_known_packages","gopls.regenerate_cgo","gopls.remove_dependency","gopls.run_tests","gopls.start_debugging","gopls.test","gopls.tidy","gopls.toggle_gc_details","gopls.update_go_sum","gopls.upgrade_dependency","gopls.vendor","gopls.workspace_metadata"]} local gopls_cmds = { - "gopls.add_dependency", - "gopls.add_import", - "gopls.apply_fix", - "gopls.check_upgrades", - "gopls.gc_details", - "gopls.generate", - "gopls.generate_gopls_mod", - "gopls.go_get_package", - "gopls.list_known_packages", - "gopls.list_imports", - "gopls.regenerate_cgo", - "gopls.remove_dependency", - "gopls.run_tests", - "gopls.start_debugging", - "gopls.test", - "gopls.tidy", - "gopls.toggle_gc_details", - "gopls.update_go_sum", - "gopls.upgrade_dependency", - "gopls.vendor", - "gopls.workspace_metadata", + 'gopls.add_dependency', + 'gopls.add_import', + 'gopls.apply_fix', + 'gopls.check_upgrades', + 'gopls.gc_details', + 'gopls.generate', + 'gopls.generate_gopls_mod', + 'gopls.go_get_package', + 'gopls.list_known_packages', + 'gopls.list_imports', + 'gopls.regenerate_cgo', + 'gopls.remove_dependency', + 'gopls.run_tests', + 'gopls.start_debugging', + 'gopls.test', + 'gopls.tidy', + 'gopls.toggle_gc_details', + 'gopls.update_go_sum', + 'gopls.upgrade_dependency', + 'gopls.vendor', + 'gopls.workspace_metadata', } local gopls_with_result = { - "gopls.gc_details", - "gopls.list_known_packages", - "gopls.list_imports", + 'gopls.gc_details', + 'gopls.list_known_packages', + 'gopls.list_imports', } local function check_for_error(msg) - if msg ~= nil and type(msg[1]) == "table" then + if msg ~= nil and type(msg[1]) == 'table' then for k, v in pairs(msg[1]) do - if k == "error" then - log("LSP", v.message) + if k == 'error' then + log('LSP', v.message) break end end @@ -49,7 +49,7 @@ local function check_for_error(msg) end for _, value in ipairs(gopls_cmds) do - local fname = string.sub(value, #"gopls." + 1) + local fname = string.sub(value, #'gopls.' + 1) cmds[fname] = function(arg) log(fname) local b = vim.api.nvim_get_current_buf() @@ -57,13 +57,13 @@ for _, value in ipairs(gopls_cmds) do local arguments = { { URI = uri } } local ft = vim.bo.filetype - if ft == "gomod" or ft == "gosum" then + if ft == 'gomod' or ft == 'gosum' then arguments = { { URIs = { uri } } } end - arguments = { vim.tbl_extend("keep", arguments[1], arg or {}) } + arguments = { vim.tbl_extend('keep', arguments[1], arg or {}) } if vim.tbl_contains(gopls_with_result, value) then - local resp = vim.lsp.buf_request_sync(b, "workspace/executeCommand", { + local resp = vim.lsp.buf_request_sync(b, 'workspace/executeCommand', { command = value, arguments = arguments, }, 2000) @@ -91,7 +91,7 @@ M.import = function(path) end M.list_imports = function(path) - path = path or vim.fn.expand("%:p") + path = path or vim.fn.expand('%:p') local resp = cmds.list_imports({ URI = path, }) @@ -101,8 +101,8 @@ M.list_imports = function(path) for k, val in pairs(v.result) do result[k] = {} for _, imp in ipairs(val) do - if imp.Name and imp.Name ~= "" then - table.insert(result[k], imp.Name .. ":" .. imp.Path) + if imp.Name and imp.Name ~= '' then + table.insert(result[k], imp.Name .. ':' .. imp.Path) else table.insert(result[k], imp.Path) end @@ -132,33 +132,33 @@ end -- check_for_upgrades({Modules = {'package'}}) function M.version() - local cache_dir = vfn.stdpath("cache") - local path = string.format("%s%sversion.txt", cache_dir, utils.sep()) + local cache_dir = vfn.stdpath('cache') + local path = string.format('%s%sversion.txt', cache_dir, utils.sep()) local cfg = _GO_NVIM_CFG or {} - local gopls = cfg.gopls_cmd or { "gopls" } + local gopls = cfg.gopls_cmd or { 'gopls' } if vfn.executable(gopls[1]) == 0 then - vim.notify("gopls not found", vim.log.levels.WARN) + vim.notify('gopls not found', vim.log.levels.WARN) return end - vfn.jobstart({ gopls[1], "version" }, { + vfn.jobstart({ gopls[1], 'version' }, { on_stdout = function(_, data, _) - local msg = "" - if type(data) == "table" and #data > 0 then - data = table.concat(data, " ") + local msg = '' + if type(data) == 'table' and #data > 0 then + data = table.concat(data, ' ') end if #data > 1 then msg = msg .. data end log(msg) - local version = string.match(msg, "%s+v([%d%.]+)%s+") + local version = string.match(msg, '%s+v([%d%.]+)%s+') if version == nil then log(version, msg) return end - local f = io.open(path, "w+") + local f = io.open(path, 'w+') if f == nil then return end @@ -168,25 +168,25 @@ function M.version() end, }) - local f = io.open(path, "r") + local f = io.open(path, 'r') if f == nil then - local version_cmd = gopls[1] .. " version" - return vfn.system(version_cmd):match("%s+v([%d%.]+)%s+") + local version_cmd = gopls[1] .. ' version' + return vfn.system(version_cmd):match('%s+v([%d%.]+)%s+') end - local version = f:read("*l") + local version = f:read('*l') f:close() log(version) return version end local get_current_gomod = function() - local file = io.open("go.mod", "r") + local file = io.open('go.mod', 'r') if file == nil then return nil end local first_line = file:read() - local mod_name = first_line:gsub("module ", "") + local mod_name = first_line:gsub('module ', '') file:close() return mod_name end @@ -198,16 +198,16 @@ local setups = { completionItem = { commitCharactersSupport = true, deprecatedSupport = true, - documentationFormat = { "markdown", "plaintext" }, + documentationFormat = { 'markdown', 'plaintext' }, preselectSupport = true, insertReplaceSupport = true, labelDetailsSupport = true, snippetSupport = true, resolveSupport = { properties = { - "documentation", - "details", - "additionalTextEdits", + 'documentation', + 'details', + 'additionalTextEdits', }, }, }, @@ -216,17 +216,17 @@ local setups = { }, }, }, - filetypes = { "go", "gomod", "gosum", "gotmpl", "gohtmltmpl", "gotexttmpl" }, + filetypes = { 'go', 'gomod', 'gosum', 'gotmpl', 'gohtmltmpl', 'gotexttmpl' }, message_level = vim.lsp.protocol.MessageType.Error, cmd = { - "gopls", -- share the gopls instance if there is one already - "-remote.debug=:0", + 'gopls', -- share the gopls instance if there is one already + '-remote.debug=:0', }, root_dir = function(fname) - local has_lsp, lspconfig = pcall(require, "lspconfig") + local has_lsp, lspconfig = pcall(require, 'lspconfig') if has_lsp then local util = lspconfig.util - return util.root_pattern("go.mod", ".git")(fname) or util.path.dirname(fname) + return util.root_pattern('go.mod', '.git')(fname) or util.path.dirname(fname) end end, flags = { allow_incremental_sync = true, debounce_text_changes = 500 }, @@ -259,19 +259,19 @@ local setups = { usePlaceholders = true, completeUnimported = true, staticcheck = true, - matcher = "Fuzzy", - diagnosticsDelay = "500ms", - experimentalWatchedFileDelay = "200ms", - symbolMatcher = "fuzzy", - ["local"] = get_current_gomod(), + matcher = 'Fuzzy', + diagnosticsDelay = '500ms', + experimentalWatchedFileDelay = '200ms', + symbolMatcher = 'fuzzy', + ['local'] = get_current_gomod(), gofumpt = _GO_NVIM_CFG.lsp_gofumpt or false, -- true|false, -- turn on for new repos, gofmpt is good but also create code turmoils - buildFlags = { "-tags", "integration" }, + buildFlags = { '-tags', 'integration' }, }, }, } local function get_build_flags() - local get_build_tags = require("go.gotest").get_build_tags + local get_build_tags = require('go.gotest').get_build_tags local tags = get_build_tags() log(vim.inspect(tags)) if tags then @@ -288,17 +288,17 @@ M.setups = function() end tags = get_build_flags() - if tags ~= "" then - setups.settings.gopls.buildFlags = {tags} + if tags ~= '' then + setups.settings.gopls.buildFlags = { tags } end - if v > "0.7" then - setups.settings.gopls = vim.tbl_deep_extend("force", setups.settings.gopls, { + if v > '0.7' then + setups.settings.gopls = vim.tbl_deep_extend('force', setups.settings.gopls, { experimentalUseInvalidMetadata = true, -- hoverKind = "Structured", }) end - if v > "0.9" and _GO_NVIM_CFG.lsp_inlay_hints.enable then - setups.settings.gopls = vim.tbl_deep_extend("force", setups.settings.gopls, { + if v > '0.9' and _GO_NVIM_CFG.lsp_inlay_hints.enable then + setups.settings.gopls = vim.tbl_deep_extend('force', setups.settings.gopls, { hints = { assignVariableTypes = true, compositeLiteralFields = true, diff --git a/lua/go/guru.lua b/lua/go/guru.lua index a6d1dbc..9aa5095 100644 --- a/lua/go/guru.lua +++ b/lua/go/guru.lua @@ -80,7 +80,6 @@ local guru_cmd = function(args) if not data then return end - print(data) log(data) local res = data if format == "json" then diff --git a/lua/go/lsp.lua b/lua/go/lsp.lua index cd11dc8..da9beea 100644 --- a/lua/go/lsp.lua +++ b/lua/go/lsp.lua @@ -214,4 +214,161 @@ M.codelens_enabled = function() return codelens_enabled end +local function request(method, params, handler) + return vim.lsp.buf_request(0, method, params, handler) +end + +function M.gen_return(lsp_result) + if not lsp_result or not lsp_result.contents then + return + end + local contents = vim.split(lsp_result.contents.value, '\n') + local func + for _, line in ipairs(contents) do + if line:match('^func') then + func = line + break + end + end + if not func then + return + end + local ret_list, err = M.find_ret(func) + if ret_list == nil or next(ret_list) == nil then + return + end + local header = ret_list[1] + for i = 2, #ret_list do + header = header .. ', ' .. ret_list[i] + end + local current_line = vim.api.nvim_get_current_line() + local ss, se = string.find(current_line, '%s+') + local leading_space = '' + if ss then + leading_space = current_line:sub(ss, se) + end + header = leading_space .. header .. ' := ' .. vim.trim(current_line) + + local row, col = unpack(api.nvim_win_get_cursor(0)) + vim.api.nvim_buf_set_lines(0, row - 1, row, true, { header }) + vim.cmd('write') + if err then + require('go.iferr').run() + end + return header +end + +local name_map = { + error = 'err', + int = 'i', + int64 = 'i', + uint = 'i', + uint64 = 'i', + float = 'f', + float64 = 'f', + string = 's', + rune = 'r', + bool = 'b', + channel = 'ch', + byte = 'b', +} + +function gen_name(types) + local rets = {} + local used = {} + for _, t in pairs(types) do + if name_map[t] then + if not used[name_map[t]] then + rets[#rets + 1] = name_map[t] + used[name_map[t]] = 1 + else + rets[#rets + 1] = name_map[t] .. tostring(used[name_map[t]]) + used[name_map[t]] = used[name_map[t]] + 1 + end + else + local f = t:sub(1, 1) + if f == f:upper() then + name_map[t] = f:lower() .. t:sub(2) + table.insert(rets, name_map[t]) + used[name_map[t]] = (used[name_map[t]] or 0) + 1 + else + name_map[t] = f + table.insert(rets, name_map[t]) + used[name_map[t]] = (used[name_map[t]] or 0) + 1 + end + end + end + log(rets) + return rets +end + +function M.find_ret(str) + str = vim.trim(str) + local pat = [[\v^func\s+%(\w|\.)+\(%(\w|\_s|[*\.\[\],{}<>-])*\)\s+]] + local regex = vim.regex(pat) + local start, endpos = regex:match_str(str) + if start == nil then + return + end + + local ret = vim.trim(str:sub(endpos + 1)) + if ret == '' then + return + end + pat = [[\v\(%(\w|\_s|[*\.\[\],{}<>-])*\)]] + regex = vim.regex(pat) + + start, endpos = regex:match_str(ret) + -- handle return type in bracket + local retlist = {} + if start ~= nil then + ret = ret:sub(2, #ret - 1) -- remove ( and ) + local ret_types = vim.split(ret, ',%s*') + local need_convert = true + for _, t in pairs(ret_types) do + t = vim.trim(t) + local m = vim.split(t, '%s+') + if #m > 1 then + need_convert = false + end + table.insert(retlist, m[1]) + end + if need_convert then + retlist = gen_name(ret_types) + end + else + retlist = gen_name({ ret }) + end + local includes_err = vim.tbl_contains(retlist, 'err') + return retlist, includes_err +end + +function M.hover_returns() + local util = require('vim.lsp.util') + + local current_line = vim.api.nvim_get_current_line() + local row, col = unpack(api.nvim_win_get_cursor(0)) + local pat = [[\w\+(]] + local r = vim.regex(pat) + local s, e = r:match_str(current_line) + log(s, e) + if s == nil then + return + end + + local params = util.make_position_params() + params.position.character = e - 1 + log(params) + request('textDocument/hover', params, function(err, result, ctx) + if err ~= nil then + log(err) + return + end + if result == nil then + return + end + M.gen_return(result) + end) +end + return M diff --git a/lua/go/ts/utils.lua b/lua/go/ts/utils.lua index 8a1c9c2..3029b3e 100644 --- a/lua/go/ts/utils.lua +++ b/lua/go/ts/utils.lua @@ -4,7 +4,8 @@ local ts_query = vim.treesitter.query local ts_utils = require("nvim-treesitter.ts_utils") local util = require("go.utils") local log = util.log -local trace = util.log +local trace = util.trace +-- local trace = util.log local M = {} -- local ulog = require("go.utils").log @@ -105,9 +106,13 @@ end function M.list_definitions_toc(bufnr) bufnr = bufnr or api.nvim_win_get_buf(api.nvim_get_current_win()) vim.api.nvim_buf_set_option(bufnr, "filetype", "go") + vim.api.nvim_buf_set_option(bufnr, "syntax", "enable") + log('get_definitions', bufnr) local definitions = get_definitions(bufnr) + log("definitions: ", definitions) if #definitions < 1 then + log('unable to find definitions') return end diff --git a/lua/snips/all.lua b/lua/snips/all.lua index b380709..8e9af98 100644 --- a/lua/snips/all.lua +++ b/lua/snips/all.lua @@ -42,11 +42,6 @@ local function filter(prefix) end ls.add_snippets("all", { - ls.s("dl1", { - ls.i(1, "sample_text"), - ls.t({ ":", "" }), - dl(2, l._1, 1), - }), ls.s("time", partial(vim.fn.strftime, "%H:%M:%S")), ls.s("date", partial(vim.fn.strftime, "%Y-%m-%d")), ls.s("pwd", { partial(utils.run_command, "pwd") }), diff --git a/lua/tests/go_fixplurals_spec.lua b/lua/tests/go_fixplurals_spec.lua index 94967b4..40e4653 100644 --- a/lua/tests/go_fixplurals_spec.lua +++ b/lua/tests/go_fixplurals_spec.lua @@ -1,43 +1,41 @@ -local _ = require("plenary/busted") - +local _ = require('plenary/busted') +print('aaa') local eq = assert.are.same -local cur_dir = vim.fn.expand("%:p:h") +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 fixplurals", function() +describe('should run fixplurals', function() -- vim.fn.readfile('minimal.vim') -- vim.fn.writefile(vim.fn.readfile('fixtures/fmt/hello.go'), name) -- status = require("plenary.reload").reload_module("go.nvim") - it("should run fixplurals", function() + it('should run fixplurals', function() -- - local name = vim.fn.tempname() .. ".go" - local path = cur_dir .. "/lua/tests/fixtures/fixplurals/fixp_input.go" -- %:p:h ? %:p + local name = vim.fn.tempname() .. '.go' + local path = cur_dir .. '/lua/tests/fixtures/fixplurals/fixp_input.go' -- %:p:h ? %:p local lines = vim.fn.readfile(path) vim.fn.writefile(lines, name) - local expected = vim.fn.join(vim.fn.readfile(cur_dir - .. "/lua/tests/fixtures/fixplurals/fixp_golden.go"), - "\n") + local expected = vim.fn.join(vim.fn.readfile(cur_dir .. '/lua/tests/fixtures/fixplurals/fixp_golden.go'), '\n') local cmd = " silent exe 'e " .. name .. "'" vim.cmd(cmd) - local bufn = vim.fn.bufnr("") + local bufn = vim.fn.bufnr('') - vim.fn.setpos(".", {bufn, 2, 11, 0}) + vim.fn.setpos('.', { bufn, 2, 11, 0 }) -- local l = vim.api.nvim_buf_get_lines(0, 0, -1, true) - vim.bo.filetype = "go" + vim.bo.filetype = 'go' - local gofixp = require("go.fixplurals") + local gofixp = require('go.fixplurals') gofixp.fixplurals() vim.wait(100, function() - vim.cmd('w') + vim.cmd('w') end) - local fmt = vim.fn.join(vim.fn.readfile(name), "\n") + local fmt = vim.fn.join(vim.fn.readfile(name), '\n') vim.fn.assert_equal(fmt, expected) eq(expected, fmt) - cmd = "bd! " .. name + cmd = 'bd! ' .. name vim.cmd(cmd) end) end) diff --git a/lua/tests/go_hover_spec.lua b/lua/tests/go_hover_spec.lua new file mode 100644 index 0000000..2260d80 --- /dev/null +++ b/lua/tests/go_hover_spec.lua @@ -0,0 +1,91 @@ +local eq = assert.are.same +local cur_dir = vim.fn.expand('%:p:h') +-- func Println(a ...any) (n int, err error) +-- func fmt.Println(a ...any) (n int, err error) +-- func fmt.inner.Println(a ...any) (n int, err error) +-- func fmt.inner2.Println3(a ...any) (n int, err error) +-- func fmt.inner2.Println3(a any, par int) (n int, err error) +-- func fmt.inner2.Println3(a any, par int) int +-- func fmt.inner2.Println3(par int) int +-- func fmt.inner2.Println3(par int) +-- func fmt.inner2.Println3(par *[]int) +-- func fmt.inner2.Println3(par struct mnt{}) +-- /(\%(\w\|\_s\|[*\.\[\],\{\}<>-]\)*)/ +-- /\v\((\w|\_s|[*\.\[\],{}<>-])*\) +local busted = require('plenary/busted') + +describe('regex should work', function() + -- vim.fn.readfile('minimal.vim') + -- vim.fn.writefile(vim.fn.readfile('fixtures/fmt/hello.go'), name) + require('plenary.reload').reload_module('go.nvim') + + require('go').setup({ + trace = true, + log_path = vim.fn.expand('$HOME') .. '/tmp/gonvim.log', + }) + + print(vim.fn.expand('$HOME') .. '/tmp/gonvim.log') + it('should find return', function() + local str = [[func Println(a ...any) (n int, err error)]] + local ret = require('go.lsp').find_ret(str) + print(vim.inspect(ret)) + eq({'n', 'err'}, ret) + end) + + it('should find return', function() + local str = [[func fmt.Println(a ...any) (int, error)]] + local ret, e = require('go.lsp').find_ret(str) + print(vim.inspect(ret)) + eq({'i', 'err'}, ret) + eq(true, e) + end) + it('should find return', function() + local str = [[func fmt.Println(a, b int) (int, error)]] + local ret, e = require('go.lsp').find_ret(str) + print(vim.inspect(ret)) + eq({'i', 'err'}, ret) + eq(true, e) + end) + + it('should find return', function() + local str = [[func fmt.Println(a, b int) int]] + local ret, e = require('go.lsp').find_ret(str) + print(vim.inspect(ret)) + eq({'i'}, ret) + eq(false, e) + end) + + it('should find return', function() + local str = [[func fmt.Println(a, b int) MyType]] + local ret, e = require('go.lsp').find_ret(str) + print(vim.inspect(ret)) + eq({'myType'}, ret) + eq(false, e) + end) + + it('should find return', function() + local str = [[func fmt.Println(a, b int) (MyType, error)]] + local ret, e = require('go.lsp').find_ret(str) + print(vim.inspect(ret)) + eq({'myType', 'err'}, ret) + eq(true, e) + end) +end) +describe('should run hover', function() + -- vim.fn.readfile('minimal.vim') + -- vim.fn.writefile(vim.fn.readfile('fixtures/fmt/hello.go'), name) + require('plenary.reload').reload_module('go.nvim') + it('should run hover', function() + local result = { + contents = { + kind = 'markdown', + value = [[```go\nfunc fmt.Println(a ...any) (n int, err error)(\%(\w\|\_s\|[*\.\[\],\{\}<>-]\)*)\n```\n\nPrintln formats using the default formats for its operands and writes to standard output\\.\nSpaces are always added between operands and a newline is appended\\.\nIt returns the number of bytes written and any write error encountered\\.\n\n\n[`fmt.Println` on pkg.go.dev](https://pkg.go.dev/fmt?utm_source=gopls#Println)]], + }, + range = {}, + } + + local ret = require('go.lsp').gen_return(result) + print(vim.inspect(ret)) + + end) +end) diff --git a/queries/go/folds.scm b/queries/go/folds.scm deleted file mode 100644 index 1782111..0000000 --- a/queries/go/folds.scm +++ /dev/null @@ -1,4 +0,0 @@ -[ -(composite_literal) -(literal_element) -] @fold