diff options
Diffstat (limited to 'nvim/.config')
-rw-r--r-- | nvim/.config/nvim/lua/plugins/dap.lua | 61 | ||||
-rw-r--r-- | nvim/.config/nvim/lua/plugins/telescope-dap.lua | 25 | ||||
-rw-r--r-- | nvim/.config/nvim/lua/tobyvin/dap/adapters.lua | 33 | ||||
-rw-r--r-- | nvim/.config/nvim/lua/tobyvin/dap/configs.lua | 76 | ||||
-rw-r--r-- | nvim/.config/nvim/lua/tobyvin/utils/dap.lua | 187 |
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 |