From 1e190d6a0efa59a05017b42317db4849c5453e80 Mon Sep 17 00:00:00 2001 From: ray-x Date: Wed, 10 Mar 2021 23:15:06 +1100 Subject: [PATCH] init commit --- README.md | 92 +++++++ TODO.md | 5 + lua/go.lua | 39 +++ lua/go/comment.lua | 64 +++++ lua/go/format.lua | 78 ++++++ lua/go/gotests.lua | 85 +++++++ lua/go/install.lua | 61 +++++ lua/go/runner.lua | 65 +++++ lua/go/tags.lua | 86 +++++++ lua/go/ts/go.lua | 158 ++++++++++++ lua/go/ts/nodes.lua | 240 ++++++++++++++++++ lua/go/ts/utils.lua | 19 ++ lua/go/utils.lua | 41 +++ lua/tests/fixtures/fmt/goimports.go | 5 + lua/tests/fixtures/fmt/goimports_golden.go | 7 + lua/tests/fixtures/fmt/hello.go | 7 + lua/tests/fixtures/fmt/hello_golden.go | 7 + lua/tests/fixtures/tags/add_all_golden.go | 17 ++ .../fixtures/tags/add_all_golden_options.go | 17 ++ lua/tests/fixtures/tags/add_all_input.go | 17 ++ lua/tests/fixtures/tags/remove_all_golden.go | 17 ++ lua/tests/fixtures/tags/remove_all_input.go | 17 ++ lua/tests/fixtures/ts/interfaces.go | 48 ++++ lua/tests/fixtures/ts/playlist.go | 99 ++++++++ lua/tests/go_comment_spec.lua | 42 +++ lua/tests/go_fmt_spec.lua | 129 ++++++++++ lua/tests/go_tags_spec.lua | 162 ++++++++++++ lua/tests/go_ts_node_spec.lua | 211 +++++++++++++++ lua/tests/minimal.vim | 6 + 29 files changed, 1841 insertions(+) create mode 100644 README.md create mode 100644 TODO.md create mode 100644 lua/go.lua create mode 100644 lua/go/comment.lua create mode 100644 lua/go/format.lua create mode 100644 lua/go/gotests.lua create mode 100644 lua/go/install.lua create mode 100644 lua/go/runner.lua create mode 100644 lua/go/tags.lua create mode 100644 lua/go/ts/go.lua create mode 100644 lua/go/ts/nodes.lua create mode 100644 lua/go/ts/utils.lua create mode 100644 lua/go/utils.lua create mode 100644 lua/tests/fixtures/fmt/goimports.go create mode 100644 lua/tests/fixtures/fmt/goimports_golden.go create mode 100644 lua/tests/fixtures/fmt/hello.go create mode 100644 lua/tests/fixtures/fmt/hello_golden.go create mode 100644 lua/tests/fixtures/tags/add_all_golden.go create mode 100644 lua/tests/fixtures/tags/add_all_golden_options.go create mode 100644 lua/tests/fixtures/tags/add_all_input.go create mode 100644 lua/tests/fixtures/tags/remove_all_golden.go create mode 100644 lua/tests/fixtures/tags/remove_all_input.go create mode 100644 lua/tests/fixtures/ts/interfaces.go create mode 100644 lua/tests/fixtures/ts/playlist.go create mode 100644 lua/tests/go_comment_spec.lua create mode 100644 lua/tests/go_fmt_spec.lua create mode 100644 lua/tests/go_tags_spec.lua create mode 100644 lua/tests/go_ts_node_spec.lua create mode 100644 lua/tests/minimal.vim diff --git a/README.md b/README.md new file mode 100644 index 0000000..0d199fb --- /dev/null +++ b/README.md @@ -0,0 +1,92 @@ +# [WIP] go.nvim + +A modern golang neovim plugin based on treesitter and nvim-lsp. Written in Lua. Async as much as possible. +PR & Suggestions welcome + +## install + +add 'ray-x/go.nvim' to your package manager +related binaries will be installed the first time you using it +Add lsp format in your vimrc. You can check my dotfiles for details + +```lua +require('go').setup() +``` + +## code format + +nvim-lsp support goimport by default. +```vim +autocmd BufWritePre (InsertLeave?) lua vim.lsp.buf.formatting_sync(nil,500) +``` + +The plugin provides code format, by default is goline + gofumports (stricter version of goimport) + +The format tool is a asyn format tool in format.lua + +```lua +require("go.format").gofmt() +require("go.format").goimport() +``` + +## Textobject + +Supported by treesitter. TS provided better parse result compared to regular expression. + +## Build and test + +Provided wrapper for gobulild/test etc + +## unit test with gotests + +Support table based unit test auto generate, parse current function/method name using treesitter + +## Modifytags + +modifytags by `modifytags` and treesitter + +## GoFmt + +nvim-lsp support goimport by default. The plugin provided a new formatter, goline + gofumports (stricter version of +goimport) + +## Comments and Doc + +Auto doc (to suppress golang-lint warning), generate comments by treesitter parsing result + +```go +type GoLintComplaining struct{} +``` +```lua + lua.require('go.comment').add_comment() -- or your faviourite key binding and setup placeholder "no more complaint ;P" +``` +The code will be: +```go +// GoLintComplaining struct no more complaint ;P +type GoLintComplaining struct{} +``` + +## LSP + +LSP supported by nvim-lsp is good enough for a gopher. If you looking for a better GUI. lspsaga and lsp-utils are +what you are looking for. + +## Lint + +Supported by LSP, if you need golangci-lint better with ALE + +## configuration + +lua suggested: +```lua +require('go').setup(cfg = { + goimport='gofumports', -- g:go_nvim_goimport + gofmt = 'gofumpt', --g:go_nvim_gofmt, + max_len = 100, -- g:go_nvim_max_len + transform = false, -- vim.g.go_nvim_tag_transfer check gomodifytags for details + test_template = '', -- default to testify if not set; g:go_nvim_tests_template check gotests for details + test_template_dir = '', -- default to nil if not set; g:go_nvim_tests_template_dir check gotests for details + comment_placeholder = '' , -- vim.g.go_nvim_comment_placeholder your cool placeholder e.g. ﳑ     + verbose = false, -- output loginf in messages +}) +``` diff --git a/TODO.md b/TODO.md new file mode 100644 index 0000000..9db74d5 --- /dev/null +++ b/TODO.md @@ -0,0 +1,5 @@ +goimport not stable (failed on 2nd time from time to time) +gomodifytags +goimport: could not import.... auto import by run 'go get' "not in your go.mod" append to gomod +key mappings/nvim commands +GoTests -- add unit test diff --git a/lua/go.lua b/lua/go.lua new file mode 100644 index 0000000..dd5863a --- /dev/null +++ b/lua/go.lua @@ -0,0 +1,39 @@ +-- some of commands extracted from gopher.vim + +local go = {} + +function go.setup(cfg) + vim.g.go_nvim_goimport = cfg.goimport or 'gofumports' -- g:go_nvim_goimport + vim.g.go_nvim_gofmt = cfg.gofmt or 'gofumpt' --g:go_nvim_gofmt, + vim.g.go_nvim_max_len = cfg.max_len or 100 -- g:go_nvim_max_len + vim.g.go_nvim_transform = cfg.transform or false -- vim.g.go_nvim_tag_transfer check gomodifytags for details + vim.g.go_nvim_test_dir = cfg.test_dir or '' -- default to current dir. g:go_nvim_tests_dir check gotests for details + vim.g.go_nvim_comment_placeholder = cfg.comment_placeholder or '  ' -- vim.g.go_nvim_comment_placeholder your cool placeholder e.g. ﳑ     + vim.g.go_nvim_verbose = cfg.verbose or false -- output loginf in messages + + + vim.cmd('command Gofmt lua require("go.format").gofmt()') + vim.cmd('command Goimport lua require("go.format").goimport()') + + vim.cmd([[command GoBuild :setl makeprg=go\ build | :make]]) + vim.cmd([[command GoGenerate :setl makeprg=go\ generate | :make]]) + vim.cmd([[command GoRun :setl makeprg=go\ run | :make]]) + vim.cmd([[command GoTestFunc :make -run ..]]) + + vim.cmd([[command GoTest :compiler gotest | :make]]) + vim.cmd([[command GoTestCompile setl makeprg=go\ build | :make]]) + vim.cmd([[command GoTest setl makeprg=go\ build | :make]]) + + + vim.cmd([[command GoAddTest lua require("go.gotests").fun_test()]]) + vim.cmd([[command GoAddExpTest lua require("go.gotests").exported_test()]]) + vim.cmd([[command GoAddAllTest lua require("go.gotests").all_test()]]) + + vim.cmd([[command! -nargs=* GoAddTag lua require("go.tags").add()]]) + vim.cmd([[command! -nargs=* GoRmTag lua require("go.tags").rm()]]) + vim.cmd([[command GoClearTag lua require("go.tags").clear()]]) + + vim.cmd([[command GoLint :compiler golangci-lint | :make]]) + +end +return go diff --git a/lua/go/comment.lua b/lua/go/comment.lua new file mode 100644 index 0000000..ac81089 --- /dev/null +++ b/lua/go/comment.lua @@ -0,0 +1,64 @@ +-- todo +-- for func name(args) rets {} +-- add cmts // name : rets +local comment = {} +local placeholder = vim.g.go_nvim_comment_placeholder or "" +local ulog = require "go.utils".log +local gen_comment = function(row, col) + local comments = nil + + local ns = require("go.ts.go").get_package_node_at_pos(row, col) + if ns ~= nil and ns ~= {} then + -- utils.log("parnode" .. vim.inspect(ns)) + comments = "// Package " .. ns.name .. " provides " .. ns.name + return comments, ns + end + ns = require("go.ts.go").get_func_method_node_at_pos(row, col) + if ns ~= nil and ns ~= {} then + -- utils.log("parnode" .. vim.inspect(ns)) + comments = "// " .. ns.name .. " " .. ns.type + return comments, ns + end + ns = require("go.ts.go").get_struct_node_at_pos(row, col) + if ns ~= nil and ns ~= {} then + comments = "// " .. ns.name .. " " .. ns.type + return comments, ns + end + ns = require("go.ts.go").get_interface_node_at_pos(row, col) + if ns ~= nil and ns ~= {} then + -- utils.log("parnode" .. vim.inspect(ns)) + comments = "// " .. ns.name .. " " .. ns.type + return comments, ns + end + return "" +end + +local wrap_comment = function(comment_line, ns) + if string.len(comment_line)>0 and placeholder ~= nil and string.len(placeholder)>0 then + return comment_line .. " " .. placeholder, ns + end + return comment_line, ns +end + +comment.gen = function(row, col) + if row == nil or col == nil then + row, col = unpack(vim.api.nvim_win_get_cursor(0)) + row, col = row + 1, col + 1 + end + local c, ns = wrap_comment(gen_comment(row, col)) + --ulog(vim.inspect(ns)) + row, col = ns.dim.s.r, ns.dim.s.c + ulog("set cursor " .. tostring(row)) + vim.api.nvim_win_set_cursor(0, {row, col}) + -- insert doc + vim.fn.append(row - 1, c) + -- set curosr + vim.fn.cursor(row, #c+1) + -- enter into insert mode + vim.api.nvim_command('startinsert!') + return c +end + + + +return comment diff --git a/lua/go/format.lua b/lua/go/format.lua new file mode 100644 index 0000000..402c6e7 --- /dev/null +++ b/lua/go/format.lua @@ -0,0 +1,78 @@ +-- golines A golang formatter that fixes long lines +-- golines + gofumports(stricter gofmt + goimport) +local api = vim.api +local util = require("go.utils") +local max_len = vim.g.go_nvim_max_len and vim.g.go_nvim_max_len or 100 +local goimport = vim.g.go_nvim_goimport ~= nil and vim.g.go_nvim_goimport or "gofumports" +local gofmt = vim.g.go_nvim_gofmt ~= nil and vim.g.go_nvim_gofmt or "gofumpt" +local gofmt_args = + vim.g.go_nvim_gofmt_args and vim.g.go_nvim_gofmt_args or + {"--max-len=" .. tostring(max_len), "--base-formatter=" .. gofmt} +local utils = require('go').utils + +local goimport_args = + vim.g.go_nvim_goimport_args and vim.g.go_nvim_goimport_args or + {"--max-len=" .. tostring(max_len), "--base-formatter=" .. goimport} + +local run = function(args, from_buffer) + if not from_buffer then + table.insert(args, api.nvim_buf_get_name(0)) + print('formatting... ' .. api.nvim_buf_get_name(0) .. vim.inspect(args)) + end + + local old_lines = api.nvim_buf_get_lines(0, 0, -1, true) + table.insert(args, 1, "golines") + + local j = + vim.fn.jobstart( + args, + { + on_stdout = function(job_id, data, event) + if not data or #data==1 and data[1] == "" then return end + if not util.check_same(old_lines, data) then + print("updating codes") + api.nvim_buf_set_lines(0, 0, #data, false, data) + api.nvim_command("write") + else + print("already formatted") + end + utils.log("stdout" .. vim.inspect(data)) + old_lines = nil + + end, + on_stderr = function(job_id, data, event) + print(vim.inspect(data) .. "stderr") + end, + on_exit = function(id, data, event) + -- utils.log(vim.inspect(data) .. "exit") + -- utils.log("current data " .. vim.inspect(new_lines)) + old_lines = nil + end, + stdout_buffered = true, + stderr_buffered = true, + } + ) + vim.fn.chansend(j, old_lines) + vim.fn.chanclose(j, "stdin") +end + +local M = {} +M.gofmt = function(buf) + vim.env.GO_TEST = "gofmt" + buf = buf or false + require("go.install").install(gofmt) + require("go.install").install("golines") + local a = {} + util.copy_array(gofmt_args, a) + run(a, buf) +end + +M.goimport = function() + buf = buf or false + require("go.install").install(goimport) + require("go.install").install("golines") + local a = {} + util.copy_array(goimport_args, a) + run(a, buf) +end +return M diff --git a/lua/go/gotests.lua b/lua/go/gotests.lua new file mode 100644 index 0000000..d54b54e --- /dev/null +++ b/lua/go/gotests.lua @@ -0,0 +1,85 @@ +-- Table driven tests based on its target source files' function and method signatures. +-- https://github.com/cweill/gotests +local ut = {} +local gotests = "gotests" +local test_dir = vim.g.go_nvim_test_dir or "" +local test_template = vim.go_nvim_test_template or "" +local utils = require("go").utils +local run = function(setup) + print(vim.inspect(setup)) + local j = + vim.fn.jobstart( + setup, + { + on_stdout = function(jobid, data, event) + print("unit tests generate " .. vim.inspect(data)) + end, + on_stderr = function(_, data, _) + print("failed to generate tests for " .. vim.inspect(setup) .. "error: " .. vim.inspect(data)) + end + } + ) +end + +local add_test = function(args) + require("go.install").install(gotests) + if string.len(test_template) > 1 then + table.insert(args, "-template") + table.insert(args, test_template) + if string.len(test_dir) > 1 then + table.insert(args, "-template_dir") + table.insert(args, test_dir) + end + end + local row, col = unpack(vim.api.nvim_win_get_cursor(0)) + row, col = row + 1, col + 1 + local ns = require("go.ts.go").get_func_method_node_at_pos(row, col) + if ns == nil or ns == {} then + return + end + + utils.log("parnode" .. vim.inspect(ns)) + run(args) +end + +ut.fun_test = function(parallel) + parallel = parallel or false + local row, col = unpack(vim.api.nvim_win_get_cursor(0)) + row, col = row + 1, col + 1 + local ns = require("go.ts.go").get_func_method_node_at_pos(row, col) + if ns == nil or ns == {} then + return + end + + -- utils.log("parnode" .. vim.inspect(ns)) + local funame = ns.name + -- local rs, re = ns.dim.s.r, ns.dim.e.r + local gofile = vim.fn.expand("%") + local args = {gotests, "-w", "-only", funame, gofile} + if parallel then + table.insert(args, "-parallel") + end + add_test(args) +end + +ut.all_test = function(parallel) + parallel = parallel or false + local gofile = vim.fn.expand("%") + local args = {gotests, "-all", "-w", gofile} + if parallel then + table.insert(args, "-parallel") + end + add_test(args) +end + +ut.exported_test = function(parallel) + parallel = parallel or false + local gofile = vim.fn.expand("%") + local args = {gotests, "-exported", "-w", gofile} + if parallel then + table.insert(args, "-parallel") + end + add_test(args) +end + +return ut diff --git a/lua/go/install.lua b/lua/go/install.lua new file mode 100644 index 0000000..c89d5d3 --- /dev/null +++ b/lua/go/install.lua @@ -0,0 +1,61 @@ +local uv = vim.loop +local gopath = vim.fn.expand("$GOPATH") +local gobinpath = gopath .. "/bin/" +local url = { + golines = "segmentio/golines", + gofumpt = "mvdan/gofumpt", + gofumports = "mvdan/gofumpt", + gomodifytags = "fatih/gomodifytags", + gotsts = "cweill/gotests", +} + +local function install(bin) + local state = uv.fs_stat(gobinpath .. bin) + if not state then + print("installing " .. bin) + local u = url[bin] + if u == nil then + print("command " .. bin .. " not supported, please update install.lua") + return + end + local setup = { + "go", "get", + u + } + vim.fn.jobstart( + setup, + -- setup.args, + { + on_stdout = function(c, data, name) + print(data) + end + } + ) + end +end + +local function update(bin) + local u = url[bin] + if u == nil then + print("command " .. bin .. " not supported, please update install.lua") + return + end + local setup = {"go", "get", "-u", u} + + vim.fn.jobstart( + setup, + { + on_stdout = function(c, data, name) + print(data) + end + } + ) +end + +local function install_all() + for key, value in pairs(url) do + install(key) + end +end + +return {install = install, update = update, install_all = install_all} diff --git a/lua/go/runner.lua b/lua/go/runner.lua new file mode 100644 index 0000000..9dbba1d --- /dev/null +++ b/lua/go/runner.lua @@ -0,0 +1,65 @@ +local uv, api = vim.loop, vim.api + +local check_same = function(tbl1, tbl2) + if #tbl1 ~= #tbl2 then + return + end + for k, v in ipairs(tbl1) do + if v ~= tbl2[k] then + return true + end + end + return false +end + +local run = function(cmd, args, on_stdout, stdin_data, buf) + buf = buf or false + local stdin = uv.new_pipe(false) + local stdout = uv.new_pipe(false) + local stderr = uv.new_pipe(false) + local file = api.nvim_buf_get_name(0) + local handle, pid = + uv.spawn( + cmd, + { + stdio = {stdin, stdout, stderr}, + args = args + }, + function(code, signal) -- on exit + end + ) + + uv.read_start(stdout, vim.schedule_wrap(on_stdout)) + + uv.read_start( + stderr, + function(err, data) + assert(not err, err) + if data then + print("stderr chunk", stderr, data) + end + end + ) + if buf then + for i = 1, #stdin_data do + print("sending " .. stdin_data[i]) + stdin:write(stdin_data[i]) + end + if not stdin:is_closing() then + stdin:close() + end + end + + uv.shutdown( + stdin, + function() + uv.close( + handle, + function() + end + ) + end + ) +end + +return {golines_format = golines_format, run = run} diff --git a/lua/go/tags.lua b/lua/go/tags.lua new file mode 100644 index 0000000..b640690 --- /dev/null +++ b/lua/go/tags.lua @@ -0,0 +1,86 @@ +local tags = {} +-- support -add-tags, --add-options, -remove-tags, -remove-options, clear-tags, clear-options +-- for struct and line range +-- gomodifytags -file demo.go -struct Server -add-tags json +-- gomodifytags -file demo.go -struct Server -add-tags json -w +-- gomodifytags -file demo.go -struct Server -add-tags json,xml +-- gomodifytags -file demo.go -struct Server -add-tags json,xml -transform camelcase +-- gomodifytags -file demo.go -line 8,11 -clear-tags xml + +local opts = {"-add-tags", "-add-options", "-remove-tags", "-remove-options", "-clear-tags", "-clear-options"} +local gomodify = "gomodifytags" +local transform = vim.g.go_nvim_tag_transfer +tags.modify = function(...) + require("go.install").install(gomodify) + local fname = vim.fn.expand("%") -- %:p:h ? %:p + local row, col = unpack(vim.api.nvim_win_get_cursor(0)) + row, col = row + 1, col + 1 + local ns = require("go.ts.go").get_struct_node_at_pos(row, col) + if ns == nil or ns == {} then + return + end + + -- print("parnode" .. vim.inspect(ns)) + local struct_name = ns.name + local rs, re = ns.dim.s.r, ns.dim.e.r + setup = {gomodify, "-format", "json", "-file", fname, "-struct", struct_name, '-w'} + if transform ~= nil then + table.insert(setup.args, "-transform") + table.insert(setup.args, transform) + end + local arg = {...} + for i, v in ipairs(arg) do + table.insert(setup, v) + end + + if #arg == 1 and arg[1] ~= "clear-tags" then + table.insert(setup, "json") + end + -- print(vim.inspect(setup)) + local j = + vim.fn.jobstart( + setup, + { + on_stdout = function(jobid, data, event) + if not data or #data == 1 and data[1] == "" then + return + end + local tagged = vim.fn.json_decode(data) + -- print(vim.inspect(tagged)) + -- print(tagged["start"], tagged["end"], tagged.lines) + if tagged.errors ~= nil or tagged.lines == nil or tagged["start"] == nil or tagged["start"] == 0 then + print("failed to set tags" .. vim.inspect(tagged)) + end + vim.api.nvim_buf_set_lines(0, tagged["start"]-1, tagged["start"]-1+#tagged.lines, false, tagged.lines) + vim.cmd("write") + print("wrote " .. tostring(tagged.lines)) + end + } + ) +end + +tags.add = function(...) + local cmd = {"-add-tags"} + local arg = {...} + for _, v in ipairs(arg) do + table.insert(cmd, v) + end + + tags.modify(unpack(cmd)) +end + +tags.rm = function(...) + local cmd = {"-remove-tags"} + local arg = {...} + for _, v in ipairs(arg) do + table.insert(cmd, v) + end + tags.modify(unpack(cmd)) +end + +tags.clear = function() + local cmd = {"-clear-tags"} + tags.modify(unpack(cmd)) +end + +return tags diff --git a/lua/go/ts/go.lua b/lua/go/ts/go.lua new file mode 100644 index 0000000..f4ed519 --- /dev/null +++ b/lua/go/ts/go.lua @@ -0,0 +1,158 @@ +M = { + -- query_struct = "(type_spec name:(type_identifier) @definition.struct type: (struct_type))", + query_package = "(package_clause (package_identifier)@package.name)@package.clause", + + query_struct_id = "(type_spec name:(type_identifier) @definition.struct (struct_type))", + + query_em_struct_id = "(field_declaration name:(field_identifier) @definition.struct (struct_type))", + + query_struct_block = "(type_declaration (type_spec name:(type_identifier) @struct.name type: (struct_type)))@struct.declaration", + + query_em_struct_block = "(field_declaration name:(field_identifier)@struct.name type: (struct_type)) @struct.declaration", + + query_struct_block_from_id = "((type_spec name:(type_identifier) type: (struct_type)))@block.struct_from_id", + + --query_em_struct = "(field_declaration name:(field_identifier) @definition.struct type: (struct_type))", + query_interface_id = [[(type_declaration (type_spec name:(type_identifier) @interface.name type:(interface_type)))@interface.declaration]], + + query_interface_method = [[(method_spec name: (field_identifier)@method.name)@interface.method.declaration]], + + query_func = "((function_declaration name: (identifier)@function.name) @function.declaration)", + -- query_method = "(method_declaration receiver: (parameter_list (parameter_declaration name:(identifier)@method.receiver.name type:(type_identifier)@method.receiver.type)) name:(field_identifier)@method.name)@method.declaration" + + query_method_name = [[(method_declaration + receiver: (parameter_list)@method.receiver + name: (field_identifier)@method.name + body:(block))@method.declaration]], + + query_method_void = [[(method_declaration + receiver: (parameter_list + (parameter_declaration + name: (identifier)@method.receiver.name + type: (pointer_type)@method.receiver.type) + ) + name: (field_identifier)@method.name + parameters: (parameter_list)@method.parameter + body:(block) + )@method.declaration]], + + query_method_multi_ret = [[(method_declaration + receiver: (parameter_list + (parameter_declaration + name: (identifier)@method.receiver.name + type: (pointer_type)@method.receiver.type) + ) + name: (field_identifier)@method.name + parameters: (parameter_list)@method.parameter + result: (parameter_list)@method.result + body:(block) + )@method.declaration]], + + query_method_single_ret = [[(method_declaration + receiver: (parameter_list + (parameter_declaration + name: (identifier)@method.receiver.name + type: (pointer_type)@method.receiver.type) + ) + name: (field_identifier)@method.name + parameters: (parameter_list)@method.parameter + result: (type_identifier)@method.result + body:(block) + )@method.declaration]], + + query_tr_method_void = [[(method_declaration + receiver: (parameter_list + (parameter_declaration + name: (identifier)@method.receiver.name + type: (type_identifier)@method.receiver.type) + ) + name: (field_identifier)@method.name + parameters: (parameter_list)@method.parameter + body:(block) + )@method.declaration]], + + query_tr_method_multi_ret = [[(method_declaration + receiver: (parameter_list + (parameter_declaration + name: (identifier)@method.receiver.name + type: (type_identifier)@method.receiver.type) + ) + name: (field_identifier)@method.name + parameters: (parameter_list)@method.parameter + result: (parameter_list)@method.result + body:(block) + )@method.declaration]], + + query_tr_method_single_ret = [[(method_declaration + receiver: (parameter_list + (parameter_declaration + name: (identifier)@method.receiver.name + type: (type_identifier)@method.receiver.type) + ) + name: (field_identifier)@method.name + parameters: (parameter_list)@method.parameter + result: (type_identifier)@method.result + body:(block) + )@method.declaration]] +} +function get_name_defaults() + return { + ["func"] = "function", + ["if"] = "if", + ["else"] = "else", + ["for"] = "for", + } +end + +M.get_struct_node_at_pos = function(row, col) + local query = require("go.ts.go").query_struct_block .. " " .. require("go.ts.go").query_em_struct_block + + local nodes = require("go.ts.nodes") + local bufn = vim.fn.bufnr("") + + local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) + return ns[#ns] +end + +M.get_interface_node_at_pos = function(row, col) + local query = require("go.ts.go").query_interface_id + local nodes = require("go.ts.nodes") + local bufn = vim.fn.bufnr("") + + local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) + return ns[#ns] +end + +M.get_interface_method_node_at_pos = function(row, col) + local query = require("go.ts.go").query_interface_method + local nodes = require("go.ts.nodes") + local bufn = vim.fn.bufnr("") + + local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) + return ns[#ns] +end + +M.get_func_method_node_at_pos = function(row, col) + local query = require("go.ts.go").query_func .. " " .. require("go.ts.go").query_method_name + -- local query = require("go.ts.go").query_method_name + local nodes = require("go.ts.nodes") + local bufn = vim.fn.bufnr("") + + local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) + if ns == nil then return nil end + return ns[#ns] +end + +M.get_package_node_at_pos = function(row, col) + if row > 10 then return end + local query = require("go.ts.go").query_package + -- local query = require("go.ts.go").query_method_name + local nodes = require("go.ts.nodes") + local bufn = vim.fn.bufnr("") + + local ns = nodes.nodes_at_cursor(query, get_name_defaults(), bufn, row, col) + if ns == nil then return nil end + return ns[#ns] +end + +return M diff --git a/lua/go/ts/nodes.lua b/lua/go/ts/nodes.lua new file mode 100644 index 0000000..d8ce786 --- /dev/null +++ b/lua/go/ts/nodes.lua @@ -0,0 +1,240 @@ +-- part of the code from polarmutex/contextprint.nvim + +local ts_utils = require("nvim-treesitter.ts_utils") +local ts_query = require("nvim-treesitter.query") +local parsers = require("nvim-treesitter.parsers") +local locals = require("nvim-treesitter.locals") +local utils = require("go.ts.utils") +local ulog = require("go.utils").log + +local M = {} + +-- Array +M.intersect_nodes = function(nodes, row, col) + local found = {} + for idx = 1, #nodes do + local node = nodes[idx] + local sRow = node.dim.s.r + local sCol = node.dim.s.c + local eRow = node.dim.e.r + local eCol = node.dim.e.c + + if utils.intersects(row, col, sRow, sCol, eRow, eCol) then + table.insert(found, node) + end + end + + return found +end + +M.count_parents = function(node) + local count = 0 + local n = node.declaring_node + while n ~= nil do + n = n:parent() + count = count + 1 + end + return count +end + +-- @param nodes Array +-- perf note. I could memoize some values here... +M.sort_nodes = function(nodes) + table.sort( + nodes, + function(a, b) + return M.count_parents(a) < M.count_parents(b) + end + ) + return nodes +end + +-- local lang = vim.api.nvim_buf_get_option(bufnr, 'ft') +-- node_wrapper +-- returns [{ +-- declaring_node = tsnode +-- dim: {s: {r, c}, e: {r, c}}, +-- name: string +-- type: string +-- }] +M.get_nodes = function(query, lang, defaults, bufnr) + bufnr = bufnr or 0 + local success, parsed_query = + pcall( + function() + return vim.treesitter.parse_query(lang, query) + end + ) + if not success then + return nil + end + + local parser = parsers.get_parser(bufnr, lang) + local root = parser:parse()[1]:root() + local start_row, _, end_row, _ = root:range() + -- local n = ts_utils.get_node_at_cursor() + -- local a, b, c, d = ts_utils.get_node_range(n) + local results = {} + for match in ts_query.iter_prepared_matches(parsed_query, root, bufnr, start_row, end_row) do + local sRow, sCol, eRow, eCol + local declaration_node + local type = "nil" + local name = "nil" + locals.recurse_local_nodes( + match, + function(_, node, path) + local idx = string.find(path, ".", 1, true) + local op = string.sub(path, idx + 1, #path) + + local a1, b1, c1, d1 = ts_utils.get_node_range(node) + + type = string.sub(path, 1, idx - 1) + if name == nil then + name = defaults[type] or "empty" + end + + if op == "name" then + name = ts_utils.get_node_text(node)[1] + elseif op == "declaration" then + declaration_node = node + sRow, sCol, eRow, eCol = node:range() + sRow = sRow + 1 + eRow = eRow + 1 + sCol = sCol + 1 + eCol = eCol + 1 + end + end + ) + + if declaration_node ~= nil then + table.insert( + results, + { + declaring_node = declaration_node, + dim = { + s = {r = sRow, c = sCol}, + e = {r = eRow, c = eCol} + }, + name = name, + type = type + } + ) + end + end + + return results +end + +-- local lang = vim.api.nvim_buf_get_option(bufnr, 'ft') +-- node_wrapper +-- returns { +-- declaring_node = tsnode +-- dim: {s: {r, c}, e: {r, c}}, +-- name: string +-- type: string +-- } +M.get_all_nodes = function(query, lang, defaults, bufnr, pos_row, pos_col) + bufnr = bufnr or 0 + -- todo a huge number + pos_row = pos_row or 30000 + local success, parsed_query = + pcall( + function() + return vim.treesitter.parse_query(lang, query) + end + ) + if not success then + return nil + end + + local parser = parsers.get_parser(bufnr, lang) + local root = parser:parse()[1]:root() + local start_row, _, end_row, _ = root:range() + -- local n = ts_utils.get_node_at_cursor() + -- local a, b, c, d = ts_utils.get_node_range(n) + -- ulog("node range " .. tostring(a) .. tostring(b) .. tostring(c).. tostring(d)) + -- ulog("cru node:" .. vim.inspect(n) .. "text: " .. vim.inspect(ts_utils.get_node_text(n))) + local results = {} + for match in ts_query.iter_prepared_matches(parsed_query, root, bufnr, start_row, end_row) do + local sRow, sCol, eRow, eCol + local declaration_node + local type = "" + local name = "" + local op = "" + local method_receiver = "" + + locals.recurse_local_nodes( + match, + function(_, node, path) + --local idx = string.find(path, ".", 1, true) + local idx = string.find(path, ".[^.]*$") -- find last . + op = string.sub(path, idx + 1, #path) + local a1, b1, c1, d1 = ts_utils.get_node_range(node) + local dbg_txt = ts_utils.get_node_text(node, bufnr)[1] + type = string.sub(path, 1, idx - 1) + + ulog( "node" .. vim.inspect(node) .. "\n path: " .. path .. " op: " .. op .. " type: " .. type .. "\n txt: " .. dbg_txt .. "\n range: " .. tostring(a1) .. ":" .. tostring(b1) .. " TO " .. tostring(c1) .. ":" .. tostring(d1)) + -- + -- may not handle complex node + if op == "name" then + -- ulog("node name " .. name) + name = ts_utils.get_node_text(node, bufnr)[1] + elseif op == "declaration" or op == "clause" then + declaration_node = node + sRow, sCol, eRow, eCol = node:range() + sRow = sRow + 1 + eRow = eRow + 1 + sCol = sCol + 1 + eCol = eCol + 1 + end + end + ) + if declaration_node ~= nil then + -- ulog(name .. " " .. op) + -- ulog(sRow, pos_row) + if sRow > pos_row then + ulog("beyond " .. tostring(pos_row)) + break + end + table.insert( + results, + { + declaring_node = declaration_node, + dim = { + s = {r = sRow, c = sCol}, + e = {r = eRow, c = eCol} + }, + name = name, + operator = op, + type = type + } + ) + end + end + -- ulog("total nodes got: " .. tostring(#results)) + return results +end + +M.nodes_at_cursor = function(query, default, bufnr, row, col) + bufnr = bufnr or vim.api.nvim_get_current_buf() + local ft = vim.api.nvim_buf_get_option(bufnr, "ft") + if row == nil or col == nil then + row, col = unpack(vim.api.nvim_win_get_cursor(0)) + row, col = row + 1, col + 1 + end + local nodes = M.get_all_nodes(query, ft, default, bufnr, row, col) + if nodes == nil then + print("Unable to find any nodes. Is your query correct?") + return nil + end + + nodes = M.sort_nodes(M.intersect_nodes(nodes, row, col)) + if nodes == nil or #nodes == 0 then + print("Unable to find any nodes at pos. " .. tostring(row) .. ":" .. tostring(col)) + return nil + end + + return nodes +end + +return M diff --git a/lua/go/ts/utils.lua b/lua/go/ts/utils.lua new file mode 100644 index 0000000..1efc966 --- /dev/null +++ b/lua/go/ts/utils.lua @@ -0,0 +1,19 @@ +local M = {} +M.intersects = function(row, col, sRow, sCol, eRow, eCol) + -- print(row, col, sRow, sCol, eRow, eCol) + if sRow > row or eRow < row then + return false + end + + if sRow == row and sCol > col then + return false + end + + if eRow == row and eCol < col then + return false + end + + return true +end + +return M diff --git a/lua/go/utils.lua b/lua/go/utils.lua new file mode 100644 index 0000000..ade4508 --- /dev/null +++ b/lua/go/utils.lua @@ -0,0 +1,41 @@ +local util = {} +util.check_same = function(tbl1, tbl2) + if #tbl1 ~= #tbl2 then + return + end + for k, v in ipairs(tbl1) do + if v ~= tbl2[k] then + return true + end + end + return false +end + +util.copy_array = function(from, to) + for i = 1, #from do + to[i] = from[i] + end +end + +util.deepcopy = function (orig) + local orig_type = type(orig) + local copy + if orig_type == 'table' then + copy = {} + for orig_key, orig_value in next, orig, nil do + copy[util.deepcopy(orig_key)] = util.deepcopy(orig_value) + end + setmetatable(copy, util.deepcopy(getmetatable(orig))) + else -- number, string, boolean, etc + copy = orig + end + return copy + end + +util.log = function(...) + if vim.g.go_nvim_verbose then + print(...) + end +end + +return util diff --git a/lua/tests/fixtures/fmt/goimports.go b/lua/tests/fixtures/fmt/goimports.go new file mode 100644 index 0000000..716653d --- /dev/null +++ b/lua/tests/fixtures/fmt/goimports.go @@ -0,0 +1,5 @@ +package main + +func main() { + fmt.Println("vim-go") +} diff --git a/lua/tests/fixtures/fmt/goimports_golden.go b/lua/tests/fixtures/fmt/goimports_golden.go new file mode 100644 index 0000000..50e8d8d --- /dev/null +++ b/lua/tests/fixtures/fmt/goimports_golden.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("vim-go") +} diff --git a/lua/tests/fixtures/fmt/hello.go b/lua/tests/fixtures/fmt/hello.go new file mode 100644 index 0000000..3be42f6 --- /dev/null +++ b/lua/tests/fixtures/fmt/hello.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + + func main() { +fmt.Println("vim-go") +} diff --git a/lua/tests/fixtures/fmt/hello_golden.go b/lua/tests/fixtures/fmt/hello_golden.go new file mode 100644 index 0000000..50e8d8d --- /dev/null +++ b/lua/tests/fixtures/fmt/hello_golden.go @@ -0,0 +1,7 @@ +package main + +import "fmt" + +func main() { + fmt.Println("vim-go") +} diff --git a/lua/tests/fixtures/tags/add_all_golden.go b/lua/tests/fixtures/tags/add_all_golden.go new file mode 100644 index 0000000..d45d536 --- /dev/null +++ b/lua/tests/fixtures/tags/add_all_golden.go @@ -0,0 +1,17 @@ +package main + +type Server struct { + Name string `json:"name"` + ID int `json:"id"` + MyHomeAddress string `json:"my_home_address"` + SubDomains []string `json:"sub_domains"` + Empty string `json:"empty"` + Example int64 `json:"example"` + Example2 string `json:"example_2"` + Bar struct { + Four string `json:"four"` + Five string `json:"five"` + } `json:"bar"` + Lala interface{} `json:"lala"` +} //eos +//eof diff --git a/lua/tests/fixtures/tags/add_all_golden_options.go b/lua/tests/fixtures/tags/add_all_golden_options.go new file mode 100644 index 0000000..852d5db --- /dev/null +++ b/lua/tests/fixtures/tags/add_all_golden_options.go @@ -0,0 +1,17 @@ +package main + +type Server struct { + Name string `json:"name,omitempty"` + ID int `json:"id,omitempty"` + MyHomeAddress string `json:"my_home_address,omitempty"` + SubDomains []string `json:"sub_domains,omitempty"` + Empty string `json:"empty,omitempty"` + Example int64 `json:"example,omitempty"` + Example2 string `json:"example_2,omitempty"` + Bar struct { + Four string `json:"four,omitempty"` + Five string `json:"five,omitempty"` + } `json:"bar,omitempty"` + Lala interface{} `json:"lala,omitempty"` +} //eos +//eof diff --git a/lua/tests/fixtures/tags/add_all_input.go b/lua/tests/fixtures/tags/add_all_input.go new file mode 100644 index 0000000..51346ae --- /dev/null +++ b/lua/tests/fixtures/tags/add_all_input.go @@ -0,0 +1,17 @@ +package main + +type Server struct { + Name string + ID int + MyHomeAddress string + SubDomains []string + Empty string + Example int64 + Example2 string + Bar struct { + Four string + Five string + } + Lala interface{} +} //eos +//eof diff --git a/lua/tests/fixtures/tags/remove_all_golden.go b/lua/tests/fixtures/tags/remove_all_golden.go new file mode 100644 index 0000000..51346ae --- /dev/null +++ b/lua/tests/fixtures/tags/remove_all_golden.go @@ -0,0 +1,17 @@ +package main + +type Server struct { + Name string + ID int + MyHomeAddress string + SubDomains []string + Empty string + Example int64 + Example2 string + Bar struct { + Four string + Five string + } + Lala interface{} +} //eos +//eof diff --git a/lua/tests/fixtures/tags/remove_all_input.go b/lua/tests/fixtures/tags/remove_all_input.go new file mode 100644 index 0000000..d45d536 --- /dev/null +++ b/lua/tests/fixtures/tags/remove_all_input.go @@ -0,0 +1,17 @@ +package main + +type Server struct { + Name string `json:"name"` + ID int `json:"id"` + MyHomeAddress string `json:"my_home_address"` + SubDomains []string `json:"sub_domains"` + Empty string `json:"empty"` + Example int64 `json:"example"` + Example2 string `json:"example_2"` + Bar struct { + Four string `json:"four"` + Five string `json:"five"` + } `json:"bar"` + Lala interface{} `json:"lala"` +} //eos +//eof diff --git a/lua/tests/fixtures/ts/interfaces.go b/lua/tests/fixtures/ts/interfaces.go new file mode 100644 index 0000000..f69db13 --- /dev/null +++ b/lua/tests/fixtures/ts/interfaces.go @@ -0,0 +1,48 @@ +// https://gobyexample.com/interfaces +// +package main + +import ( + "fmt" + "math" +) + +type Geometry interface { + Area() float64 + perim() float64 +} + +type rect struct { + width, height float64 +} +type circle struct { + radius float64 +} + +func (r rect) Area() float64 { + return r.width * r.height +} +func (r rect) perim() float64 { + return 2*r.width + 2*r.height +} + +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) { + fmt.Println(g) + fmt.Println(g.Area()) + fmt.Println(g.perim()) +} + +func main() { + r := rect{width: 3, height: 4} + c := circle{radius: 5} + + measure(r) + measure(c) +} diff --git a/lua/tests/fixtures/ts/playlist.go b/lua/tests/fixtures/ts/playlist.go new file mode 100644 index 0000000..f58af81 --- /dev/null +++ b/lua/tests/fixtures/ts/playlist.go @@ -0,0 +1,99 @@ +// code form https://medium.com/backendarmy/linked-lists-in-go-f7a7b27a03b9 +// this code is use to test treesiter parser +package main + +import "fmt" + +type song struct { + name string + artist string + next *song +} + +type playlist struct { + name string + head *song + nowPlaying *song +} + +func createPlaylist(name string) *playlist { + return &playlist{ + name: name, + } +} + +func (p *playlist) addSong(name, artist string) error { + s := &song{ + name: name, + artist: artist, + } + if p.head == nil { + p.head = s + } else { + currentNode := p.head + for currentNode.next != nil { + currentNode = currentNode.next + } + currentNode.next = s + } + return nil +} + +func (p *playlist) showAllSongs() error { + currentNode := p.head + if currentNode == nil { + fmt.Println("Playlist is empty.") + return nil + } + fmt.Printf("%+v\n", *currentNode) + for currentNode.next != nil { + currentNode = currentNode.next + fmt.Printf("%+v\n", *currentNode) + } + + return nil +} + +func (p *playlist) startPlaying() *song { + p.nowPlaying = p.head + return p.nowPlaying +} + +func (p *playlist) nextSong() *song { + p.nowPlaying = p.nowPlaying.next + return p.nowPlaying +} + +func (p *playlist) NextSong() *song { // exported + p.nowPlaying = p.nowPlaying.next + return p.nowPlaying +} + +func main() { + playlistName := "myplaylist" + myPlaylist := createPlaylist(playlistName) + fmt.Println("Created playlist") + fmt.Println() + + fmt.Print("Adding songs to the playlist...\n\n") + myPlaylist.addSong("Ophelia", "The Lumineers") + myPlaylist.addSong("Shape of you", "Ed Sheeran") + myPlaylist.addSong("Stubborn Love", "The Lumineers") + myPlaylist.addSong("Feels", "Calvin Harris") + fmt.Println("Showing all songs in playlist...") + myPlaylist.showAllSongs() + fmt.Println() + + myPlaylist.startPlaying() + fmt.Printf("Now playing: %s by %s\n", myPlaylist.nowPlaying.name, myPlaylist.nowPlaying.artist) + fmt.Println() + + myPlaylist.nextSong() + fmt.Println("Changing next song...") + fmt.Printf("Now playing: %s by %s\n", myPlaylist.nowPlaying.name, myPlaylist.nowPlaying.artist) + fmt.Println() + myPlaylist.nextSong() + fmt.Println("Changing next song...") + fmt.Printf("Now playing: %s by %s\n", myPlaylist.nowPlaying.name, myPlaylist.nowPlaying.artist) + +} diff --git a/lua/tests/go_comment_spec.lua b/lua/tests/go_comment_spec.lua new file mode 100644 index 0000000..148a2e0 --- /dev/null +++ b/lua/tests/go_comment_spec.lua @@ -0,0 +1,42 @@ +local eq = assert.are.same + +local busted = require("plenary/busted") +local cur_dir = vim.fn.expand("%:p:h") +describe( + "should get nodes ", + function() + vim.g.go_nvim_verbose = true + vim.g.go_nvim_comment_placeholder = "go.nvim" + + local status = require("plenary.reload").reload_module("go.nvim") + status = require("plenary.reload").reload_module("nvim-treesitter/nvim-treesitter") + + local name = vim.fn.tempname() .. ".go" + print("tmp:" .. name) + -- + local path = cur_dir .. "/lua/tests/fixtures/ts/playlist.go" -- %:p:h ? %:p + local lines = vim.fn.readfile(path) + vim.fn.writefile(lines, name) + local cmd = "silent exe 'e " .. name .. "'" + vim.cmd(cmd) + + local bufn = vim.fn.bufnr("") + it( + "should get struct playlist and generate comments", + function() + vim.fn.setpos(".", {bufn, 20, 14, 0}) + local query = require("go.comment").gen(20, 14) + eq("// createPlaylist function go.nvim", query) + end + ) + + it( + "should get struct playlist and generate comments", + function() + vim.fn.setpos(".", {bufn, 14, 4, 0}) + local query = require("go.comment").gen(14, 4) + eq("// playlist struct go.nvim", query) + end + ) + end +) diff --git a/lua/tests/go_fmt_spec.lua b/lua/tests/go_fmt_spec.lua new file mode 100644 index 0000000..abdfef7 --- /dev/null +++ b/lua/tests/go_fmt_spec.lua @@ -0,0 +1,129 @@ + +local eq = assert.are.same +local cur_dir = vim.fn.expand("%:p:h") + +describe( + "should run gofmt", + 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 fmt", + function() + local name = vim.fn.tempname() .. ".go" + print("tmp:" .. name) + -- + local path = cur_dir .. "/lua/tests/fixtures/fmt/hello.go" -- %:p:h ? %:p + print("test:" .. path) + local lines = vim.fn.readfile(path) + vim.fn.writefile(lines, name) + local expected = + vim.fn.join(vim.fn.readfile(cur_dir .. "/lua/tests/fixtures/fmt/hello_golden.go"), "\n") + local cmd = " silent exe 'e " .. name .. "'" + vim.cmd(cmd) + local l = vim.api.nvim_buf_get_lines(0, 0, -1, true) + print("buf read: " .. vim.inspect(l)) + + vim.bo.filetype = "go" + + print("exp:" .. vim.inspect(expected)) + print("tmp" .. name) + + local gofmt = require("go.format") + gofmt.gofmt() + -- enable the channel response + vim.wait(100, function () end) + local fmt = + vim.fn.join(vim.fn.readfile(name), "\n") + print("fmt" .. fmt) + vim.fn.assert_equal(fmt, expected) + eq(expected, fmt) + local cmd = "bd! ".. name + vim.cmd(cmd) + end + ) + it( + "should run fmt sending from buffer", + function() + local name = vim.fn.tempname() .. ".go" + print("tmp:" .. name) + -- + local path = cur_dir .. "/lua/tests/fixtures/fmt/hello.go" -- %:p:h ? %:p + print("test:" .. path) + local lines = vim.fn.readfile(path) + vim.fn.writefile(lines, name) + local expected = + vim.fn.join(vim.fn.readfile(cur_dir .. "/lua/tests/fixtures/fmt/hello_golden.go"), "\n") + local cmd = " silent exe 'e " .. name .. "'" + vim.cmd(cmd) + local l = vim.api.nvim_buf_get_lines(0, 0, -1, true) + print("buf read: " .. vim.inspect(l)) + + vim.bo.filetype = "go" + + print("exp:" .. vim.inspect(expected)) + print("tmp" .. name) + + local gofmt = require("go.format") + gofmt.gofmt(true) + -- enable the channel response + vim.wait(100, function () end) + local fmt = + vim.fn.join(vim.fn.readfile(name), "\n") + print("fmt" .. fmt) + vim.fn.assert_equal(fmt, expected) + eq(expected, fmt) + local cmd = "bd! ".. name + vim.cmd(cmd) + end + ) + it( + "should run import from file", + function() + local path = cur_dir .. "/lua/tests/fixtures/fmt/goimports.go" -- %:p:h ? %:p + local expected = vim.fn.join(vim.fn.readfile(cur_dir .. "/lua/tests/fixtures/fmt/goimports_golden.go"), "\n") + local name = vim.fn.tempname() .. ".go" + print(name) + local lines = vim.fn.readfile(path) + vim.fn.writefile(lines, name) + local cmd = " silent exe 'e " .. name .. "'" + vim.cmd(cmd) + local gofmt = require("go.format") + gofmt.goimport() + vim.wait( 100, function() end) + local fmt = vim.fn.join(vim.fn.readfile(name), "\n") + eq(expected, fmt) + cmd = "bd! " .. name + vim.cmd(cmd) + end + ) + it( + "should run import from file buffer", + function() + local path = cur_dir .. "/lua/tests/fixtures/fmt/goimports.go" -- %:p:h ? %:p + local expected = vim.fn.join(vim.fn.readfile(cur_dir .. "/lua/tests/fixtures/fmt/goimports_golden.go"), "\n") + local name = vim.fn.tempname() .. ".go" + print(name) + local lines = vim.fn.readfile(path) + local cmd = " silent exe 'e " .. name .. "'" + vim.fn.writefile(lines, name) + vim.cmd(cmd) + + print("code write to " .. name) + local gofmt = require("go.format") + gofmt.goimport(true) + + vim.wait( + 100, + function() + end + ) + local fmt = vim.fn.join(vim.fn.readfile(name), "\n") + + print(fmt) + eq(expected, fmt) + end + ) + end +) diff --git a/lua/tests/go_tags_spec.lua b/lua/tests/go_tags_spec.lua new file mode 100644 index 0000000..222cba7 --- /dev/null +++ b/lua/tests/go_tags_spec.lua @@ -0,0 +1,162 @@ +local helpers = {} +local busted = require("plenary/busted") + +local eq = assert.are.same +local cur_dir = vim.fn.expand("%:p:h") +local ulog = require('go.utils').log +describe( + "should run gotags", + 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 add json tags", + function() + -- + local name = vim.fn.tempname() .. ".go" + local path = cur_dir .. "/lua/tests/fixtures/tags/add_all_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/tags/add_all_golden.go"), "\n") + local cmd = " silent exe 'e " .. name .. "'" + vim.cmd(cmd) + local bufn = vim.fn.bufnr("") + + vim.fn.setpos(".", {bufn, 8, 4, 0}) + + + local l = vim.api.nvim_buf_get_lines(0, 0, -1, true) + -- ulog("buf read: " .. vim.inspect(l)) + + vim.bo.filetype = "go" + + -- ulog("exp:" .. vim.inspect(expected)) + + local gotags = require("go.tags") + gotags.add() + -- enable the channel response + vim.wait(100, function () end) + local fmt = + vim.fn.join(vim.fn.readfile(name), "\n") + -- ulog("tagged file: " .. fmt) + vim.fn.assert_equal(fmt, expected) + eq(expected, fmt) + local cmd = "bd! ".. name + vim.cmd(cmd) + end + ) + it( + "should rm json tags", + function() + local name = vim.fn.tempname() .. ".go" + -- + local path = cur_dir .. "/lua/tests/fixtures/tags/add_all_golden.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/tags/add_all_input.go"), "\n") + local cmd = " silent exe 'e " .. name .. "'" + vim.cmd(cmd) + local bufn = vim.fn.bufnr("") + + vim.fn.setpos(".", {bufn, 8, 4, 0}) + + + local l = vim.api.nvim_buf_get_lines(0, 0, -1, true) + -- ulog("buf read: " .. vim.inspect(l)) + + vim.bo.filetype = "go" + + -- ulog("exp:" .. vim.inspect(expected)) + + local gotags = require("go.tags") + gotags.rm('json') + -- enable the channel response + vim.wait(100, function () end) + local fmt = + vim.fn.join(vim.fn.readfile(name), "\n") + -- ulog("tagged file: " .. fmt) + vim.fn.assert_equal(fmt, expected) + eq(expected, fmt) + local cmd = "bd! ".. name + vim.cmd(cmd) + end + ) + it( + "should run clear json tags by default", + function() + local name = vim.fn.tempname() .. ".go" + -- + local path = cur_dir .. "/lua/tests/fixtures/tags/add_all_golden.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/tags/add_all_input.go"), "\n") + local cmd = " silent exe 'e " .. name .. "'" + vim.cmd(cmd) + local bufn = vim.fn.bufnr("") + + vim.fn.setpos(".", {bufn, 8, 4, 0}) + + + local l = vim.api.nvim_buf_get_lines(0, 0, -1, true) + -- ulog("buf read: " .. vim.inspect(l)) + + vim.bo.filetype = "go" + + -- ulog("exp:" .. vim.inspect(expected)) + + local gotags = require("go.tags") + gotags.rm() + -- enable the channel response + vim.wait(100, function () end) + local fmt = + vim.fn.join(vim.fn.readfile(name), "\n") + -- ulog("tagged file: " .. fmt) + vim.fn.assert_equal(fmt, expected) + eq(expected, fmt) + local cmd = "bd! ".. name + vim.cmd(cmd) + end + ) + it( + "should clear all tags", + function() + local name = vim.fn.tempname() .. ".go" + -- + local path = cur_dir .. "/lua/tests/fixtures/tags/add_all_golden.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/tags/add_all_input.go"), "\n") + local cmd = " silent exe 'e " .. name .. "'" + vim.cmd(cmd) + local bufn = vim.fn.bufnr("") + + vim.fn.setpos(".", {bufn, 8, 4, 0}) + + + local l = vim.api.nvim_buf_get_lines(0, 0, -1, true) + -- ulog("buf read: " .. vim.inspect(l)) + + vim.bo.filetype = "go" + + -- ulog("exp:" .. vim.inspect(expected)) + + local gotags = require("go.tags") + gotags.rm() + -- enable the channel response + vim.wait(100, function () end) + local fmt = + vim.fn.join(vim.fn.readfile(name), "\n") + -- ulog("tagged file: " .. fmt) + vim.fn.assert_equal(fmt, expected) + eq(expected, fmt) + local cmd = "bd! ".. name + vim.cmd(cmd) + end + ) + end +) diff --git a/lua/tests/go_ts_node_spec.lua b/lua/tests/go_ts_node_spec.lua new file mode 100644 index 0000000..802ee18 --- /dev/null +++ b/lua/tests/go_ts_node_spec.lua @@ -0,0 +1,211 @@ +local eq = assert.are.same +local input = { + "package a", + "", + "type x struct {", + "\tFoo int", + "\tbar int", + "\ty struct {", + "\t\tFoo int", + "\t\tbar int", + "\t}", + "}", + "type z struct{}" +} + +local default = { + ["function"] = "func", + ["method"] = "func", + ["struct"] = "struct", + ["interface"] = "interface" +} + +local output_inner = { + "package a", + "", + "type x struct {", + '\tFoo int `xx:"foo"`', + '\tbar int `xx:"bar"`', + "y struct {", + '\t\tFoo int `xx:"foo"`', + '\t\tbar int `xx:"bar"`', + "}", + "" +} + +describe( + "should get nodes ", + function() + vim.cmd([[silent exe 'e tags.go']]) + vim.fn.append(0, input) + local bufn = vim.fn.bufnr("") + status = require("plenary.reload").reload_module("go.nvim") + status = require("plenary.reload").reload_module("nvim-treesitter/nvim-treesitter") + vim.g.go_nvim_verbose = true + local cur_dir = vim.fn.expand("%:p:h") + local nodes = require("go.ts.nodes") + it( + "get all nodes should get struct x", + function() + vim.fn.setpos(".", {bufn, 4, 1, 0}) + local query = require("go.ts.go").query_struct_block + local ns = nodes.get_all_nodes(query, "go", default, bufn) + eq("x", ns[1].name) + end + ) + it( + "it should get struct y", + function() + vim.fn.setpos(".", {bufn, 8, 1, 0}) + local query = require("go.ts.go").query_struct_block .. require("go.ts.go").query_em_struct_block + -- local query = require('go.ts.go').query_em_struct + local ns = nodes.get_all_nodes(query, "go", default, bufn) + eq("y", ns[2].name) + end + ) + it( + "node at cursor should get struct x", + function() + vim.fn.setpos(".", {bufn, 4, 1, 0}) + local query = require("go.ts.go").query_struct_block + local ns = nodes.nodes_at_cursor(query, default, bufn) + eq("x", ns[1].name) + end + ) + it( + "it should get struct y", + function() + vim.fn.setpos(".", {bufn, 8, 1, 0}) + local query = require("go.ts.go").query_struct_block .. require("go.ts.go").query_em_struct_block + -- local query = require('go.ts.go').query_em_struct + local ns = nodes.nodes_at_cursor(query, default, bufn) + eq("y", ns[#ns].name) + end + ) + it( + "struct at pos should get struct y", + function() + vim.fn.setpos(".", {bufn, 8, 4, 0}) + local ns = require("go.ts.go").get_struct_node_at_pos(8, 1) + print(vim.inspect(ns)) + eq("y", ns.name) + end + ) + it( + "should get function name", + function() + local name = vim.fn.tempname() .. ".go" + print("tmp:" .. name) + -- + local path = cur_dir .. "/lua/tests/fixtures/ts/playlist.go" -- %:p:h ? %:p + print("test:" .. path) + local lines = vim.fn.readfile(path) + vim.fn.writefile(lines, name) + local cmd = " silent exe 'e " .. name .. "'" + vim.cmd(cmd) + vim.fn.setpos(".", {bufn, 21, 5, 0}) + local ns = require("go.ts.go").get_func_method_node_at_pos(21, 5) + print(vim.inspect(ns)) + eq("createPlaylist", ns.name) + end + ) + it( + "should get method (with par list) name", + function() + local path = cur_dir .. "/lua/tests/fixtures/ts/playlist.go" -- %:p:h ? %:p + print("test:" .. path) + local cmd = " silent exe 'e " .. path .. "'" + vim.cmd(cmd) + vim.fn.setpos(".", {bufn, 33, 21, 0}) + local ns = require("go.ts.go").get_func_method_node_at_pos(33, 21) + print(vim.inspect(ns)) + eq("addSong", ns.name) + end + ) + it( + "should get method (no par) name", + function() + local path = cur_dir .. "/lua/tests/fixtures/ts/playlist.go" -- %:p:h ? %:p + print("test:" .. path) + local cmd = " silent exe 'e " .. path .. "'" + vim.cmd(cmd) + vim.fn.setpos(".", {bufn, 48, 3, 0}) + local ns = require("go.ts.go").get_func_method_node_at_pos(48, 3) + print(vim.inspect(ns)) + eq("showAllSongs", ns.name) + end + ) + it( + "should get interface name", + function() + local name = vim.fn.tempname() .. ".go" + print("tmp:" .. name) + -- + local path = cur_dir .. "/lua/tests/fixtures/ts/interfaces.go" -- %:p:h ? %:p + print("test:" .. path) + local lines = vim.fn.readfile(path) + vim.fn.writefile(lines, name) + local cmd = " silent exe 'e " .. name .. "'" + vim.cmd(cmd) + vim.fn.setpos(".", {bufn, 11, 6, 0}) + local ns = require("go.ts.go").get_interface_node_at_pos(11, 6) + print(vim.inspect(ns)) + eq("Geometry", ns.name) + end + ) + it( + "should get interface method name", + function() + local name = vim.fn.tempname() .. ".go" + print("tmp:" .. name) + -- + local path = cur_dir .. "/lua/tests/fixtures/ts/interfaces.go" -- %:p:h ? %:p + print("test:" .. path) + local lines = vim.fn.readfile(path) + vim.fn.writefile(lines, name) + local cmd = " silent exe 'e " .. name .. "'" + vim.cmd(cmd) + vim.fn.setpos(".", {bufn, 11, 5, 0}) + local ns = require("go.ts.go").get_interface_method_node_at_pos(11, 5) + print(vim.inspect(ns)) + eq("Area", ns.name) + end + ) + it( + "should get package name", + function() + local name = vim.fn.tempname() .. ".go" + print("tmp:" .. name) + -- + local path = cur_dir .. "/lua/tests/fixtures/ts/interfaces.go" -- %:p:h ? %:p + print("test:" .. path) + local lines = vim.fn.readfile(path) + vim.fn.writefile(lines, name) + local cmd = " silent exe 'e " .. name .. "'" + vim.cmd(cmd) + vim.fn.setpos(".", {bufn, 3, 5, 0}) + local ns = require("go.ts.go").get_package_node_at_pos(3, 5) + print(vim.inspect(ns)) + eq("main", ns.name) + end + ) + it( + "should get package name", + function() + local name = vim.fn.tempname() .. ".go" + print("tmp:" .. name) + -- + local path = cur_dir .. "/lua/tests/fixtures/ts/interfaces.go" -- %:p:h ? %:p + print("test:" .. path) + local lines = vim.fn.readfile(path) + vim.fn.writefile(lines, name) + local cmd = " silent exe 'e " .. name .. "'" + vim.cmd(cmd) + vim.fn.setpos(".", {bufn, 3, 1, 0}) + local ns = require("go.ts.go").get_package_node_at_pos(3, 1) + print(vim.inspect(ns)) + eq("main", ns.name) + end + ) + end +) diff --git a/lua/tests/minimal.vim b/lua/tests/minimal.vim new file mode 100644 index 0000000..a7d8b65 --- /dev/null +++ b/lua/tests/minimal.vim @@ -0,0 +1,6 @@ +set rtp +=. +set rtp +=~/.vim/autoload/plenary.nvim/ +runtime! plugin/plenary.vim + +lua require("plenary/busted") +lua require("go.nvim")