aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
authorToby Vincent <tobyv@tobyvin.dev>2023-10-27 16:06:29 -0500
committerToby Vincent <tobyv@tobyvin.dev>2023-10-27 16:06:58 -0500
commitbf2deb4e7e4d12767ea56118017e6e1cc384ae47 (patch)
tree060d5e83dacb7efcb4ff3f53be2ddc09a50e3d5d
parent729448740e31bbdb2e6464a024927fb85b01b4c4 (diff)
fix(nvim): improve dap (and possibly made it worse)
-rw-r--r--nvim/.config/nvim/lua/plugins/dap.lua61
-rw-r--r--nvim/.config/nvim/lua/plugins/telescope-dap.lua25
-rw-r--r--nvim/.config/nvim/lua/tobyvin/dap/adapters.lua33
-rw-r--r--nvim/.config/nvim/lua/tobyvin/dap/configs.lua76
-rw-r--r--nvim/.config/nvim/lua/tobyvin/utils/dap.lua187
5 files changed, 300 insertions, 82 deletions
diff --git a/nvim/.config/nvim/lua/plugins/dap.lua b/nvim/.config/nvim/lua/plugins/dap.lua
index d0d1135..a67fbc3 100644
--- a/nvim/.config/nvim/lua/plugins/dap.lua
+++ b/nvim/.config/nvim/lua/plugins/dap.lua
@@ -27,6 +27,60 @@ local M = {
},
}
+function M:init()
+ local function parse(args)
+ local parts = vim.split(vim.trim(args), "%s+")
+ if parts[1]:match("Dap") then
+ table.remove(parts, 1)
+ end
+ if args:sub(-1) == " " then
+ parts[#parts + 1] = ""
+ end
+ return table.remove(parts, 1) or "", parts
+ end
+
+ vim.api.nvim_create_user_command("Dap", function(cmd)
+ local prefix, args = parse(cmd.args)
+ local command = require("dap")[prefix]
+
+ if type(command) == "table" then
+ command = command[table.remove(args, 1)]
+ end
+
+ if type(command) == "function" then
+ command(unpack(args))
+ else
+ vim.notify("Invalid Dap command '" .. prefix .. "'", vim.log.levels.ERROR)
+ end
+ end, {
+ bar = true,
+ bang = false,
+ nargs = "?",
+ desc = "Dap",
+ complete = function(_, line)
+ local prefix, args = parse(line)
+ local cmds = require("dap")
+
+ if #args > 0 then
+ if type(require("dap")[prefix]) == "table" then
+ cmds = require("dap")[prefix]
+ if #cmds == 0 and pcall(require, "dap." .. prefix) then
+ cmds = require("dap." .. prefix)
+ end
+ prefix = args[#args]
+ else
+ return nil
+ end
+ end
+
+ ---@param key string
+ return vim.tbl_filter(function(key)
+ return key:find(prefix, 1, true) == 1 and (type(cmds[key]) == "function" or type(cmds[key]) == "table")
+ end, vim.tbl_keys(cmds))
+ end,
+ })
+end
+
function M:config()
require("dap").listeners.after.event_initialized["User"] = function()
vim.api.nvim_exec_autocmds("User", { pattern = "DapAttach" })
@@ -58,9 +112,9 @@ function M:config()
end
local configs = require("tobyvin.dap.configs")
- for name, config in pairs(configs) do
- if require("dap").configurations[name] == nil then
- require("dap").configurations[name] = config
+ for ft, config in pairs(configs) do
+ if require("dap").configurations[ft] == nil then
+ require("dap").configurations[ft] = config
end
end
@@ -77,6 +131,7 @@ function M:config()
vim.keymap.set("n", "<leader>dq", require("dap").terminate, { desc = "terminate" })
vim.keymap.set("n", "<leader>db", require("dap").toggle_breakpoint, { desc = "toggle breakpoint" })
vim.keymap.set("n", "<leader>dl", require("dap.ui.widgets").hover)
+ vim.keymap.set("n", "<leader>dd", require("dap").continue, { desc = "launch" })
vim.api.nvim_create_user_command("Break", function(opts)
require("dap").toggle_breakpoint(unpack(opts.fargs))
diff --git a/nvim/.config/nvim/lua/plugins/telescope-dap.lua b/nvim/.config/nvim/lua/plugins/telescope-dap.lua
deleted file mode 100644
index eff291c..0000000
--- a/nvim/.config/nvim/lua/plugins/telescope-dap.lua
+++ /dev/null
@@ -1,25 +0,0 @@
----@type LazyPluginSpec
-local M = {
- "nvim-telescope/telescope-dap.nvim",
- keys = {
- "<leader>dC",
- "<leader>dd",
- },
- dependencies = {
- "nvim-telescope/telescope.nvim",
- "mfussenegger/nvim-dap",
- },
-}
-
-function M:config()
- require("telescope").load_extension("dap")
- local ts_dap = require("telescope").extensions.dap
-
- vim.keymap.set("n", "<leader>dC", ts_dap.commands, { desc = "commands" })
- vim.keymap.set("n", "<leader>dd", ts_dap.configurations, { desc = "configurations" })
- vim.keymap.set("n", "<leader>dB", ts_dap.list_breakpoints, { desc = "breakpoints" })
- vim.keymap.set("n", "<leader>df", ts_dap.frames, { desc = "frames" })
- vim.keymap.set("n", "<leader>dv", ts_dap.variables, { desc = "variables" })
-end
-
-return M
diff --git a/nvim/.config/nvim/lua/tobyvin/dap/adapters.lua b/nvim/.config/nvim/lua/tobyvin/dap/adapters.lua
index 50ee41d..6c97b66 100644
--- a/nvim/.config/nvim/lua/tobyvin/dap/adapters.lua
+++ b/nvim/.config/nvim/lua/tobyvin/dap/adapters.lua
@@ -1,3 +1,5 @@
+local dap_utils = require("tobyvin.utils.dap")
+
local M = {
nlua = function(callback, config)
callback({ type = "server", host = config.host, port = config.port })
@@ -12,20 +14,25 @@ local M = {
command = "gdb",
args = { "-i", "dap" },
},
- codelldb = function(on_config, _)
- local codelldb_path = vim.fn.exepath("codelldb")
- local liblldb_path = vim.fn.resolve(codelldb_path):gsub("/codelldb$", "/extension/lldb/lib/liblldb.so")
-
- on_config({
- type = "server",
- port = "${port}",
- host = "127.0.0.1",
- executable = {
- command = codelldb_path,
- args = { "--liblldb", liblldb_path, "--port", "${port}" },
+ codelldb = {
+ type = "server",
+ port = "${port}",
+ host = "127.0.0.1",
+ executable = {
+ command = vim.fn.exepath("codelldb"),
+ args = {
+ "--liblldb",
+ (vim.fn.resolve(vim.fn.exepath("codelldb")):gsub("/codelldb$", "/extension/lldb/lib/liblldb.so")),
+ "--port",
+ "${port}",
},
- })
- end,
+ },
+ enrich_config = function(config, on_config)
+ if config["cargo"] ~= nil then
+ on_config(dap_utils.cargo_inspector(config))
+ end
+ end,
+ },
}
return M
diff --git a/nvim/.config/nvim/lua/tobyvin/dap/configs.lua b/nvim/.config/nvim/lua/tobyvin/dap/configs.lua
index e9b7c50..7f8b47d 100644
--- a/nvim/.config/nvim/lua/tobyvin/dap/configs.lua
+++ b/nvim/.config/nvim/lua/tobyvin/dap/configs.lua
@@ -1,22 +1,24 @@
local M = {
lua = {
- name = "Attach to running Neovim instance",
- type = "nlua",
- request = "attach",
- host = function()
- local host
- vim.ui.input({ prompt = "Host: ", default = "127.0.0.1" }, function(input)
- host = input
- end)
- return host
- end,
- port = function()
- local host
- vim.ui.input({ prompt = "Port: ", default = "7777" }, function(input)
- host = input
- end)
- return host
- end,
+ {
+ name = "Attach to running Neovim instance",
+ type = "nlua",
+ request = "attach",
+ host = function()
+ local host
+ vim.ui.input({ prompt = "Host: ", default = "127.0.0.1" }, function(input)
+ host = input
+ end)
+ return host
+ end,
+ port = function()
+ local host
+ vim.ui.input({ prompt = "Port: ", default = "7777" }, function(input)
+ host = input
+ end)
+ return host
+ end,
+ },
},
c = {
{
@@ -46,34 +48,26 @@ local M = {
},
rust = {
{
- name = "Launch",
+ name = "build",
type = "codelldb",
request = "launch",
- program = function()
- return vim.fn.input("Path to executable: ", vim.fn.getcwd() .. "/", "file")
- end,
+ cargo = {
+ args = { "build" },
+ },
cwd = "${workspaceFolder}",
stopOnEntry = false,
- runInTerminal = false,
- initCommands = function()
- -- Find out where to look for the pretty printer Python module
- local rustc_sysroot = vim.fn.trim(vim.fn.system("rustc --print sysroot"))
-
- local script_import = 'command script import "' .. rustc_sysroot .. '/lib/rustlib/etc/lldb_lookup.py"'
- local commands_file = rustc_sysroot .. "/lib/rustlib/etc/lldb_commands"
-
- local commands = {}
- local file = io.open(commands_file, "r")
- if file then
- for line in file:lines() do
- table.insert(commands, line)
- end
- file:close()
- end
- table.insert(commands, 1, script_import)
-
- return commands
- end,
+ terminal = "console",
+ },
+ {
+ name = "test --lib",
+ type = "codelldb",
+ request = "launch",
+ cargo = {
+ args = { "test", "--no-run", "--lib" },
+ },
+ cwd = "${workspaceFolder}",
+ stopOnEntry = false,
+ terminal = "console",
},
},
}
diff --git a/nvim/.config/nvim/lua/tobyvin/utils/dap.lua b/nvim/.config/nvim/lua/tobyvin/utils/dap.lua
new file mode 100644
index 0000000..85bbf83
--- /dev/null
+++ b/nvim/.config/nvim/lua/tobyvin/utils/dap.lua
@@ -0,0 +1,187 @@
+local M = {}
+
+function M.cargo_inspector(config)
+ local final_config = vim.deepcopy(config)
+
+ -- Create a buffer to receive compiler progress messages
+ local compiler_msg_buf = vim.api.nvim_create_buf(false, true)
+ vim.api.nvim_buf_set_option(compiler_msg_buf, "buftype", "nofile")
+
+ -- And a floating window in the corner to display those messages
+ local window_width = math.max(#final_config.name + 1, 50)
+ local window_height = 12
+ local compiler_msg_window = vim.api.nvim_open_win(compiler_msg_buf, false, {
+ relative = "editor",
+ width = window_width,
+ height = window_height,
+ col = vim.api.nvim_get_option("columns") - window_width - 1,
+ row = vim.api.nvim_get_option("lines") - window_height - 1,
+ border = "rounded",
+ style = "minimal",
+ })
+
+ -- Let the user know what's going on
+ ---@diagnostic disable-next-line: param-type-mismatch
+ vim.fn.appendbufline(compiler_msg_buf, "$", "Compiling: ")
+ ---@diagnostic disable-next-line: param-type-mismatch
+ vim.fn.appendbufline(compiler_msg_buf, "$", final_config.name)
+ ---@diagnostic disable-next-line: param-type-mismatch
+ vim.fn.appendbufline(compiler_msg_buf, "$", string.rep("=", window_width - 1))
+
+ -- Instruct cargo to emit compiler metadata as JSON
+ local message_format = "--message-format=json"
+ if final_config.cargo.args ~= nil then
+ table.insert(final_config.cargo.args, message_format)
+ else
+ final_config.cargo.args = { message_format }
+ end
+
+ -- Build final `cargo` command to be executed
+ local cargo_cmd = { "cargo" }
+ for _, value in pairs(final_config.cargo.args) do
+ table.insert(cargo_cmd, value)
+ end
+
+ -- Run `cargo`, retaining buffered `stdout` for later processing,
+ -- and emitting compiler messages to to a window
+ local compiler_metadata = {}
+ local cargo_job = vim.fn.jobstart(cargo_cmd, {
+ clear_env = false,
+ env = final_config.cargo.env,
+ cwd = final_config.cwd,
+
+ -- Cargo emits compiler metadata to `stdout`
+ stdout_buffered = true,
+ on_stdout = function(_, data)
+ compiler_metadata = data
+ end,
+
+ -- Cargo emits compiler messages to `stderr`
+ on_stderr = function(_, data)
+ local complete_line = ""
+
+ -- `data` might contain partial lines, glue data together until
+ -- the stream indicates the line is complete with an empty string
+ for _, partial_line in ipairs(data) do
+ if string.len(partial_line) ~= 0 then
+ complete_line = complete_line .. partial_line
+ end
+ end
+
+ if vim.api.nvim_buf_is_valid(compiler_msg_buf) then
+ ---@diagnostic disable-next-line: param-type-mismatch
+ vim.fn.appendbufline(compiler_msg_buf, "$", complete_line)
+ vim.api.nvim_win_set_cursor(compiler_msg_window, { vim.api.nvim_buf_line_count(compiler_msg_buf), 1 })
+ vim.cmd("redraw")
+ end
+ end,
+
+ on_exit = function(_, exit_code)
+ -- Cleanup the compile message window and buffer
+ if vim.api.nvim_win_is_valid(compiler_msg_window) then
+ vim.api.nvim_win_close(compiler_msg_window, true)
+ end
+
+ if vim.api.nvim_buf_is_valid(compiler_msg_buf) then
+ vim.api.nvim_buf_delete(compiler_msg_buf, { force = true })
+ end
+
+ -- If compiling succeeed, send the compile metadata off for processing
+ -- and add the resulting executable name to the `program` field of the final config
+ if exit_code == 0 then
+ local executable_name = M.parse_cargo_metadata(compiler_metadata)
+ if executable_name ~= nil then
+ final_config.program = executable_name
+ else
+ vim.notify(
+ "Cargo could not find an executable for debug configuration:\n\n\t" .. final_config.name,
+ vim.log.levels.ERROR
+ )
+ end
+ else
+ vim.notify(
+ "Cargo failed to compile debug configuration:\n\n\t" .. final_config.name,
+ vim.log.levels.ERROR
+ )
+ end
+ end,
+ })
+
+ -- Get the rust compiler's commit hash for the source map
+ local rust_hash = ""
+ local rust_hash_stdout = {}
+ local rust_hash_job = vim.fn.jobstart({ "rustc", "--version", "--verbose" }, {
+ clear_env = false,
+ stdout_buffered = true,
+ on_stdout = function(_, data)
+ rust_hash_stdout = data
+ end,
+ on_exit = function()
+ for _, line in pairs(rust_hash_stdout) do
+ local start, finish = string.find(line, "commit-hash: ", 1, true)
+
+ if start ~= nil then
+ rust_hash = string.sub(line, finish + 1)
+ end
+ end
+ end,
+ })
+
+ -- Get the location of the rust toolchain's source code for the source map
+ local rust_source_path = ""
+ local rust_source_job = vim.fn.jobstart({ "rustc", "--print", "sysroot" }, {
+ clear_env = false,
+ stdout_buffered = true,
+ on_stdout = function(_, data)
+ rust_source_path = data[1]
+ end,
+ })
+
+ -- Wait until compiling and parsing are done
+ -- This blocks the UI (except for the :redraw above) and I haven't figured
+ -- out how to avoid it, yet
+ -- Regardless, not much point in debugging if the binary isn't ready yet
+ vim.fn.jobwait({ cargo_job, rust_hash_job, rust_source_job })
+
+ -- Enable visualization of built in Rust datatypes
+ final_config.sourceLanguages = { "rust" }
+
+ -- Build sourcemap to rust's source code so we can step into stdlib
+ rust_hash = "/rustc/" .. rust_hash .. "/"
+ rust_source_path = rust_source_path .. "/lib/rustlib/src/rust/"
+ if final_config.sourceMap == nil then
+ final_config["sourceMap"] = {}
+ end
+ final_config.sourceMap[rust_hash] = rust_source_path
+
+ -- Cargo section is no longer needed
+ final_config.cargo = nil
+
+ return final_config
+end
+
+-- After extracting cargo's compiler metadata with the cargo inspector
+-- parse it to find the binary to debug
+function M.parse_cargo_metadata(cargo_metadata)
+ -- Iterate backwards through the metadata list since the binary
+ -- we're interested will be near the end (usually second to last)
+ for i = 1, #cargo_metadata do
+ local json_table = cargo_metadata[#cargo_metadata + 1 - i]
+
+ -- Some metadata lines may be blank, skip those
+ if string.len(json_table) ~= 0 then
+ -- Each matadata line is a JSON table,
+ -- parse it into a data structure we can work with
+ json_table = vim.fn.json_decode(json_table)
+
+ -- Our binary will be the compiler artifact with an executable defined
+ if json_table["reason"] == "compiler-artifact" and json_table["executable"] ~= vim.NIL then
+ return json_table["executable"]
+ end
+ end
+ end
+
+ return nil
+end
+
+return M