return { { 'mfussenegger/nvim-dap', dependencies = { 'rcarriga/nvim-dap-ui', 'nvim-neotest/nvim-nio', { 'theHamsta/nvim-dap-virtual-text', opts = {} }, 'jay-babu/mason-nvim-dap.nvim', }, config = function() local dap = require('dap') local dapui = require('dapui') ---@diagnostic disable-next-line: missing-fields dapui.setup({ enter = false, layouts = { { elements = { { id = 'scopes', size = 0.25 }, { id = 'breakpoints', size = 0.25 }, { id = 'stacks', size = 0.25 }, { id = 'watches', size = 0.25 }, }, size = 40, position = 'left', }, { elements = { { id = 'repl', size = 0.5 }, { id = 'console', size = 0.5 }, }, size = 10, position = 'bottom', }, }, }) -- open/close UI automatically with session lifecycle dap.listeners.after.event_initialized['dapui_config'] = function() dapui.open() end dap.listeners.before.event_terminated['dapui_config'] = function() dapui.close() end dap.listeners.before.event_exited['dapui_config'] = function() dapui.close() end -- defer mason disk I/O after first keypress vim.schedule(function() require('mason-nvim-dap').setup({ automatic_installation = true, ensure_installed = { 'codelldb', 'python', 'delve' }, handlers = {}, }) end) -- VSCode launch.json support local ok, vscode = pcall(require, 'dap.ext.vscode') if ok then vscode.load_launchjs(nil, { codelldb = { 'c', 'cpp', 'rust', 'zig' }, delve = { 'go' }, python = { 'python' }, }) end -- language adapters: only setup when filetype is opened AND dap is loaded local function get_python_path() local venv = vim.env.VIRTUAL_ENV or vim.env.CONDA_PREFIX if venv then local bin = venv .. '/bin/python' if vim.fn.executable(bin) == 1 then return bin end end local local_venv = vim.fn.getcwd() .. '/.venv/bin/python' if vim.fn.executable(local_venv) == 1 then return local_venv end return vim.fn.exepath('python3') ~= '' and vim.fn.exepath('python3') or vim.fn.exepath('python') ~= '' and vim.fn.exepath('python') or 'python3' end vim.api.nvim_create_autocmd('FileType', { pattern = 'go', once = true, callback = function() if package.loaded['dap'] then require('dap-go').setup() end end, }) vim.api.nvim_create_autocmd('FileType', { pattern = 'python', once = true, callback = function() if package.loaded['dap'] then require('dap-python').setup(get_python_path()) end end, }) -- C/C++/Rust/Zig via codelldb local codelldb_config = { { name = 'Launch', type = 'codelldb', request = 'launch', program = function() return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file') end, cwd = '${workspaceFolder}', stopOnEntry = false, }, { name = 'Launch with ASan', type = 'codelldb', request = 'launch', program = function() return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/', 'file') end, cwd = '${workspaceFolder}', stopOnEntry = false, env = { ASAN_OPTIONS = 'detect_leaks=1' }, }, } dap.configurations.c = codelldb_config dap.configurations.cpp = vim.deepcopy(codelldb_config) dap.configurations.rust = { { name = 'Launch', type = 'codelldb', request = 'launch', program = function() return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/target/debug/', 'file') end, cwd = '${workspaceFolder}', stopOnEntry = false, }, } dap.configurations.zig = { { name = 'Launch', type = 'codelldb', request = 'launch', program = function() return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/zig-out/bin/', 'file') end, cwd = '${workspaceFolder}', stopOnEntry = false, }, } -- signs vim.fn.sign_define('DapBreakpoint', { text = '🔴', texthl = 'DiagnosticError', linehl = '', numhl = '' }) vim.fn.sign_define( 'DapBreakpointCondition', { text = '🟡', texthl = 'DiagnosticWarn', linehl = '', numhl = '' } ) vim.fn.sign_define('DapBreakpointRejected', { text = '⭕', texthl = 'DiagnosticError', linehl = '', numhl = '' }) vim.fn.sign_define('DapStopped', { text = '➡️', texthl = 'DiagnosticInfo', linehl = 'debugPC', numhl = '' }) vim.api.nvim_set_hl(0, 'DapStoppedLine', { default = true, link = 'Visual' }) end, keys = { { 'dc', function() require('dap').continue() end, desc = 'Debug: Continue', }, { 'da', function() require('dap').continue({ before = function(c) local args = vim.split(vim.fn.input('Args: '), ' ') c.args = args return c end, }) end, desc = 'Debug: Run with Args', }, { 'dC', function() require('dap').run_to_cursor() end, desc = 'Debug: Run to Cursor', }, { 'ds', function() require('dap').step_over() end, desc = 'Debug: Step Over', }, { 'di', function() require('dap').step_into() end, desc = 'Debug: Step Into', }, { 'do', function() require('dap').step_out() end, desc = 'Debug: Step Out', }, { 'dj', function() require('dap').down() end, desc = 'Debug: Down Frame', }, { 'dk', function() require('dap').up() end, desc = 'Debug: Up Frame', }, { 'dg', function() require('dap').goto_() end, desc = 'Debug: Go to Line', }, { 'db', function() require('dap').toggle_breakpoint() end, desc = 'Debug: Toggle Breakpoint', }, { 'dB', function() require('dap').set_breakpoint(vim.fn.input('Condition: ')) end, desc = 'Debug: Conditional Breakpoint', }, { 'dl', function() require('dap').set_breakpoint(nil, nil, vim.fn.input('Log: ')) end, desc = 'Debug: Log Breakpoint', }, { 'dr', function() require('dap').repl.toggle() end, desc = 'Debug: Toggle REPL', }, { 'dR', function() require('dap').run_last() end, desc = 'Debug: Run Last', }, { 'dt', function() require('dap').terminate() end, desc = 'Debug: Terminate', }, { 'du', function() require('dapui').toggle() end, desc = 'Debug: Toggle UI', }, { 'de', function() require('dapui').eval() end, desc = 'Debug: Eval', mode = { 'n', 'v' }, }, }, }, { 'mfussenegger/nvim-dap-python', lazy = true, config = false }, { 'leoluz/nvim-dap-go', lazy = true, config = false }, }