feat(lsp,debugger,formatting): C/C++ setup with clang
This commit is contained in:
238
lua/dap-lldb/init.lua
Normal file
238
lua/dap-lldb/init.lua
Normal file
@@ -0,0 +1,238 @@
|
||||
---@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
|
||||
Reference in New Issue
Block a user