local ts = require("dap-go.ts") local M = { last_testname = "", last_testpath = "", test_buildflags = "", test_verbose = false, } local default_config = { delve = { path = "dlv", initialize_timeout_sec = 20, port = "${port}", args = {}, build_flags = "", -- Automatically handle the issue on delve Windows versions < 1.24.0 -- where delve needs to be run in attched mode or it will fail (actually crashes). detached = vim.fn.has("win32") == 0, output_mode = "remote", }, tests = { verbose = false, }, } local internal_global_config = {} 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 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) end) end) end local function get_build_flags(config) return coroutine.create(function(dap_run_co) local build_flags = config.build_flags vim.ui.input({ prompt = "Build Flags: " }, function(input) build_flags = vim.split(input or "", " ") coroutine.resume(dap_run_co, build_flags) 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) 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) local delve_config = { type = "server", port = config.delve.port, executable = { command = config.delve.path, args = args, detached = config.delve.detached, cwd = config.delve.cwd, }, options = { initialize_timeout_sec = config.delve.initialize_timeout_sec, }, } dap.adapters.go = function(callback, client_config) if client_config.port == nil then callback(delve_config) return end local host = client_config.host if host == nil then host = "127.0.0.1" end local listener_addr = host .. ":" .. client_config.port delve_config.port = client_config.port delve_config.executable.args = { "dap", "-l", listener_addr } callback(delve_config) end end local function setup_go_configuration(dap, configs) local common_debug_configs = { { type = "go", name = "Debug", request = "launch", program = "${file}", buildFlags = configs.delve.build_flags, outputMode = configs.delve.output_mode, }, { type = "go", name = "Debug (Arguments)", request = "launch", program = "${file}", args = get_arguments, buildFlags = configs.delve.build_flags, outputMode = configs.delve.output_mode, }, { type = "go", name = "Debug (Arguments & Build Flags)", request = "launch", program = "${file}", args = get_arguments, buildFlags = get_build_flags, outputMode = configs.delve.output_mode, }, { type = "go", name = "Debug Package", request = "launch", program = "${fileDirname}", buildFlags = configs.delve.build_flags, outputMode = configs.delve.output_mode, }, { 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, outputMode = configs.delve.output_mode, }, { type = "go", name = "Debug test (go.mod)", request = "launch", mode = "test", program = "./${relativeFileDirname}", buildFlags = configs.delve.build_flags, outputMode = configs.delve.output_mode, }, } if dap.configurations.go == nil then dap.configurations.go = {} end for _, config in ipairs(common_debug_configs) do table.insert(dap.configurations.go, config) end 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) end end end function M.setup(opts) internal_global_config = vim.tbl_deep_extend("force", default_config, opts or {}) M.test_buildflags = internal_global_config.delve.build_flags M.test_verbose = internal_global_config.tests.verbose local dap = load_module("dap") setup_delve_adapter(dap, internal_global_config) setup_go_configuration(dap, internal_global_config) end local function debug_test(testname, testpath, build_flags, extra_args, custom_config) local dap = load_module("dap") local config = { type = "go", name = testname, request = "launch", mode = "test", program = testpath, args = { "-test.run", "^" .. testname .. "$" }, buildFlags = build_flags, outputMode = "remote", } config = vim.tbl_deep_extend("force", config, custom_config or {}) if not vim.tbl_isempty(extra_args) then table.move(extra_args, 1, #extra_args, #config.args + 1, config.args) end dap.run(config) end function M.debug_test(custom_config) local test = ts.closest_test() if test.name == "" or test.name == nil then vim.notify("no test found") return false end 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) local extra_args = {} if M.test_verbose then extra_args = { "-test.v" } end debug_test(test.name, test.package, M.test_buildflags, extra_args, custom_config) return true end function M.debug_last_test() local testname = M.last_testname local testpath = M.last_testpath 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) local extra_args = {} if M.test_verbose then extra_args = { "-test.v" } end debug_test(testname, testpath, M.test_buildflags, extra_args) return true end function M.get_build_flags() return get_build_flags(internal_global_config) end function M.get_arguments() return get_arguments() end return M