From 514271848eafb8d9c471d00c1e876ceb9aaba8fe Mon Sep 17 00:00:00 2001 From: Daniel Kolomeets Date: Fri, 2 Feb 2024 01:08:12 +0300 Subject: [PATCH] refactor(dap-go) choose go testflags dynamically --- lua/dap-go-ts.lua | 285 +++++++++++++++++++++++++++------------------- lua/dap-go.lua | 274 ++++++++++++++++++++++---------------------- 2 files changed, 311 insertions(+), 248 deletions(-) diff --git a/lua/dap-go-ts.lua b/lua/dap-go-ts.lua index 1af89ca..fd0a89e 100644 --- a/lua/dap-go-ts.lua +++ b/lua/dap-go-ts.lua @@ -22,147 +22,204 @@ local subtests_query = [[ ]] local function format_subtest(testcase, test_tree) - local parent - if testcase.parent then - for _, curr in pairs(test_tree) do - if curr.name == testcase.parent then - parent = curr - break - end + local parent + if testcase.parent then + for _, curr in pairs(test_tree) do + if curr.name == testcase.parent then + parent = curr + break + end + end + return string.format("%s/%s", format_subtest(parent, test_tree), testcase.name) + else + return testcase.name end - return string.format("%s/%s", format_subtest(parent, test_tree), testcase.name) - else - return testcase.name - end end local function get_closest_above_cursor(test_tree) - local result - for _, curr in pairs(test_tree) do - if not result then - result = curr - else - local node_row1, _, _, _ = curr.node:range() - local result_row1, _, _, _ = result.node:range() - if node_row1 > result_row1 then - result = curr - end + local result + for _, curr in pairs(test_tree) do + if not result then + result = curr + else + local node_row1, _, _, _ = curr.node:range() + local result_row1, _, _, _ = result.node:range() + if node_row1 > result_row1 then + result = curr + end + end + end + if result then + return format_subtest(result, test_tree) end - end - if result then - return format_subtest(result, test_tree) - end - return nil + return nil end local function is_parent(dest, source) - if not (dest and source) then - return false - end - if dest == source then - return false - end - - local current = source - while current ~= nil do - if current == dest then - return true + if not (dest and source) then + return false end + if dest == source then + return false + end + + local current = source + while current ~= nil do + if current == dest then + return true + end - current = current:parent() - end + current = current:parent() + end - return false + return false end local function get_closest_test() - local stop_row = vim.api.nvim_win_get_cursor(0)[1] - local ft = vim.api.nvim_buf_get_option(0, "filetype") - assert(ft == "go", "can only find test in go files, not " .. ft) - local parser = vim.treesitter.get_parser(0) - local root = (parser:parse()[1]):root() - - local test_tree = {} - - local test_query = vim.treesitter.query.parse(ft, tests_query) - assert(test_query, "could not parse test query") - for _, match, _ in test_query:iter_matches(root, 0, 0, stop_row) do - local test_match = {} - for id, node in pairs(match) do - local capture = test_query.captures[id] - if capture == "testname" then - local name = vim.treesitter.get_node_text(node, 0) - test_match.name = name - end - if capture == "parent" then - test_match.node = node - end + local stop_row = vim.api.nvim_win_get_cursor(0)[1] + local ft = vim.api.nvim_buf_get_option(0, "filetype") + assert(ft == "go", "can only find test in go files, not " .. ft) + local parser = vim.treesitter.get_parser(0) + local root = (parser:parse()[1]):root() + + local test_tree = {} + + local test_query = vim.treesitter.query.parse(ft, tests_query) + assert(test_query, "could not parse test query") + for _, match, _ in test_query:iter_matches(root, 0, 0, stop_row) do + local test_match = {} + for id, node in pairs(match) do + local capture = test_query.captures[id] + if capture == "testname" then + local name = vim.treesitter.get_node_text(node, 0) + test_match.name = name + end + if capture == "parent" then + test_match.node = node + end + end + table.insert(test_tree, test_match) end - table.insert(test_tree, test_match) - end - - local subtest_query = vim.treesitter.query.parse(ft, subtests_query) - assert(subtest_query, "could not parse test query") - for _, match, _ in subtest_query:iter_matches(root, 0, 0, stop_row) do - local test_match = {} - for id, node in pairs(match) do - local capture = subtest_query.captures[id] - if capture == "testname" then - local name = vim.treesitter.get_node_text(node, 0) - test_match.name = string.gsub(string.gsub(name, " ", "_"), '"', "") - end - if capture == "parent" then - test_match.node = node - end + + local subtest_query = vim.treesitter.query.parse(ft, subtests_query) + assert(subtest_query, "could not parse test query") + for _, match, _ in subtest_query:iter_matches(root, 0, 0, stop_row) do + local test_match = {} + for id, node in pairs(match) do + local capture = subtest_query.captures[id] + if capture == "testname" then + local name = vim.treesitter.get_node_text(node, 0) + test_match.name = string.gsub(string.gsub(name, " ", "_"), '"', "") + end + if capture == "parent" then + test_match.node = node + end + end + table.insert(test_tree, test_match) end - table.insert(test_tree, test_match) - end - - table.sort(test_tree, function(a, b) - return is_parent(a.node, b.node) - end) - - for _, parent in ipairs(test_tree) do - for _, child in ipairs(test_tree) do - if is_parent(parent.node, child.node) then - child.parent = parent.name - end + + table.sort(test_tree, function(a, b) + return is_parent(a.node, b.node) + end) + + for _, parent in ipairs(test_tree) do + for _, child in ipairs(test_tree) do + if is_parent(parent.node, child.node) then + child.parent = parent.name + end + end end - end - return get_closest_above_cursor(test_tree) + return get_closest_above_cursor(test_tree) end local function get_package_name() - local test_dir = vim.fn.fnamemodify(vim.fn.expand("%:.:h"), ":r") - return "./" .. test_dir + local test_dir = vim.fn.fnamemodify(vim.fn.expand("%:.:h"), ":r") + return "./" .. test_dir +end + +local function get_testfunc_name(str) + local pattern = "%s(Test%w+)%s*%(" + local substring = string.match(str, pattern) + + return substring +end + +local function get_testsuite_name(str) + local pattern = "%(%w+%s*%*?(%w+)%).-" + local prefix = "Test" + local substring = string.match(str, pattern) + + return prefix .. substring +end + +local function get_testcase() + local cursor_line = vim.api.nvim_get_current_line() + + local case_name = get_testfunc_name(cursor_line) + local case_suite = get_testsuite_name(cursor_line) + + return { + name = case_name, + suite = case_suite, + } +end + +local function get_params(testname) + -- Running tool: + -- /usr/bin/go test -timeout 30s -run ^TestSuiteName$ + -- -testify.m ^(TestFuncName)$ ./path/to/package.go + local name, test_args + + if testname then + name = testname + test_args = { "-test.run", "^" .. name .. "$" } + else + name = get_testcase().name + local suite = get_testcase().suite + test_args = { + "-test.timeout 12", "30s", + "-test.run", "^" .. suite .. "$", + "-testify.m", "^(" .. name .. ")$", + } + end + + return { + name = name, + args = test_args, + } end M.closest_test = function() - local package_name = get_package_name() - local test_case = get_closest_test() - local test_scope - if test_case then - test_scope = "testcase" - else - test_scope = "package" - end - return { - package = package_name, - name = test_case, - scope = test_scope, - } + local test_scope + + local package_name = get_package_name() + local test_case = get_params(get_closest_test()) + + if test_case.name then + test_scope = "testcase" + else + test_scope = "package" + end + + return { + package = package_name, + args = test_case.args, + name = test_case.name, + scope = test_scope, + } end + M.get_root_dir = function() - local id, client = next(vim.lsp.buf_get_clients()) - if id == nil then - error({ error_msg = "lsp client not attached" }) - end - if not client.config.root_dir then - error({ error_msg = "lsp root_dir not defined" }) - end - return client.config.root_dir + local id, client = next(vim.lsp.buf_get_clients()) + if id == nil then + error({ error_msg = "lsp client not attached" }) + end + if not client.config.root_dir then + error({ error_msg = "lsp root_dir not defined" }) + end + return client.config.root_dir end return M diff --git a/lua/dap-go.lua b/lua/dap-go.lua index b2419fe..d08e482 100644 --- a/lua/dap-go.lua +++ b/lua/dap-go.lua @@ -1,179 +1,185 @@ local ts = require("dap-go-ts") local M = { - last_testname = "", - last_testpath = "", - test_buildflags = "", + last_testname = "", + last_testpath = "", + last_args = {}, + test_buildflags = "", } local default_config = { - delve = { - path = "dlv", - initialize_timeout_sec = 20, - port = "${port}", - args = {}, - build_flags = "", - }, + delve = { + path = "dlv", + initialize_timeout_sec = 20, + port = "${port}", + args = {}, + build_flags = "", + }, } local function load_module(module_name) - local ok, module = pcall(require, module_name) - assert(ok, string.format("dap-go dependency error: %s not installed", module_name)) - return module + local ok, module = pcall(require, module_name) + assert(ok, string.format("dap-go dependency error: %s not installed", module_name)) + return module end local function get_arguments() - return coroutine.create(function(dap_run_co) - local args = {} - vim.ui.input({ prompt = "Args: " }, function(input) - args = vim.split(input or "", " ") - coroutine.resume(dap_run_co, args) + return coroutine.create(function(dap_run_co) + local args = {} + vim.ui.input({ prompt = "Args: " }, function(input) + args = vim.split(input or "", " ") + coroutine.resume(dap_run_co, args) + end) end) - end) end local function filtered_pick_process() - local opts = {} - vim.ui.input( - { prompt = "Search by process name (lua pattern), or hit enter to select from the process list: " }, - function(input) - opts["filter"] = input or "" - end - ) - return require("dap.utils").pick_process(opts) + local opts = {} + vim.ui.input( + { prompt = "Search by process name (lua pattern), or hit enter to select from the process list: " }, + function(input) + opts["filter"] = input or "" + end + ) + return require("dap.utils").pick_process(opts) end local function setup_delve_adapter(dap, config) - local args = { "dap", "-l", "127.0.0.1:" .. config.delve.port } - vim.list_extend(args, config.delve.args) - - dap.adapters.go = { - type = "server", - port = config.delve.port, - executable = { - command = config.delve.path, - args = args, - }, - options = { - initialize_timeout_sec = config.delve.initialize_timeout_sec, - }, - } + local args = { "dap", "-l", "127.0.0.1:" .. config.delve.port } + vim.list_extend(args, config.delve.args) + + dap.adapters.go = { + type = "server", + port = config.delve.port, + executable = { + command = config.delve.path, + args = args, + }, + options = { + initialize_timeout_sec = config.delve.initialize_timeout_sec, + }, + } end local function setup_go_configuration(dap, configs) - dap.configurations.go = { - { - type = "go", - name = "Debug", - request = "launch", - program = "${file}", - buildFlags = configs.delve.build_flags, - }, - { - type = "go", - name = "Debug (Arguments)", - request = "launch", - program = "${file}", - args = get_arguments, - buildFlags = configs.delve.build_flags, - }, - { - type = "go", - name = "Debug Package", - request = "launch", - program = "${fileDirname}", - buildFlags = configs.delve.build_flags, - }, - { - type = "go", - name = "Attach", - mode = "local", - request = "attach", - processId = filtered_pick_process, - buildFlags = configs.delve.build_flags, - }, - { - type = "go", - name = "Debug test", - request = "launch", - mode = "test", - program = "${file}", - buildFlags = configs.delve.build_flags, - }, - { - type = "go", - name = "Debug test (go.mod)", - request = "launch", - mode = "test", - program = "./${relativeFileDirname}", - buildFlags = configs.delve.build_flags, - }, - } - - if configs == nil or configs.dap_configurations == nil then - return - end + dap.configurations.go = { + { + type = "go", + name = "Debug", + request = "launch", + program = "${file}", + buildFlags = configs.delve.build_flags, + }, + { + type = "go", + name = "Debug (Arguments)", + request = "launch", + program = "${file}", + args = get_arguments, + buildFlags = configs.delve.build_flags, + }, + { + type = "go", + name = "Debug Package", + request = "launch", + program = "${fileDirname}", + buildFlags = configs.delve.build_flags, + }, + { + type = "go", + name = "Attach", + mode = "local", + request = "attach", + processId = filtered_pick_process, + buildFlags = configs.delve.build_flags, + }, + { + type = "go", + name = "Debug test", + request = "launch", + mode = "test", + program = "${file}", + buildFlags = configs.delve.build_flags, + }, + { + type = "go", + name = "Debug test (go.mod)", + request = "launch", + mode = "test", + program = "./${relativeFileDirname}", + buildFlags = configs.delve.build_flags, + }, + } + + if configs == nil or configs.dap_configurations == nil then + return + end - for _, config in ipairs(configs.dap_configurations) do - if config.type == "go" then - table.insert(dap.configurations.go, config) + for _, config in ipairs(configs.dap_configurations) do + if config.type == "go" then + table.insert(dap.configurations.go, config) + end end - end end function M.setup(opts) - local config = vim.tbl_deep_extend("force", default_config, opts or {}) - M.test_buildflags = config.delve.build_flags - local dap = load_module("dap") - setup_delve_adapter(dap, config) - setup_go_configuration(dap, config) + local config = vim.tbl_deep_extend("force", default_config, opts or {}) + M.test_buildflags = config.delve.build_flags + local dap = load_module("dap") + setup_delve_adapter(dap, config) + setup_go_configuration(dap, config) end -local function debug_test(testname, testpath, build_flags) - local dap = load_module("dap") - dap.run({ - type = "go", - name = testname, - request = "launch", - mode = "test", - program = testpath, - args = { "-test.run", "^" .. testname .. "$" }, - buildFlags = build_flags, - }) +local function debug_test(testname, testpath, args, build_flags) + local dap = load_module("dap") + + dap.run({ + type = "go", + name = testname, + request = "launch", + mode = "test", + program = testpath, + args = args, + buildFlags = build_flags, + }) end function M.debug_test() - local test = ts.closest_test() + local test = ts.closest_test() - if test.name == "" then - vim.notify("no test found") - return false - end + if test.name == "" then + vim.notify("no test found") + return false + end + + M.last_testname = test.name + M.last_testpath = test.package + M.last_args = test.args - M.last_testname = test.name - M.last_testpath = test.package - local msg = string.format("starting debug session '%s : %s'...", test.package, test.name) - vim.notify(msg) - debug_test(test.name, test.package, M.test_buildflags) + local msg = string.format("starting debug session '%s : %s'...", test.package, test.name) + vim.notify(msg) + vim.notify(test.args[3] .. test.args[4], vim.log.levels.DEBUG, nil) + debug_test(test.name, test.package, test.args, M.test_buildflags) - return true + return true end function M.debug_last_test() - local testname = M.last_testname - local testpath = M.last_testpath + local testname = M.last_testname + local testpath = M.last_testpath + local testargs = M.last_args - if testname == "" then - vim.notify("no last run test found") - return false - end + if testname == "" then + vim.notify("no last run test found") + return false + end - local msg = string.format("starting debug session '%s : %s'...", testpath, testname) - vim.notify(msg) - debug_test(testname, testpath, M.test_buildflags) + local msg = string.format("starting debug session '%s : %s'...", testpath, testname) + vim.notify(msg) + debug_test(testname, testpath, testargs, M.test_buildflags) - return true + return true end return M