239 lines
5.8 KiB
Lua
239 lines
5.8 KiB
Lua
---@mod dap-lldb LLDB extension for nvim-dap
|
|
|
|
---@brief [[
|
|
---An extension for nvim-dap to provide C, C++, and Rust debugging support.
|
|
---@brief ]]
|
|
|
|
local M = {}
|
|
|
|
local ts_query = [[
|
|
(mod_item
|
|
name: (identifier) @module
|
|
body: (declaration_list
|
|
(attribute_item (attribute (identifier) @attribute (#eq? @attribute "test")))
|
|
(function_item
|
|
name: (identifier) @function)))
|
|
]]
|
|
|
|
local function require_dap()
|
|
local ok, dap = pcall(require, "dap")
|
|
assert(ok, "nvim-dap is required to use dap-lldb")
|
|
return dap
|
|
end
|
|
|
|
local function compiler_error(input)
|
|
local _, json = pcall(vim.fn.json_decode, input)
|
|
|
|
if type(json) == "table" and json.reason == "compiler-message" then
|
|
return json.message.rendered
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local function compiler_target(input)
|
|
local _, json = pcall(vim.fn.json_decode, input)
|
|
|
|
if
|
|
type(json) == "table"
|
|
and json.reason == "compiler-artifact"
|
|
and json.executable ~= nil
|
|
and (vim.tbl_contains(json.target.kind, "bin") or json.profile.test)
|
|
then
|
|
return json.executable
|
|
end
|
|
|
|
return nil
|
|
end
|
|
|
|
local function read_target()
|
|
local cwd = vim.fs.joinpath(vim.fn.getcwd(), "")
|
|
return vim.fn.input("Path to executable: ", cwd, "file")
|
|
end
|
|
|
|
local function list_targets(selection)
|
|
local arg = string.format("--%s", selection or "bins")
|
|
local cmd = { "cargo", "build", arg, "--quiet", "--message-format", "json" }
|
|
local out = vim.fn.systemlist(cmd)
|
|
|
|
if vim.v.shell_error ~= 0 then
|
|
local errors = vim.tbl_map(compiler_error, out)
|
|
vim.notify(table.concat(errors, "\n"), vim.log.levels.ERROR)
|
|
return nil
|
|
end
|
|
|
|
local function filter(e)
|
|
return e ~= nil
|
|
end
|
|
|
|
return vim.tbl_filter(filter, vim.tbl_map(compiler_target, out))
|
|
end
|
|
|
|
local function select_target(selection)
|
|
local targets = list_targets(selection)
|
|
|
|
if targets == nil then
|
|
return nil
|
|
end
|
|
|
|
if #targets == 0 then
|
|
return read_target()
|
|
end
|
|
|
|
if #targets == 1 then
|
|
return targets[1]
|
|
end
|
|
|
|
local options = { "Select a target:" }
|
|
|
|
for index, target in ipairs(targets) do
|
|
local name = vim.fs.basename(target)
|
|
local option = string.format("%d. %s", index, name)
|
|
table.insert(options, option)
|
|
end
|
|
|
|
local choice = vim.fn.inputlist(options)
|
|
|
|
return targets[choice]
|
|
end
|
|
|
|
local function select_test()
|
|
local filetype = vim.bo.filetype
|
|
|
|
if filetype ~= "rust" or vim.treesitter.language.get_lang(filetype) == nil then
|
|
return nil
|
|
end
|
|
|
|
local bufnr = vim.api.nvim_get_current_buf()
|
|
local query = vim.treesitter.query.parse(filetype, ts_query)
|
|
local parser = vim.treesitter.get_parser(bufnr, filetype)
|
|
local tree = parser:parse()[1]
|
|
local root = tree:root()
|
|
local stop = vim.api.nvim_win_get_cursor(0)[1]
|
|
local mod = nil
|
|
local fun = nil
|
|
|
|
for id, node in query:iter_captures(root, bufnr, 0, stop) do
|
|
local capture = query.captures[id]
|
|
|
|
if capture == "module" then
|
|
mod = vim.treesitter.get_node_text(node, 0)
|
|
elseif capture == "function" then
|
|
fun = vim.treesitter.get_node_text(node, 0)
|
|
end
|
|
end
|
|
|
|
if not mod or not fun then
|
|
return nil
|
|
end
|
|
|
|
return string.format("%s::%s", mod, fun)
|
|
end
|
|
|
|
local function read_args()
|
|
local args = vim.fn.input("Enter args: ")
|
|
return vim.split(args, " ", { trimempty = true })
|
|
end
|
|
|
|
local function default_configurations(dap)
|
|
local cfg = {
|
|
name = "Debug",
|
|
type = "lldb",
|
|
request = "launch",
|
|
cwd = "${workspaceFolder}",
|
|
program = read_target,
|
|
stopOnEntry = false,
|
|
}
|
|
|
|
dap.configurations.c = {
|
|
cfg,
|
|
vim.tbl_extend("force", cfg, { name = "Debug (+args)", args = read_args }),
|
|
vim.tbl_extend("force", cfg, { name = "Attach debugger", request = "attach" }),
|
|
}
|
|
|
|
dap.configurations.cpp = vim.tbl_extend("keep", {}, dap.configurations.c)
|
|
|
|
dap.configurations.rust = {
|
|
vim.tbl_extend("force", cfg, { program = select_target }),
|
|
vim.tbl_extend("force", cfg, { name = "Debug (+args)", program = select_target, args = read_args }),
|
|
vim.tbl_extend("force", cfg, {
|
|
name = "Debug tests",
|
|
program = function()
|
|
return select_target("tests")
|
|
end,
|
|
args = { "--test-threads=1" },
|
|
}),
|
|
vim.tbl_extend("force", cfg, {
|
|
name = "Debug tests (+args)",
|
|
program = function()
|
|
return select_target("tests")
|
|
end,
|
|
args = function()
|
|
return vim.list_extend(read_args(), { "--test-threads=1" })
|
|
end,
|
|
}),
|
|
vim.tbl_extend("force", cfg, {
|
|
name = "Debug test (cursor)",
|
|
program = function()
|
|
return select_target("tests")
|
|
end,
|
|
args = function()
|
|
local test = select_test()
|
|
local args = test and { "--exact", test } or {}
|
|
return vim.list_extend(args, { "--test-threads=1" })
|
|
end,
|
|
}),
|
|
vim.tbl_extend("force", cfg, { name = "Attach debugger", request = "attach", program = select_target }),
|
|
}
|
|
end
|
|
|
|
local function custom_configurations(dap, opts)
|
|
if type(opts.configurations) == "table" then
|
|
for lang, cfg in pairs(opts.configurations) do
|
|
local config = dap.configurations[lang] or {}
|
|
dap.configurations[lang] = vim.list_extend(config, cfg)
|
|
end
|
|
end
|
|
end
|
|
|
|
---@class SetupOpts
|
|
---@field codelldb_path string|nil Path to CodeLLDB extension
|
|
---@field configurations table|nil Per programming language configuration
|
|
---@see https://github.com/vadimcn/codelldb/blob/master/MANUAL.md
|
|
|
|
---Register LLDB debug adapter
|
|
---@param opts SetupOpts|nil See |dap-lldb.SetupOpts|
|
|
function M.setup(opts)
|
|
opts = type(opts) == "table" and opts or {}
|
|
|
|
local dap = require_dap()
|
|
local codelldb = opts.codelldb_path or "codelldb"
|
|
|
|
dap.adapters.lldb = {
|
|
type = "server",
|
|
port = "${port}",
|
|
executable = {
|
|
command = codelldb,
|
|
args = { "--port", "${port}" },
|
|
detached = vim.loop.os_uname().sysname ~= "Windows",
|
|
},
|
|
}
|
|
|
|
default_configurations(dap)
|
|
custom_configurations(dap, opts)
|
|
end
|
|
|
|
---Debug test function above the cursor
|
|
function M.debug_test()
|
|
if vim.bo.filetype ~= "rust" then
|
|
vim.notify("This feature is available only for Rust", vim.log.levels.ERROR)
|
|
return nil
|
|
end
|
|
|
|
local dap = require_dap()
|
|
local cfg = dap.configurations.rust[5]
|
|
dap.run(cfg)
|
|
end
|
|
|
|
return M
|