diff --git a/lua/ow/plugins/dap.lua b/lua/ow/plugins/dap.lua deleted file mode 100644 index 5578088..0000000 --- a/lua/ow/plugins/dap.lua +++ /dev/null @@ -1,90 +0,0 @@ --- https://github.com/mfussenegger/nvim-dap - ----@type LazyPluginSpec -return { - "mfussenegger/nvim-dap", - keys = { - { - "db", - function() - require("dap").toggle_breakpoint() - end, - }, - { - "", - function() - require("dap.ui.widgets").hover() - end, - }, - { - "", - function() - require("dap").step_into() - end, - }, - { - "", - function() - require("dap").step_over() - end, - }, - { - "", - function() - require("dap").step_out() - end, - }, - { - "", - function() - require("dap").continue() - end, - }, - { - "", - function() - local widgets = require("dap.ui.widgets") - widgets.centered_float(widgets.scopes) - end, - }, - { - "", - function() - require("dap").terminate() - end, - }, - }, - config = function() - local dap = require("dap") - - -- https://sourceware.org/gdb/current/onlinedocs/gdb#Debugger-Adapter-Protocol - dap.adapters.gdb = { - type = "executable", - command = "gdb", - args = { - "--interpreter=dap", - "--eval-command", - "set print pretty on", - "--eval-command", - "set startup-with-shell off", - }, - } - - dap.configurations.cpp = { - { - name = "Launch", - type = "gdb", - request = "launch", - program = function() - return vim.fn.input( - "Path to executable: ", - vim.fn.getcwd() .. "/", - "file" - ) - end, - cwd = "${workspaceFolder}", - stopAtBeginningOfMainSubprogram = false, - }, - } - end, -} diff --git a/lua/ow/plugins/nvim-dap-ui.lua b/lua/ow/plugins/nvim-dap-ui.lua new file mode 100644 index 0000000..4667043 --- /dev/null +++ b/lua/ow/plugins/nvim-dap-ui.lua @@ -0,0 +1,8 @@ +-- https://github.com/igorlfs/nvim-dap-view + +---@type LazyPluginSpec +return { + "rcarriga/nvim-dap-ui", + dependencies = { "mfussenegger/nvim-dap", "nvim-neotest/nvim-nio" }, + config = true, +} diff --git a/lua/ow/plugins/nvim-dap.lua b/lua/ow/plugins/nvim-dap.lua new file mode 100644 index 0000000..ddd4550 --- /dev/null +++ b/lua/ow/plugins/nvim-dap.lua @@ -0,0 +1,418 @@ +-- https://github.com/mfussenegger/nvim-dap +local log = require("ow.log") + +local Hover = {} + +---@param filetype string +---@param lines string[] +function Hover.show_lines(filetype, lines) + local buf = vim.api.nvim_create_buf(false, true) + vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines) + vim.api.nvim_set_option_value("filetype", filetype, { buf = buf }) + + local max_width = 0 + for _, line in ipairs(lines) do + max_width = math.max(max_width, #line) + end + + if max_width == 0 then + return + end + + local win = vim.api.nvim_open_win(buf, false, { + relative = "cursor", + width = math.min(80, max_width), + height = math.min(20, #lines), + row = 1, + col = 0, + border = "rounded", + style = "minimal", + }) + + vim.api.nvim_create_autocmd({ "CursorMoved", "BufLeave", "InsertEnter" }, { + once = true, + callback = function() + if vim.api.nvim_win_is_valid(win) then + vim.api.nvim_win_close(win, true) + end + end, + }) +end + +---@param frame_id integer +---@param expr string +---@param eval_resp dap.EvaluateResponse +---@param variables dap.Variable[] +---@param callback fun(filetype: string, lines: string[]) +function Hover.format_expandable(frame_id, expr, eval_resp, variables, callback) + local filetype = vim.bo.filetype + local lines = {} + + if filetype == "c" or filetype == "cpp" then + if eval_resp.type and eval_resp.type:match("%*%s*$") then + if eval_resp.result == "0x0" then + table.insert( + lines, + string.format("%s = %s", expr, eval_resp.result) + ) + goto done + else + Hover.eval_expr(frame_id, "*" .. expr) + return + end + else + table.insert( + lines, + string.format("%s = (%s) {", expr, eval_resp.type) + ) + end + for _, var in ipairs(variables) do + if var.variablesReference and var.variablesReference > 0 then + table.insert( + lines, + string.format(" .%s = (%s) { ... },", var.name, var.type) + ) + else + table.insert( + lines, + string.format(" .%s = %s,", var.name, var.value or "?") + ) + end + end + table.insert(lines, "}") + elseif filetype == "python" then + local ignored_names = { + ["special variables"] = true, + ["function variables"] = true, + ["class variables"] = true, + } + local ignored_types = { + ["method"] = true, + ["function"] = true, + } + table.insert(lines, string.format("%s: %s", expr, eval_resp.type)) + for _, var in ipairs(variables) do + if ignored_names[var.name] or ignored_types[var.type] then + goto continue + end + + if var.variablesReference and var.variablesReference > 0 then + table.insert( + lines, + string.format(" %s: %s = ...", var.name, var.type) + ) + else + table.insert( + lines, + string.format( + " %s: %s = %s", + var.name, + var.type, + var.value + ) + ) + end + ::continue:: + end + else + filetype = "yaml" + table.insert(lines, string.format("%s:", expr)) + for _, var in ipairs(variables) do + if var.variablesReference and var.variablesReference > 0 then + table.insert(lines, string.format(" %s: ...", var.name)) + else + table.insert( + lines, + string.format(" %s: %s", var.name, var.value) + ) + end + end + end + + ::done:: + callback(filetype, lines) +end + +---@param frame_id integer +---@param expr string +function Hover.eval_expr(frame_id, expr) + Hover.session:request("evaluate", { + expression = expr, + frameId = frame_id, + context = "hover", + }, function(err, resp) + log.debug("resp: %s", vim.inspect(resp)) + if err or not resp or not resp.result then + if err then + log.warning("Failed to evaluate '%s': %s", expr, err) + end + return + end + + if resp.variablesReference and resp.variablesReference > 0 then + Hover.session:request("variables", { + variablesReference = resp.variablesReference, + }, function(e, r) + if e or not r or #r.variables <= 0 then + log.warning("Failed to evaluate '%s': %s", expr, e) + Hover.show_lines( + vim.bo.filetype, + { string.format("%s = %s", expr, resp.result) } + ) + else + Hover.format_expandable( + frame_id, + expr, + resp, + r.variables, + function(filetype, lines) + Hover.show_lines(filetype, lines) + end + ) + end + end) + else + Hover.show_lines( + vim.bo.filetype, + { string.format("%s = %s", expr, resp.result) } + ) + end + end) +end + +function Hover.dap_hover() + Hover.dap = require("dap") + Hover.session = Hover.dap.session() + if not Hover.session then + return + end + + local expr + local mode = vim.api.nvim_get_mode() + if mode.mode == "v" then + local start_pos = vim.fn.getpos("v") + local end_pos = vim.fn.getpos(".") + + local start_row, start_col = start_pos[2], start_pos[3] + local end_row, end_col = end_pos[2], end_pos[3] + + if start_row == end_row and end_col < start_col then + start_col, end_col = end_col, start_col + elseif end_row < start_row then + start_row, end_row = end_row, start_row + start_col, end_col = end_col, start_col + end + + local lines = vim.api.nvim_buf_get_text( + 0, + start_row - 1, + start_col - 1, + end_row - 1, + end_col, + {} + ) + expr = table.concat(lines, "\n") + + vim.api.nvim_feedkeys( + vim.api.nvim_replace_termcodes("", true, false, true), + "n", + false + ) + else + expr = vim.fn.expand("") + end + + if expr == "" then + return + end + + Hover.session:request( + "stackTrace", + { threadId = 1 }, + function(err, stack_resp) + if err or not stack_resp or not stack_resp.stackFrames[1] then + return + end + + local frame_id = stack_resp.stackFrames[1].id + Hover.eval_expr(frame_id, expr) + end + ) +end + +---@type LazyPluginSpec +return { + "mfussenegger/nvim-dap", + keys = { + { + "db", + function() + require("dap").toggle_breakpoint() + end, + }, + { + "df", + function() + require("dap").focus_frame() + end, + }, + { + "do", + function() + require("dap").up() + end, + }, + { + "di", + function() + require("dap").down() + end, + }, + { + "dk", + Hover.dap_hover, + mode = { "n", "x" }, + }, + { + "", + function() + require("dap").step_into() + end, + }, + { + "", + function() + require("dap").step_over() + end, + }, + { + "", + function() + require("dap").step_out() + end, + }, + { + "", + function() + require("dap").continue() + end, + }, + { + "", + function() + require("dap").terminate() + end, + }, + }, + config = function() + local dap = require("dap") + + vim.api.nvim_create_user_command( + "Debug", + ---@param opts vim.api.keyset.create_user_command.command_args + function(opts) + local cfgs = dap.configurations[vim.bo.filetype] + if not cfgs then + log.error( + "No configurations available for filetype %s", + vim.bo.filetype + ) + return + end + + local function run_config(cfg) + local all_args = vim.fn.split(opts.args) + cfg.program = all_args[1] + + local args = {} + for i = 2, #all_args do + table.insert(args, all_args[i]) + end + cfg.args = args + + log.debug("Config: %s", vim.inspect(cfg)) + dap.run(cfg) + end + + if #cfgs == 1 then + run_config(cfgs[1]) + return + end + + local names = {} + for _, c in ipairs(cfgs) do + table.insert(names, c.name) + end + + vim.ui.select(names, { + prompt = "Select DAP configuration to use:", + }, function(choice, idx) + if choice and idx then + run_config(cfgs[idx]) + end + end) + end, + { + nargs = "+", + ---@param ArgLead string + ---@param CmdLine string + complete = function(ArgLead, CmdLine, CursorPos) + local _, spaces = CmdLine:sub(1, CursorPos):gsub("%s+", "") + + if spaces == 1 then + return vim.fn.getcompletion(ArgLead, "file") + end + end, + } + ) + + dap.adapters.gdb = { + type = "executable", + command = "gdb", + args = { "--interpreter=dap" }, + } + + dap.adapters.python = { + type = "executable", + command = "python", + args = { "-m", "debugpy.adapter" }, + } + + dap.configurations.c = { + { + type = "gdb", + request = "launch", + name = "Launch", + program = function() + local path = vim.fn.input({ + prompt = "Path to executable: ", + default = vim.fn.getcwd() .. "/", + completion = "file", + }) + return (path and path ~= "") and path or dap.ABORT + end, + cwd = "${workspaceFolder}", + stopAtBeginningOfMainSubprogram = false, + }, + } + + dap.configurations.cpp = dap.configurations.c + + dap.configurations.python = { + { + type = "python", + request = "launch", + name = "Launch", + program = function() + local path = vim.fn.input({ + prompt = "Path to executable: ", + default = vim.fn.getcwd() .. "/", + completion = "file", + }) + return (path and path ~= "") and path or dap.ABORT + end, + cwd = "${workspaceFolder}", + }, + } + end, +}