diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 739bf19..acbfe7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,15 +18,11 @@ jobs: url: https://github.com/neovim/neovim/releases/download/v0.9.5/nvim-linux64.tar.gz manager: sudo snap packages: go - - os: ubuntu-22.04 - url: https://github.com/neovim/neovim/releases/download/v0.9.4/nvim-linux64.tar.gz - manager: sudo snap - packages: go steps: - uses: actions/checkout@v2 - uses: actions/setup-go@v2 with: - go-version: "^1.20" # The Go version to download (if necessary) and use. + go-version: "^1.22" # The Go version to download (if necessary) and use. - run: date +%F > todays-date - name: Restore cache for today's nightly. uses: actions/cache@v2 diff --git a/lua/go/commands.lua b/lua/go/commands.lua index d7c7bd1..a65fa4e 100644 --- a/lua/go/commands.lua +++ b/lua/go/commands.lua @@ -370,7 +370,7 @@ return { require('go.iferr').run() end) create_cmd('GoFillStruct', function(_) - require('go.reftool').fillstruct() + require('go.lsp').codeaction('apply_fix', 'refactor.rewrite') end) create_cmd('GoFillSwitch', function(_) require('go.reftool').fillswitch() diff --git a/lua/go/fixplurals.lua b/lua/go/fixplurals.lua index 60e6548..e495e33 100644 --- a/lua/go/fixplurals.lua +++ b/lua/go/fixplurals.lua @@ -37,6 +37,7 @@ local function fixplurals() if #edits == 0 then return info("no plural parameter") end - vim.lsp.util.apply_text_edits(edits, 0, "utf-8") + local bufnr = vim.api.nvim_get_current_buf() + vim.lsp.util.apply_text_edits(edits, bufnr, "utf-8") end return { fixplurals = fixplurals } diff --git a/lua/go/install.lua b/lua/go/install.lua index f35ff1a..036419c 100644 --- a/lua/go/install.lua +++ b/lua/go/install.lua @@ -16,7 +16,6 @@ local url = { callgraph = 'golang.org/x/tools/cmd/callgraph', guru = 'golang.org/x/tools/cmd/guru', impl = 'github.com/josharian/impl', - fillstruct = 'github.com/davidrjenni/reftools/cmd/fillstruct', fillswitch = 'github.com/davidrjenni/reftools/cmd/fillswitch', dlv = 'github.com/go-delve/delve/cmd/dlv', ginkgo = 'github.com/onsi/ginkgo/v2/ginkgo', diff --git a/lua/go/lsp.lua b/lua/go/lsp.lua index ad30117..a94b68a 100644 --- a/lua/go/lsp.lua +++ b/lua/go/lsp.lua @@ -160,7 +160,15 @@ local extend_config = function(gopls, opts) gopls[key] = vim.tbl_deep_extend('force', gopls[key], value) else if type(gopls[key]) ~= type(value) and key ~= 'handlers' then - vim.notify('gopls setup for ' .. key ..' type:' .. type(gopls[key]) .. ' is not ' .. type(value) .. vim.inspect(value)) + vim.notify( + 'gopls setup for ' + .. key + .. ' type:' + .. type(gopls[key]) + .. ' is not ' + .. type(value) + .. vim.inspect(value) + ) end gopls[key] = value end @@ -247,44 +255,98 @@ write", "source", "source.organizeImports" } ]] -- action / fix to take --- only this action 'refactor.rewrite' source.organizeImports -M.codeaction = function(action, only, hdlr) +-- only gopls +M.codeaction = function(gopls_cmd, only, hdlr) local params = vim.lsp.util.make_range_params() + if not gopls_cmd:find('gopls') then + gopls_cmd = 'gopls.' .. gopls_cmd + end if only then params.context = { only = { only } } end local bufnr = vim.api.nvim_get_current_buf() + local clients = vim.lsp.get_active_clients() + local gopls + -- find gopls client + for cid, c in pairs(clients) do + if c.name == 'gopls' then + log('gopls found', cid) + gopls = c + break + end + end + if gopls == nil then + log('gopls not found') + return + end + + local ctx = { bufnr = bufnr, client_id = gopls.id } - log(action, only, bufnr) - vim.lsp.buf_request_all(bufnr, 'textDocument/codeAction', params, function(result) + local function apply_action(action) + log('apply_action', action, ctx) + if action.edit then + vim.lsp.util.apply_workspace_edit(action.edit, gopls.offset_encoding) + end + if action.command then + local command = type(action.command) == 'table' and action.command or action + local fn = gopls.commands[command.command] or vim.lsp.commands[command.command] + ctx.client_id = gopls.id + if fn then + local enriched_ctx = vim.deepcopy(ctx) + fn(command, enriched_ctx) + else + gopls.request('workspace/executeCommand', { + command = command.command, + arguments = command.arguments, + workDoneToken = command.workDoneToken, + }, function(_err, r) + if _err then + log('error', _err) + end + end, bufnr) + end + end + end + local function ca_hdlr(err, result) + if err then + return log('error', err) + end + log('gocodeaction', err, result) if not result or next(result) == nil then - log('nil result') + log('nil result for codeaction with parameters', gopls_cmd, only, bufnr, params) return end - log('code action result', result) - local c = M.client() + local actions = {} for _, res in pairs(result) do - for _, r in pairs(res.result or {}) do - if r.edit and not vim.tbl_isempty(r.edit) then - local re = vim.lsp.util.apply_workspace_edit(r.edit, c.offset_encoding) - log('workspace edit', r, re) - end - if type(r.command) == 'table' then - if type(r.command) == 'table' and r.command.arguments then - for _, arg in pairs(r.command.arguments) do - if action == nil or arg['Fix'] == action then - vim.lsp.buf.execute_command(r.command) - return - end - end - end - end + local act_cmd = res.data and res.data.command or '' + if res.edit or act_cmd == gopls_cmd then + table.insert(actions, res) end end - if hdlr then - hdlr(result) + if #actions == 0 then + log('no code actions available') + vim.notify('No code actions available', vim.log.levels.INFO) + return end - end) + + local action = actions[1] + -- resolve + gopls.request('codeAction/resolve', action, function(_err, resolved_acrtion) + if _err then + log('error', _err) + if action.command then + apply_action(action) + else + log('resolved', resolved_acrtion) + vim.notify('No code actions available', vim.log.levels.INFO) + end + else + apply_action(resolved_acrtion) + end + end, bufnr) + end + + gopls.request('textDocument/codeAction', params, ca_hdlr, bufnr) end M.gopls_on_attach = on_attach @@ -509,3 +571,18 @@ function M.watchFileChanged(fname, params) end return M + +--[[ +as of 2024-03-01 + codeActionProvider = { + codeActionKinds = { "quickfix", "refactor.extract", "refactor.inline", "refactor.rewrite", "source.fixAll", "source.organizeImports" }, + resolveProvider = true + }, + executeCommandProvider = { + commands = { "gopls.add_dependency", "gopls.add_import", "gopls.add_telemetry_counters", "gopls.apply_fix", "gopls.change_signature", "gopls.check_upgrades", "gopls.diagnose_files", "gopls.edit_go_directive", "gopls.fetch_vulncheck_result", "gopls. +gc_details", "gopls.generate", "gopls.go_get_package", "gopls.list_imports", "gopls.list_known_packages", "gopls.maybe_prompt_for_telemetry", "gopls.mem_stats", "gopls.regenerate_cgo", "gopls.remove_dependency", "gopls.reset_go_mod_diagnostics", "gopls.run +_go_work_command", "gopls.run_govulncheck", "gopls.run_tests", "gopls.start_debugging", "gopls.start_profile", "gopls.stop_profile", "gopls.test", "gopls.tidy", "gopls.toggle_gc_details", "gopls.update_go_sum", "gopls.upgrade_dependency", "gopls.vendor", " +gopls.views", "gopls.workspace_stats" } + }, +]] +-- diff --git a/lua/go/project.lua b/lua/go/project.lua index 5daf044..a076416 100644 --- a/lua/go/project.lua +++ b/lua/go/project.lua @@ -4,7 +4,6 @@ return { go = "go", -- set to go1.18beta1 if necessary 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, tag_transform = false, diff --git a/lua/go/reftool.lua b/lua/go/reftool.lua index a9c1f7c..1a69d8e 100644 --- a/lua/go/reftool.lua +++ b/lua/go/reftool.lua @@ -62,16 +62,6 @@ local function fill(cmd) }) end -local function gopls_fillstruct() - log('fill struct with gopls') - local codeaction = require('go.lsp').codeaction - codeaction('fill_struct', 'refactor.rewrite') -end - -function reftool.fillstruct() - gopls_fillstruct() -end - reftool.fillswitch = function() fill('fillswitch') end diff --git a/lua/go/ts/nodes.lua b/lua/go/ts/nodes.lua index 5a6b107..8d47fd7 100644 --- a/lua/go/ts/nodes.lua +++ b/lua/go/ts/nodes.lua @@ -237,7 +237,7 @@ M.nodes_in_buf = function(query, default, bufnr, row, col) end local ns = M.get_all_nodes(query, ft, default, bufnr, row, col, true) if ns == nil then - vim.notify('Unable to find any nodes.', vim.log.levels.DEBUG) + -- vim.notify('Unable to find any nodes.', vim.log.levels.DEBUG) ulog('Unable to find any nodes. place your cursor on a go symbol and try again') return nil end @@ -250,6 +250,9 @@ M.nodes_at_cursor = function(query, default, bufnr, ntype) row, col = row, col + 1 bufnr = bufnr or vim.api.nvim_get_current_buf() local ft = vim.api.nvim_buf_get_option(bufnr, 'ft') + if ft ~= 'go' then + return + end local ns = M.get_all_nodes(query, ft, default, bufnr, row, col, ntype) if ns == nil then vim.notify( @@ -261,6 +264,13 @@ M.nodes_at_cursor = function(query, default, bufnr, ntype) end ulog(#ns) local nodes_at_cursor = M.sort_nodes(M.intersect_nodes(ns, row, col)) + if not nodes_at_cursor then + -- cmp-command-line will causing cursor to move to end of line + -- lets try move back a bit and try to find nodes again + row, col = unpack(vim.api.nvim_win_get_cursor(0)) + row, col = row, col - 5 + nodes_at_cursor = M.sort_nodes(M.intersect_nodes(ns, row, col)) + end ulog(row, col, vim.inspect(nodes_at_cursor):sub(1, 100)) if nodes_at_cursor == nil or #nodes_at_cursor == 0 then vim.notify( diff --git a/lua/tests/go_fillstruct_spec.lua b/lua/tests/go_fillstruct_spec.lua index ced3df3..4928733 100644 --- a/lua/tests/go_fillstruct_spec.lua +++ b/lua/tests/go_fillstruct_spec.lua @@ -27,22 +27,26 @@ describe('should run fillstruct', function() vim.cmd('sleep 2000m') -- allow gopls startup vim.fn.setpos('.', { 0, 20, 14, 0 }) - require('go.reftool').fillstruct() + require('go.lsp').codeaction('apply_fix', 'refactor.rewrite') local filled - for _ = 1, 8 do + local success = vim.wait(5000, function() require('go.utils').log('waiting for fill') - vim.wait(500, function() return false end) + vim.wait(500, function() + return false + end) filled = vim.api.nvim_buf_get_lines(0, 0, 40, false) filled = vim.fn.join(filled, '\n') - require('go.utils').log(vim.inspect(filled)) + require('go.utils').log('fill struct', vim.inspect(filled)) if expected == filled then eq(true, true) - return + return true end - require('go.reftool').fillstruct() - end + + return false + end, 300) + require('go.utils').log('filled', filled, success, expected) eq(expected, filled) end) diff --git a/lua/tests/go_gopls_fillstruct_spec.lua b/lua/tests/go_gopls_fillstruct_spec.lua new file mode 100644 index 0000000..4b12544 --- /dev/null +++ b/lua/tests/go_gopls_fillstruct_spec.lua @@ -0,0 +1,65 @@ +local eq = assert.are.same +local cur_dir = vim.fn.expand('%:p:h') +local busted = require('plenary/busted') + +local godir = cur_dir .. '/lua/tests/fixtures' +describe('should run gopls related functions', function() + -- vim.fn.readfile('minimal.vim') + -- vim.fn.writefile(vim.fn.readfile('fixtures/fmt/hello.go'), name) + + vim.cmd([[packadd go.nvim]]) + it('should import time from file with gopls', function() + require('plenary.reload').reload_module('go.nvim') + + require('go').setup({ goimport = 'gopls', verbose = true, log_path = '', lsp_cfg = true }) + local cmd = " silent exe 'e temp.go'" + vim.cmd(cmd) + _GO_NVIM_CFG.goimport = 'gopls' + _GO_NVIM_CFG.log_path = '' -- enable log to console + _GO_NVIM_CFG.lsp_codelens = false + local expected = + vim.fn.join(vim.fn.readfile(cur_dir .. '/lua/tests/fixtures/fmt/goimports3_golden.go'), '\n') + + vim.cmd('cd ' .. godir) + local path = './fmt/goimports3.go' -- %:p:h ? %:p + cmd = " silent exe 'e " .. path .. "'" + vim.cmd(cmd) + + vim.wait(2000, function() + return false + end) + local c = vim.lsp.get_active_clients() + eq(#c > 0, true) + + _GO_NVIM_CFG.log_path = '' -- enable log to console + require('go.format').goimport() + + vim.wait(1000, function() + return false + end) + print('workspaces:', vim.inspect(vim.lsp.buf.list_workspace_folders())) + local fmt + require('go.utils').log(vim.inspect(expected)) + require('go.utils').log('waiting for import') + vim.cmd([[wa]]) + local success, no = vim.wait(6000, function() + fmt = vim.fn.join(vim.fn.readfile(path), '\n') + require('go.utils').log(vim.inspect(fmt)) + if expected == fmt then + require('go.utils').log('success:', vim.inspect(fmt)) + return true + end + require('go.utils').log('wait:', fmt, expected) + return false + end, 400) + + require('go.utils').log('success:', success, no, fmt, expected) + if success then + eq(1, 1) + else + eq(expected, fmt) + end + cmd = 'bd! ' .. path + vim.cmd(cmd) + end) +end) diff --git a/lua/tests/go_gopls_spec.lua b/lua/tests/go_gopls_spec.lua index 621111e..b904791 100644 --- a/lua/tests/go_gopls_spec.lua +++ b/lua/tests/go_gopls_spec.lua @@ -32,7 +32,7 @@ describe('should run gopls related functions', function() require('go.format').goimport() local fmt require('go.utils').log('workspaces:', vim.inspect(vim.lsp.buf.list_workspace_folders())) - vim.wait(2000, function() + vim.wait(4000, function() vim.cmd([[wa]]) fmt = vim.fn.join(vim.fn.readfile(path), '\n') if expected == fmt then @@ -47,51 +47,4 @@ describe('should run gopls related functions', function() cmd = 'bd! ' .. path vim.cmd(cmd) end) - it('should import time from file with gopls', function() - require('plenary.reload').reload_module('go.nvim') - - require('go').setup({ goimport = 'gopls', verbose = true, log_path = '', lsp_cfg = true }) - local cmd = " silent exe 'e temp.go'" - vim.cmd(cmd) - _GO_NVIM_CFG.log_path = '' -- enable log to console - _GO_NVIM_CFG.lsp_codelens = false - local expected = - vim.fn.join(vim.fn.readfile(cur_dir .. '/lua/tests/fixtures/fmt/goimports3_golden.go'), '\n') - - vim.cmd('cd ' .. godir) - local path = './fmt/goimports3.go' -- %:p:h ? %:p - cmd = " silent exe 'e " .. path .. "'" - vim.cmd(cmd) - - vim.wait(2000, function() - return false - end) - - _GO_NVIM_CFG.log_path = '' -- enable log to console - require('go.format').goimport() - - print('workspaces:', vim.inspect(vim.lsp.buf.list_workspace_folders())) - local fmt - require('go.utils').log(vim.inspect(expected)) - require('go.utils').log('waiting for import') - vim.cmd([[wa]]) - local success, no = vim.wait(2000, function() - fmt = vim.fn.join(vim.fn.readfile(path), '\n') - require('go.utils').log(vim.inspect(fmt)) - if expected == fmt then - require('go.utils').log('success:', fmt, expected) - return true - end - return false - end, 200) - - require('go.utils').log('success:', success, no, fmt, expected) - if success then - eq(1, 1) - else - eq(expected, fmt) - end - cmd = 'bd! ' .. path - vim.cmd(cmd) - end) end)