audit: comprehensive cleanup — delete 8 files, fix buffer bug, cut bloat
This commit is contained in:
28 files changed
+279
-656
No files matched your search
+9
-62
@@ -1,70 +1,17 @@
|
|||||||
# Contributing to tiny-debugger.nvim
|
# Contributing
|
||||||
|
|
||||||
## Source Control
|
We use **jj** (Jujutsu) for source control, not git.
|
||||||
|
|
||||||
We use **jj** (Jujutsu), not git. All commits should be made with `jj commit`.
|
## Adding a new adapter
|
||||||
|
|
||||||
## Adding a New Adapter
|
1. Create `lua/tiny-debugger/adapters/<language>.lua` (see `go.lua` for reference)
|
||||||
|
2. Add the module name to the loader list in `adapters/init.lua`
|
||||||
|
3. Add the language to vimdoc
|
||||||
|
|
||||||
1. Create `lua/tiny-debugger/adapters/<language>.lua`
|
For simple executable adapters, add them inline in `adapters/init.lua` instead of a separate file.
|
||||||
2. Implement a `register(dap)` function that calls `loader.register()`
|
|
||||||
3. Add the module name to the adapters list in `lua/tiny-debugger/adapters/init.lua`
|
|
||||||
4. Add the language to the README and vimdoc tables
|
|
||||||
|
|
||||||
Example adapter (minimal):
|
## Running tests
|
||||||
|
|
||||||
```lua
|
|
||||||
local loader = require("tiny-debugger.adapters.init")
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
function M.register(dap)
|
|
||||||
local binary = loader.find_executable({ "my-debug-adapter" })
|
|
||||||
if not binary then return end
|
|
||||||
|
|
||||||
loader.register(dap, "my-adapter", {
|
|
||||||
type = "executable",
|
|
||||||
command = binary,
|
|
||||||
}, {
|
|
||||||
{
|
|
||||||
type = "my-adapter",
|
|
||||||
request = "launch",
|
|
||||||
name = "Launch file",
|
|
||||||
program = "${file}",
|
|
||||||
},
|
|
||||||
}, { "mylang" })
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
```
|
|
||||||
|
|
||||||
## Running Tests
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
nvim --headless -u tests/minimal_init.lua -c 'luafile tests/test_config.lua' -c 'qa!'
|
for t in tests/test_*.lua; do nvim --headless -u tests/minimal_init.lua -c "luafile $t" -c 'qa!'; done
|
||||||
nvim --headless -u tests/minimal_init.lua -c 'luafile tests/test_mode.lua' -c 'qa!'
|
|
||||||
nvim --headless -u tests/minimal_init.lua -c 'luafile tests/test_keys.lua' -c 'qa!'
|
|
||||||
nvim --headless -u tests/minimal_init.lua -c 'luafile tests/test_help.lua' -c 'qa!'
|
|
||||||
nvim --headless -u tests/minimal_init.lua -c 'luafile tests/test_breakpoints.lua' -c 'qa!'
|
|
||||||
nvim --headless -u tests/minimal_init.lua -c 'luafile tests/test_adapters.lua' -c 'qa!'
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Code Style
|
|
||||||
|
|
||||||
- Clean, readable Lua. No metaprogramming tricks.
|
|
||||||
- Every file should be short enough to read in one sitting.
|
|
||||||
- No abstraction for abstraction's sake.
|
|
||||||
- If a plugin does something we can do in <50 lines, we don't add the dependency.
|
|
||||||
|
|
||||||
## Dependencies
|
|
||||||
|
|
||||||
We depend on exactly five plugins (managed via `vim.pack.add`):
|
|
||||||
|
|
||||||
- nvim-dap
|
|
||||||
- nvim-dap-ui
|
|
||||||
- nvim-nio
|
|
||||||
- nvim-dap-virtual-text
|
|
||||||
- persistent-breakpoints.nvim
|
|
||||||
|
|
||||||
No language-specific plugin dependencies. Adapter configs are inlined as plain
|
|
||||||
Lua tables.
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# tiny-debugger.nvim
|
# tiny-debugger.nvim
|
||||||
|
|
||||||
A zero-config debugging experience for Neovim 0.12+. One line of config, full
|
A zero-config debugging experience for Neovim 0.12+. One plugin install, full
|
||||||
IDE-class debugging: inline variable values, hover inspection, persistent
|
IDE-class debugging: inline variable values, hover inspection, persistent
|
||||||
breakpoints, watch expressions, and modal keybindings that just work.
|
breakpoints, watch expressions, and modal keybindings that just work.
|
||||||
|
|
||||||
@@ -10,115 +10,38 @@ breakpoints, watch expressions, and modal keybindings that just work.
|
|||||||
vim.pack.add("swaits/tiny-debugger.nvim")
|
vim.pack.add("swaits/tiny-debugger.nvim")
|
||||||
```
|
```
|
||||||
|
|
||||||
That's it. No other config needed.
|
That's it. No other config needed. See `:help tiny-debugger` for customization.
|
||||||
|
|
||||||
## What You Get
|
## Usage
|
||||||
|
|
||||||
- **Inline virtual text** — variable values appear right in your source code,
|
`<leader>d` toggles debug mode. `x` sets breakpoints (works anytime).
|
||||||
updated live as you step
|
|
||||||
- **Hover inspection** — press `K` on any expression to see its value
|
|
||||||
- **Persistent breakpoints** — survive Neovim restarts, visible in the sign
|
|
||||||
column at all times
|
|
||||||
- **Watch expressions** — press `W` to add the expression under your cursor
|
|
||||||
- **Scopes panel** — all local/global variables in the current frame
|
|
||||||
- **Stack traces** — navigate the call stack, jump to any frame
|
|
||||||
- **REPL** — evaluate arbitrary expressions in the debug context
|
|
||||||
- **Console** — program stdout/stderr
|
|
||||||
- **Modal keybindings** — single-key controls, active only during debug sessions
|
|
||||||
- **Help box** — shows all keybindings, re-openable with `?`
|
|
||||||
- **Zero-config adapters** for 10+ languages
|
|
||||||
|
|
||||||
## UI Layout
|
During debug mode, single-key controls:
|
||||||
|
|
||||||
```
|
| Key | Action | Key | Action |
|
||||||
┌──────────────────────────────────┬──────────────────┐
|
|-----|------------------------|-----|------------------------|
|
||||||
│ │ Scopes (50%) │
|
| `c` | Continue | `W` | Watch expression |
|
||||||
│ Source Code │ │
|
| `s` | Step over | `K` | Hover / inspect |
|
||||||
│ ├──────────────────┤
|
| `d` | Step into | `E` | Eval in REPL |
|
||||||
│ x = 42 -- « virtual text │ Watches (25%) │
|
| `r` | Step out | `q` | Terminate session |
|
||||||
│ ├──────────────────┤
|
| `C` | Run to cursor | `R` | Restart session |
|
||||||
│ │ Stacks (25%) │
|
| `x` | Toggle breakpoint | `D` | Clear all breakpoints |
|
||||||
├──────────────────────────────────┴──────────────────┤
|
| `X` | Conditional breakpoint | `?` | Help popup |
|
||||||
│ REPL (50%) │ Console (50%) │
|
|
||||||
└──────────────────────────┴──────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Keybindings
|
All navigation keys preserved. Keybindings vanish when you exit debug mode.
|
||||||
|
|
||||||
`<leader>d` toggles debug mode. During debug mode:
|
|
||||||
|
|
||||||
| Key | Action | Mnemonic |
|
|
||||||
|-----|------------------------|---------------------|
|
|
||||||
| `c` | Continue | Continue |
|
|
||||||
| `s` | Step over | Step |
|
|
||||||
| `d` | Step into | Descend |
|
|
||||||
| `r` | Step out | Return |
|
|
||||||
| `C` | Run to cursor | Continue to cursor |
|
|
||||||
| `x` | Toggle breakpoint | X marks the spot |
|
|
||||||
| `X` | Conditional breakpoint | Shift-x |
|
|
||||||
| `W` | Watch expression | Watch |
|
|
||||||
| `K` | Hover / inspect | LSP hover convention|
|
|
||||||
| `E` | Eval in REPL | Evaluate |
|
|
||||||
| `q` | Terminate session | Quit |
|
|
||||||
| `R` | Restart session | Rerun |
|
|
||||||
| `?` | Help popup | Help |
|
|
||||||
|
|
||||||
`x` and `X` work outside debug mode too.
|
|
||||||
|
|
||||||
All navigation keys preserved: `h j k l w b e f / n` etc.
|
|
||||||
|
|
||||||
## Supported Languages
|
## Supported Languages
|
||||||
|
|
||||||
| Language | Adapter | Binary needed |
|
Adapter binaries must be installed (via mason, package manager, etc).
|
||||||
|-----------------|-----------------|----------------------|
|
|
||||||
| Python | debugpy | `python -m debugpy` |
|
|
||||||
| JavaScript / TS | js-debug | `js-debug-adapter` |
|
|
||||||
| Go | delve | `dlv` |
|
|
||||||
| C / C++ | codelldb | `codelldb` |
|
|
||||||
| Rust | codelldb | `codelldb` |
|
|
||||||
| Swift | codelldb | `codelldb` |
|
|
||||||
| C# | netcoredbg | `netcoredbg` |
|
|
||||||
| Java | java-debug | `java-debug-adapter` |
|
|
||||||
| Kotlin | kotlin-debug | `kotlin-debug-adapter`|
|
|
||||||
| PHP | php-debug | `php-debug-adapter` |
|
|
||||||
| Ruby | rdbg | `rdbg` |
|
|
||||||
| Lua (Neovim) | osv | one-small-step-for-vimkind |
|
|
||||||
|
|
||||||
Adapter binaries are the user's responsibility to install (via mason, system
|
| Language | Adapter | Language | Adapter |
|
||||||
package manager, or manually). tiny-debugger configures, it doesn't install.
|
|-----------------|------------|--------------|--------------------|
|
||||||
|
| Python | debugpy | C# | netcoredbg |
|
||||||
## Configuration (optional)
|
| JavaScript / TS | js-debug | Java | java-debug-adapter |
|
||||||
|
| Go | delve | Kotlin | kotlin-debug |
|
||||||
```lua
|
| C / C++ | codelldb | PHP | php-debug |
|
||||||
require("tiny-debugger").setup({
|
| Rust | codelldb | Ruby | rdbg |
|
||||||
help_on_enter = true, -- show help box when entering debug mode
|
| Swift | codelldb | Lua (Neovim) | osv |
|
||||||
builtin_adapters = true, -- register built-in language adapters
|
|
||||||
|
|
||||||
keys = {
|
|
||||||
continue = "c", -- override any key
|
|
||||||
step_over = false, -- set to false to disable
|
|
||||||
},
|
|
||||||
|
|
||||||
adapters = {
|
|
||||||
zig = { ... }, -- add new language adapters
|
|
||||||
},
|
|
||||||
|
|
||||||
dapui = { ... }, -- override nvim-dap-ui layout
|
|
||||||
virtual_text = { ... }, -- override virtual text settings
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Statusline
|
|
||||||
|
|
||||||
```lua
|
|
||||||
require("tiny-debugger").active() -- returns true/false
|
|
||||||
```
|
|
||||||
|
|
||||||
## Help
|
|
||||||
|
|
||||||
```
|
|
||||||
:help tiny-debugger
|
|
||||||
```
|
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ Default keymap:
|
|||||||
`C` Run to cursor Continue to cursor
|
`C` Run to cursor Continue to cursor
|
||||||
`x` Toggle breakpoint X marks the spot (always active)
|
`x` Toggle breakpoint X marks the spot (always active)
|
||||||
`X` Conditional breakpoint Shift variant of x (always active)
|
`X` Conditional breakpoint Shift variant of x (always active)
|
||||||
|
`D` Clear all breakpoints Delete all (always active)
|
||||||
`W` Watch expr under cursor Watch
|
`W` Watch expr under cursor Watch
|
||||||
`K` Hover / inspect Matches LSP hover convention
|
`K` Hover / inspect Matches LSP hover convention
|
||||||
`E` Eval in REPL Evaluate
|
`E` Eval in REPL Evaluate
|
||||||
@@ -108,7 +109,7 @@ Default keymap:
|
|||||||
`R` Restart session Rerun
|
`R` Restart session Rerun
|
||||||
`?` Help popup Show keybindings
|
`?` Help popup Show keybindings
|
||||||
|
|
||||||
Note: `x` and `X` work outside debug mode too (persistent breakpoints).
|
Note: `x`, `X`, and `D` work outside debug mode too (persistent breakpoints).
|
||||||
|
|
||||||
Override any key: >lua
|
Override any key: >lua
|
||||||
require("tiny-debugger").setup({
|
require("tiny-debugger").setup({
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ function M.register(dap)
|
|||||||
return vim.fn.input("Path to executable: ", vim.fn.getcwd() .. "/", "file")
|
return vim.fn.input("Path to executable: ", vim.fn.getcwd() .. "/", "file")
|
||||||
end,
|
end,
|
||||||
cwd = "${workspaceFolder}",
|
cwd = "${workspaceFolder}",
|
||||||
stopOnEntry = false,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
local loader = require("tiny-debugger.adapters.init")
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
function M.register(dap)
|
|
||||||
local netcoredbg = loader.find_executable({ "netcoredbg" })
|
|
||||||
if not netcoredbg then return end
|
|
||||||
|
|
||||||
loader.register(dap, "netcoredbg", {
|
|
||||||
type = "executable",
|
|
||||||
command = netcoredbg,
|
|
||||||
args = { "--interpreter=vscode" },
|
|
||||||
}, {
|
|
||||||
{
|
|
||||||
type = "netcoredbg",
|
|
||||||
request = "launch",
|
|
||||||
name = "Launch .NET",
|
|
||||||
program = function()
|
|
||||||
return vim.fn.input("Path to DLL: ", vim.fn.getcwd() .. "/bin/Debug/", "file")
|
|
||||||
end,
|
|
||||||
},
|
|
||||||
}, { "cs" })
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -6,30 +6,83 @@ local mason_bin = vim.fn.stdpath("data") .. "/mason/bin/"
|
|||||||
|
|
||||||
function M.find_executable(names)
|
function M.find_executable(names)
|
||||||
for _, name in ipairs(names) do
|
for _, name in ipairs(names) do
|
||||||
-- check PATH
|
if vim.fn.executable(name) == 1 then return name end
|
||||||
if vim.fn.executable(name) == 1 then
|
|
||||||
return name
|
|
||||||
end
|
|
||||||
-- check mason
|
|
||||||
local mason_path = mason_bin .. name
|
local mason_path = mason_bin .. name
|
||||||
if vim.fn.executable(mason_path) == 1 then
|
if vim.fn.executable(mason_path) == 1 then return mason_path end
|
||||||
return mason_path
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
return nil
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.register(dap, name, adapter, configurations, filetypes)
|
function M.register(dap, name, adapter, configurations, filetypes)
|
||||||
-- don't override user-configured adapters
|
|
||||||
if dap.adapters[name] then return end
|
if dap.adapters[name] then return end
|
||||||
|
|
||||||
dap.adapters[name] = adapter
|
dap.adapters[name] = adapter
|
||||||
|
|
||||||
for _, ft in ipairs(filetypes) do
|
for _, ft in ipairs(filetypes) do
|
||||||
if not dap.configurations[ft] then
|
dap.configurations[ft] = dap.configurations[ft] or configurations
|
||||||
dap.configurations[ft] = configurations
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
-- simple executable adapters (inlined — no separate files needed)
|
||||||
|
local function register_simple_adapters(dap)
|
||||||
|
local simple = {
|
||||||
|
{ "netcoredbg", { "netcoredbg" }, {
|
||||||
|
type = "executable", args = { "--interpreter=vscode" },
|
||||||
|
}, { {
|
||||||
|
type = "netcoredbg", request = "launch", name = "Launch .NET",
|
||||||
|
program = function() return vim.fn.input("Path to DLL: ", vim.fn.getcwd() .. "/bin/Debug/", "file") end,
|
||||||
|
} }, { "cs" } },
|
||||||
|
|
||||||
|
{ "java", { "java-debug-adapter" }, {
|
||||||
|
type = "executable",
|
||||||
|
}, { {
|
||||||
|
type = "java", request = "launch", name = "Launch class",
|
||||||
|
mainClass = function() return vim.fn.input("Main class: ") end,
|
||||||
|
cwd = "${workspaceFolder}",
|
||||||
|
} }, { "java" } },
|
||||||
|
|
||||||
|
{ "kotlin", { "kotlin-debug-adapter" }, {
|
||||||
|
type = "executable",
|
||||||
|
}, { {
|
||||||
|
type = "kotlin", request = "launch", name = "Launch main",
|
||||||
|
mainClass = function() return vim.fn.input("Main class: ") end,
|
||||||
|
projectRoot = "${workspaceFolder}",
|
||||||
|
} }, { "kotlin" } },
|
||||||
|
|
||||||
|
{ "php", { "php-debug-adapter" }, {
|
||||||
|
type = "executable",
|
||||||
|
}, { {
|
||||||
|
type = "php", request = "launch", name = "Listen for Xdebug", port = 9003,
|
||||||
|
} }, { "php" } },
|
||||||
|
|
||||||
|
{ "rdbg", { "rdbg" }, {
|
||||||
|
type = "server", port = "${port}",
|
||||||
|
executable = { command = nil, args = { "-n", "--open", "--port", "${port}", "-c", "--", "ruby", "${file}" } },
|
||||||
|
}, { {
|
||||||
|
type = "rdbg", request = "launch", name = "Launch file", command = "ruby", script = "${file}",
|
||||||
|
}, {
|
||||||
|
type = "rdbg", request = "attach", name = "Attach", localfsMap = "${workspaceFolder}",
|
||||||
|
} }, { "ruby" } },
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s in ipairs(simple) do
|
||||||
|
local name, exe_names, adapter_tpl, configurations, filetypes = s[1], s[2], s[3], s[4], s[5]
|
||||||
|
local exe = M.find_executable(exe_names)
|
||||||
|
if exe then
|
||||||
|
local adapter = vim.deepcopy(adapter_tpl)
|
||||||
|
adapter.command = adapter.command or exe
|
||||||
|
if adapter.executable then adapter.executable.command = exe end
|
||||||
|
M.register(dap, name, adapter, configurations, filetypes)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- lua/neovim (osv) — optional, skip if not installed
|
||||||
|
local ok, osv = pcall(require, "osv")
|
||||||
|
if ok then
|
||||||
|
M.register(dap, "nlua", {
|
||||||
|
type = "server", host = "127.0.0.1",
|
||||||
|
port = function() return osv.launch({ port = 0 }) end,
|
||||||
|
}, { {
|
||||||
|
type = "nlua", request = "attach", name = "Attach to running Neovim",
|
||||||
|
} }, { "lua" })
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.setup()
|
function M.setup()
|
||||||
@@ -37,26 +90,21 @@ function M.setup()
|
|||||||
|
|
||||||
local dap = require("dap")
|
local dap = require("dap")
|
||||||
|
|
||||||
-- load built-in adapter modules
|
-- load adapters with non-trivial logic from separate files
|
||||||
local adapters = {
|
for _, name in ipairs({ "python", "go", "c", "javascript" }) do
|
||||||
"python", "go", "c", "javascript", "cs", "java", "kotlin", "php", "ruby", "lua",
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, name in ipairs(adapters) do
|
|
||||||
local ok, adapter_mod = pcall(require, "tiny-debugger.adapters." .. name)
|
local ok, adapter_mod = pcall(require, "tiny-debugger.adapters." .. name)
|
||||||
if ok and adapter_mod.register then
|
if ok and adapter_mod.register then
|
||||||
adapter_mod.register(dap)
|
adapter_mod.register(dap)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- apply user adapter overrides
|
-- register simple adapters inlined above
|
||||||
|
register_simple_adapters(dap)
|
||||||
|
|
||||||
|
-- apply user adapter overrides (last — wins over everything)
|
||||||
for name, adapter_config in pairs(config.opts.adapters) do
|
for name, adapter_config in pairs(config.opts.adapters) do
|
||||||
if adapter_config.adapter then
|
if adapter_config.adapter then dap.adapters[name] = adapter_config.adapter end
|
||||||
dap.adapters[name] = adapter_config.adapter
|
if adapter_config.configurations then dap.configurations[name] = adapter_config.configurations end
|
||||||
end
|
|
||||||
if adapter_config.configurations then
|
|
||||||
dap.configurations[name] = adapter_config.configurations
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
local loader = require("tiny-debugger.adapters.init")
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
function M.register(dap)
|
|
||||||
local java_debug = loader.find_executable({ "java-debug-adapter" })
|
|
||||||
if not java_debug then return end
|
|
||||||
|
|
||||||
loader.register(dap, "java", {
|
|
||||||
type = "executable",
|
|
||||||
command = java_debug,
|
|
||||||
}, {
|
|
||||||
{
|
|
||||||
type = "java",
|
|
||||||
request = "launch",
|
|
||||||
name = "Launch class",
|
|
||||||
mainClass = function()
|
|
||||||
return vim.fn.input("Main class: ")
|
|
||||||
end,
|
|
||||||
cwd = "${workspaceFolder}",
|
|
||||||
},
|
|
||||||
}, { "java" })
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -8,7 +8,6 @@ function M.register(dap)
|
|||||||
|
|
||||||
local adapter = {
|
local adapter = {
|
||||||
type = "server",
|
type = "server",
|
||||||
host = "localhost",
|
|
||||||
port = "${port}",
|
port = "${port}",
|
||||||
executable = {
|
executable = {
|
||||||
command = js_debug,
|
command = js_debug,
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
local loader = require("tiny-debugger.adapters.init")
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
function M.register(dap)
|
|
||||||
local kotlin_debug = loader.find_executable({ "kotlin-debug-adapter" })
|
|
||||||
if not kotlin_debug then return end
|
|
||||||
|
|
||||||
loader.register(dap, "kotlin", {
|
|
||||||
type = "executable",
|
|
||||||
command = kotlin_debug,
|
|
||||||
}, {
|
|
||||||
{
|
|
||||||
type = "kotlin",
|
|
||||||
request = "launch",
|
|
||||||
name = "Launch main",
|
|
||||||
mainClass = function()
|
|
||||||
return vim.fn.input("Main class: ")
|
|
||||||
end,
|
|
||||||
projectRoot = "${workspaceFolder}",
|
|
||||||
},
|
|
||||||
}, { "kotlin" })
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
local loader = require("tiny-debugger.adapters.init")
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
function M.register(dap)
|
|
||||||
-- osv (one-small-step-for-vimkind) is optional — skip gracefully if not installed
|
|
||||||
local ok, osv = pcall(require, "osv")
|
|
||||||
if not ok then return end
|
|
||||||
|
|
||||||
loader.register(dap, "nlua", {
|
|
||||||
type = "server",
|
|
||||||
host = "127.0.0.1",
|
|
||||||
port = function()
|
|
||||||
return osv.launch({ port = 0 })
|
|
||||||
end,
|
|
||||||
}, {
|
|
||||||
{
|
|
||||||
type = "nlua",
|
|
||||||
request = "attach",
|
|
||||||
name = "Attach to running Neovim",
|
|
||||||
},
|
|
||||||
}, { "lua" })
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
local loader = require("tiny-debugger.adapters.init")
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
function M.register(dap)
|
|
||||||
local php_debug = loader.find_executable({ "php-debug-adapter" })
|
|
||||||
if not php_debug then return end
|
|
||||||
|
|
||||||
loader.register(dap, "php", {
|
|
||||||
type = "executable",
|
|
||||||
command = php_debug,
|
|
||||||
}, {
|
|
||||||
{
|
|
||||||
type = "php",
|
|
||||||
request = "launch",
|
|
||||||
name = "Listen for Xdebug",
|
|
||||||
port = 9003,
|
|
||||||
},
|
|
||||||
}, { "php" })
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
local loader = require("tiny-debugger.adapters.init")
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
function M.register(dap)
|
|
||||||
local rdbg = loader.find_executable({ "rdbg" })
|
|
||||||
if not rdbg then return end
|
|
||||||
|
|
||||||
loader.register(dap, "rdbg", {
|
|
||||||
type = "server",
|
|
||||||
port = "${port}",
|
|
||||||
executable = {
|
|
||||||
command = rdbg,
|
|
||||||
args = { "-n", "--open", "--port", "${port}", "-c", "--", "ruby", "${file}" },
|
|
||||||
},
|
|
||||||
}, {
|
|
||||||
{
|
|
||||||
type = "rdbg",
|
|
||||||
request = "launch",
|
|
||||||
name = "Launch file",
|
|
||||||
command = "ruby",
|
|
||||||
script = "${file}",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type = "rdbg",
|
|
||||||
request = "attach",
|
|
||||||
name = "Attach",
|
|
||||||
localfsMap = "${workspaceFolder}",
|
|
||||||
},
|
|
||||||
}, { "ruby" })
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -12,37 +12,32 @@ function M.setup()
|
|||||||
load_breakpoints_event = { "BufReadPost" },
|
load_breakpoints_event = { "BufReadPost" },
|
||||||
})
|
})
|
||||||
|
|
||||||
-- sign highlights
|
-- sign highlights (default = true so colorschemes can override)
|
||||||
vim.api.nvim_set_hl(0, "DapBreakpoint", { fg = "#e06c75" })
|
vim.api.nvim_set_hl(0, "DapBreakpoint", { default = true, fg = "#e06c75" })
|
||||||
vim.api.nvim_set_hl(0, "DapBreakpointCondition", { fg = "#e5c07b" })
|
vim.api.nvim_set_hl(0, "DapBreakpointCondition", { default = true, fg = "#e5c07b" })
|
||||||
vim.api.nvim_set_hl(0, "DapBreakpointRejected", { fg = "#5c6370" })
|
vim.api.nvim_set_hl(0, "DapBreakpointRejected", { default = true, fg = "#5c6370" })
|
||||||
vim.api.nvim_set_hl(0, "DapStopped", { fg = "#98c379" })
|
vim.api.nvim_set_hl(0, "DapStopped", { default = true, fg = "#98c379" })
|
||||||
vim.api.nvim_set_hl(0, "DapStoppedLine", { bg = "#2e3b2e" })
|
vim.api.nvim_set_hl(0, "DapStoppedLine", { default = true, bg = "#2e3b2e" })
|
||||||
vim.api.nvim_set_hl(0, "DapLogPoint", { fg = "#61afef" })
|
vim.api.nvim_set_hl(0, "DapLogPoint", { default = true, fg = "#61afef" })
|
||||||
|
|
||||||
-- gutter signs
|
|
||||||
vim.fn.sign_define("DapBreakpoint", { text = "🔴", texthl = "DapBreakpoint" })
|
vim.fn.sign_define("DapBreakpoint", { text = "🔴", texthl = "DapBreakpoint" })
|
||||||
vim.fn.sign_define("DapBreakpointCondition", { text = "🟡", texthl = "DapBreakpointCondition" })
|
vim.fn.sign_define("DapBreakpointCondition", { text = "🟡", texthl = "DapBreakpointCondition" })
|
||||||
vim.fn.sign_define("DapBreakpointRejected", { text = "⭕", texthl = "DapBreakpointRejected" })
|
vim.fn.sign_define("DapBreakpointRejected", { text = "⭕", texthl = "DapBreakpointRejected" })
|
||||||
vim.fn.sign_define("DapStopped", { text = "▶️", texthl = "DapStopped", linehl = "DapStoppedLine" })
|
vim.fn.sign_define("DapStopped", { text = "▶️", texthl = "DapStopped", linehl = "DapStoppedLine" })
|
||||||
vim.fn.sign_define("DapLogPoint", { text = "📝", texthl = "DapLogPoint" })
|
vim.fn.sign_define("DapLogPoint", { text = "📝", texthl = "DapLogPoint" })
|
||||||
|
|
||||||
-- global mappings — these work outside debug mode
|
-- global mappings (work outside debug mode)
|
||||||
local keys = config.opts.keys
|
local keys = config.opts.keys
|
||||||
if keys.breakpoint then
|
local pb = require("persistent-breakpoints.api")
|
||||||
vim.keymap.set("n", keys.breakpoint, function()
|
local bindings = {
|
||||||
require("persistent-breakpoints.api").toggle_breakpoint()
|
{ keys.breakpoint, pb.toggle_breakpoint, "toggle breakpoint" },
|
||||||
end, { silent = true, desc = "tiny-debugger: toggle breakpoint" })
|
{ keys.cond_breakpoint, pb.set_conditional_breakpoint, "conditional breakpoint" },
|
||||||
end
|
{ keys.clear_breakpoints, pb.clear_all_breakpoints, "clear all breakpoints" },
|
||||||
if keys.cond_breakpoint then
|
}
|
||||||
vim.keymap.set("n", keys.cond_breakpoint, function()
|
for _, b in ipairs(bindings) do
|
||||||
require("persistent-breakpoints.api").set_conditional_breakpoint()
|
if b[1] then
|
||||||
end, { silent = true, desc = "tiny-debugger: conditional breakpoint" })
|
vim.keymap.set("n", b[1], b[2], { silent = true, desc = "tiny-debugger: " .. b[3] })
|
||||||
end
|
end
|
||||||
if keys.clear_breakpoints then
|
|
||||||
vim.keymap.set("n", keys.clear_breakpoints, function()
|
|
||||||
require("persistent-breakpoints.api").clear_all_breakpoints()
|
|
||||||
end, { silent = true, desc = "tiny-debugger: clear all breakpoints" })
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -54,16 +54,13 @@ M.defaults = {
|
|||||||
adapters = {},
|
adapters = {},
|
||||||
}
|
}
|
||||||
|
|
||||||
M.opts = vim.deepcopy(M.defaults)
|
M.opts = M.defaults
|
||||||
|
|
||||||
function M.merge(user_opts)
|
function M.merge(user_opts)
|
||||||
M.opts = vim.tbl_deep_extend("force", vim.deepcopy(M.defaults), user_opts or {})
|
M.opts = vim.tbl_deep_extend("force", vim.deepcopy(M.defaults), user_opts or {})
|
||||||
-- preserve false values for disabled keys (tbl_deep_extend drops them)
|
|
||||||
if user_opts and user_opts.keys then
|
if user_opts and user_opts.keys then
|
||||||
for k, v in pairs(user_opts.keys) do
|
for k, v in pairs(user_opts.keys) do
|
||||||
if v == false then
|
if v == false then M.opts.keys[k] = false end
|
||||||
M.opts.keys[k] = false
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
return M.opts
|
return M.opts
|
||||||
|
|||||||
+29
-50
@@ -5,50 +5,37 @@ local M = {}
|
|||||||
local win_id = nil
|
local win_id = nil
|
||||||
local buf_id = nil
|
local buf_id = nil
|
||||||
|
|
||||||
local key_labels = {
|
local key_info = {
|
||||||
continue = "Continue",
|
{ "continue", "Continue" },
|
||||||
step_over = "Step over",
|
{ "step_over", "Step over" },
|
||||||
step_into = "Step into",
|
{ "step_into", "Step into" },
|
||||||
step_out = "Step out",
|
{ "step_out", "Step out" },
|
||||||
run_to_cursor = "Run to cursor",
|
{ "run_to_cursor", "Run to cursor" },
|
||||||
breakpoint = "Toggle breakpoint",
|
{ "breakpoint", "Toggle breakpoint" },
|
||||||
cond_breakpoint = "Conditional breakpoint",
|
{ "cond_breakpoint", "Conditional breakpoint" },
|
||||||
clear_breakpoints = "Clear all breakpoints",
|
{ "clear_breakpoints", "Clear all breakpoints" },
|
||||||
watch = "Watch expression",
|
{ "watch", "Watch expression" },
|
||||||
hover = "Hover / inspect",
|
{ "hover", "Hover / inspect" },
|
||||||
eval = "Eval in REPL",
|
{ "eval", "Eval in REPL" },
|
||||||
terminate = "Quit debugging",
|
{ "terminate", "Quit debugging" },
|
||||||
restart = "Restart session",
|
{ "restart", "Restart session" },
|
||||||
help = "Show this help",
|
{ "help", "Show this help" },
|
||||||
}
|
}
|
||||||
|
|
||||||
-- ordered list of key names for consistent display
|
local function build_content()
|
||||||
local key_order = {
|
|
||||||
"continue", "step_over", "step_into", "step_out", "run_to_cursor",
|
|
||||||
"breakpoint", "cond_breakpoint", "clear_breakpoints", "watch", "hover", "eval",
|
|
||||||
"terminate", "restart", "help",
|
|
||||||
}
|
|
||||||
|
|
||||||
local function build_lines()
|
|
||||||
local lines = { " tiny-debugger keybindings", "" }
|
local lines = { " tiny-debugger keybindings", "" }
|
||||||
|
local width = #lines[1]
|
||||||
local keys = config.opts.keys
|
local keys = config.opts.keys
|
||||||
for _, name in ipairs(key_order) do
|
for _, pair in ipairs(key_info) do
|
||||||
local key = keys[name]
|
local key = keys[pair[1]]
|
||||||
if key then
|
if key then
|
||||||
local label = key_labels[name] or name
|
local line = string.format(" %s %s", key, pair[2])
|
||||||
table.insert(lines, string.format(" %s %s", key, label))
|
lines[#lines + 1] = line
|
||||||
|
if #line > width then width = #line end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
table.insert(lines, "")
|
lines[#lines + 1] = ""
|
||||||
return lines
|
return lines, width + 2
|
||||||
end
|
|
||||||
|
|
||||||
local function calc_width(lines)
|
|
||||||
local max = 0
|
|
||||||
for _, line in ipairs(lines) do
|
|
||||||
max = math.max(max, #line)
|
|
||||||
end
|
|
||||||
return max + 2
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.close()
|
function M.close()
|
||||||
@@ -65,37 +52,29 @@ end
|
|||||||
function M.open()
|
function M.open()
|
||||||
M.close()
|
M.close()
|
||||||
|
|
||||||
local lines = build_lines()
|
local lines, width = build_content()
|
||||||
local width = calc_width(lines)
|
|
||||||
local height = #lines
|
|
||||||
|
|
||||||
buf_id = vim.api.nvim_create_buf(false, true)
|
buf_id = vim.api.nvim_create_buf(false, true)
|
||||||
vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines)
|
vim.api.nvim_buf_set_lines(buf_id, 0, -1, false, lines)
|
||||||
vim.bo[buf_id].modifiable = false
|
vim.bo[buf_id].modifiable = false
|
||||||
vim.bo[buf_id].bufhidden = "wipe"
|
vim.bo[buf_id].bufhidden = "wipe"
|
||||||
|
|
||||||
local editor_width = vim.o.columns
|
|
||||||
local editor_height = vim.o.lines
|
|
||||||
|
|
||||||
win_id = vim.api.nvim_open_win(buf_id, false, {
|
win_id = vim.api.nvim_open_win(buf_id, false, {
|
||||||
relative = "editor",
|
relative = "editor",
|
||||||
anchor = "SE",
|
anchor = "SE",
|
||||||
row = editor_height - 2,
|
row = vim.o.lines - 2,
|
||||||
col = editor_width,
|
col = vim.o.columns,
|
||||||
width = width,
|
width = width,
|
||||||
height = height,
|
height = #lines,
|
||||||
style = "minimal",
|
style = "minimal",
|
||||||
border = "rounded",
|
border = "rounded",
|
||||||
focusable = false,
|
focusable = false,
|
||||||
noautocmd = true,
|
noautocmd = true,
|
||||||
})
|
})
|
||||||
|
|
||||||
-- auto-close on next keypress
|
|
||||||
vim.api.nvim_create_autocmd({ "CursorMoved", "InsertEnter", "CmdlineEnter" }, {
|
vim.api.nvim_create_autocmd({ "CursorMoved", "InsertEnter", "CmdlineEnter" }, {
|
||||||
once = true,
|
once = true,
|
||||||
callback = function()
|
callback = function() M.close() end,
|
||||||
M.close()
|
|
||||||
end,
|
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
local config = require("tiny-debugger.config")
|
local config = require("tiny-debugger.config")
|
||||||
local mode = require("tiny-debugger.mode")
|
local mode = require("tiny-debugger.mode")
|
||||||
local breakpoints = require("tiny-debugger.breakpoints")
|
|
||||||
local adapters = require("tiny-debugger.adapters")
|
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
function M.setup(opts)
|
function M.setup(opts)
|
||||||
config.merge(opts)
|
config.merge(opts)
|
||||||
breakpoints.setup()
|
require("tiny-debugger.breakpoints").setup()
|
||||||
adapters.setup()
|
require("tiny-debugger.adapters").setup()
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.toggle()
|
function M.toggle()
|
||||||
|
|||||||
+59
-53
@@ -1,16 +1,40 @@
|
|||||||
local config = require("tiny-debugger.config")
|
local config = require("tiny-debugger.config")
|
||||||
local help = require("tiny-debugger.help")
|
local help = require("tiny-debugger.help")
|
||||||
local ui = require("tiny-debugger.ui")
|
|
||||||
local virtual_text = require("tiny-debugger.virtual_text")
|
|
||||||
|
|
||||||
local M = {}
|
local M = {}
|
||||||
|
|
||||||
local active = false
|
local active = false
|
||||||
|
local active_buf = nil
|
||||||
local stashed_maps = {}
|
local stashed_maps = {}
|
||||||
|
|
||||||
|
-- one-time dap-ui and virtual-text initialization
|
||||||
|
local dapui_initialized = false
|
||||||
|
local vt_initialized = false
|
||||||
|
|
||||||
|
local function ensure_dapui()
|
||||||
|
if dapui_initialized then return end
|
||||||
|
dapui_initialized = true
|
||||||
|
require("dapui").setup(config.opts.dapui)
|
||||||
|
|
||||||
|
local dap = require("dap")
|
||||||
|
dap.listeners.after.event_terminated["tiny-debugger"] = function()
|
||||||
|
if active then M.exit() end
|
||||||
|
end
|
||||||
|
dap.listeners.after.event_exited["tiny-debugger"] = function()
|
||||||
|
if active then M.exit() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
local function ensure_vt()
|
||||||
|
if vt_initialized then return end
|
||||||
|
vt_initialized = true
|
||||||
|
require("nvim-dap-virtual-text").setup(config.opts.virtual_text)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- keymap stash/restore
|
||||||
|
|
||||||
local function stash_mapping(buf, lhs)
|
local function stash_mapping(buf, lhs)
|
||||||
local maps = vim.api.nvim_buf_get_keymap(buf, "n")
|
for _, map in ipairs(vim.api.nvim_buf_get_keymap(buf, "n")) do
|
||||||
for _, map in ipairs(maps) do
|
|
||||||
if map.lhs == lhs then
|
if map.lhs == lhs then
|
||||||
stashed_maps[lhs] = map
|
stashed_maps[lhs] = map
|
||||||
return
|
return
|
||||||
@@ -37,35 +61,24 @@ local function restore_mapping(buf, lhs)
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
-- actions table: key name → function
|
-- actions (set up once)
|
||||||
-- populated by other modules via M.register_action
|
|
||||||
local actions = {}
|
local actions = {}
|
||||||
|
local visual_actions = { watch = true, hover = true }
|
||||||
|
|
||||||
function M.register_action(name, fn)
|
local function setup_actions()
|
||||||
actions[name] = fn
|
if next(actions) then return end
|
||||||
end
|
|
||||||
|
|
||||||
-- default actions that map directly to dap calls
|
|
||||||
local function setup_default_actions()
|
|
||||||
local dap = require("dap")
|
local dap = require("dap")
|
||||||
actions.continue = function() dap.continue() end
|
actions.continue = function() dap.continue() end
|
||||||
actions.step_over = function() dap.step_over() end
|
actions.step_over = function() dap.step_over() end
|
||||||
actions.step_into = function() dap.step_into() end
|
actions.step_into = function() dap.step_into() end
|
||||||
actions.step_out = function() dap.step_out() end
|
actions.step_out = function() dap.step_out() end
|
||||||
actions.run_to_cursor = function() dap.run_to_cursor() end
|
actions.run_to_cursor = function() dap.run_to_cursor() end
|
||||||
actions.terminate = function()
|
actions.terminate = function() dap.terminate(); M.exit() end
|
||||||
dap.terminate()
|
|
||||||
M.exit()
|
|
||||||
end
|
|
||||||
actions.restart = function() dap.restart() end
|
actions.restart = function() dap.restart() end
|
||||||
actions.help = function()
|
actions.help = function()
|
||||||
if help.is_open() then
|
if help.is_open() then help.close() else help.open() end
|
||||||
help.close()
|
|
||||||
else
|
|
||||||
help.open()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
actions.watch = function()
|
actions.watch = function()
|
||||||
local expr
|
local expr
|
||||||
if vim.fn.mode() == "v" or vim.fn.mode() == "V" then
|
if vim.fn.mode() == "v" or vim.fn.mode() == "V" then
|
||||||
@@ -78,18 +91,11 @@ local function setup_default_actions()
|
|||||||
require("dapui").elements.watches.add(expr)
|
require("dapui").elements.watches.add(expr)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
actions.hover = function() require("dapui").eval() end
|
||||||
actions.hover = function()
|
actions.eval = function() require("dapui").float_element("repl") end
|
||||||
require("dapui").eval()
|
|
||||||
end
|
|
||||||
|
|
||||||
actions.eval = function()
|
|
||||||
require("dapui").float_element("repl")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
-- actions that also get visual mode mappings
|
-- keymap lifecycle
|
||||||
local visual_actions = { watch = true, hover = true }
|
|
||||||
|
|
||||||
local function set_keymaps(buf)
|
local function set_keymaps(buf)
|
||||||
local keys = config.opts.keys
|
local keys = config.opts.keys
|
||||||
@@ -118,6 +124,8 @@ local function clear_keymaps(buf)
|
|||||||
stashed_maps = {}
|
stashed_maps = {}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- cursor highlight
|
||||||
|
|
||||||
local saved_cursor_hl = nil
|
local saved_cursor_hl = nil
|
||||||
|
|
||||||
local function set_cursor_highlight()
|
local function set_cursor_highlight()
|
||||||
@@ -126,25 +134,26 @@ local function set_cursor_highlight()
|
|||||||
end
|
end
|
||||||
|
|
||||||
local function restore_cursor_highlight()
|
local function restore_cursor_highlight()
|
||||||
if saved_cursor_hl then
|
vim.api.nvim_set_hl(0, "Cursor", saved_cursor_hl or {})
|
||||||
vim.api.nvim_set_hl(0, "Cursor", saved_cursor_hl)
|
saved_cursor_hl = nil
|
||||||
saved_cursor_hl = nil
|
|
||||||
else
|
|
||||||
vim.api.nvim_set_hl(0, "Cursor", {})
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
-- public API
|
||||||
|
|
||||||
function M.enter()
|
function M.enter()
|
||||||
if active then return end
|
if active then return end
|
||||||
active = true
|
active = true
|
||||||
|
setup_actions()
|
||||||
|
|
||||||
setup_default_actions()
|
active_buf = vim.api.nvim_get_current_buf()
|
||||||
|
set_keymaps(active_buf)
|
||||||
local buf = vim.api.nvim_get_current_buf()
|
|
||||||
set_keymaps(buf)
|
|
||||||
set_cursor_highlight()
|
set_cursor_highlight()
|
||||||
ui.open()
|
|
||||||
virtual_text.enable()
|
ensure_dapui()
|
||||||
|
require("dapui").open()
|
||||||
|
|
||||||
|
ensure_vt()
|
||||||
|
require("nvim-dap-virtual-text").enable()
|
||||||
|
|
||||||
if config.opts.help_on_enter then
|
if config.opts.help_on_enter then
|
||||||
help.open()
|
help.open()
|
||||||
@@ -155,20 +164,17 @@ function M.exit()
|
|||||||
if not active then return end
|
if not active then return end
|
||||||
active = false
|
active = false
|
||||||
|
|
||||||
local buf = vim.api.nvim_get_current_buf()
|
clear_keymaps(active_buf or vim.api.nvim_get_current_buf())
|
||||||
clear_keymaps(buf)
|
active_buf = nil
|
||||||
restore_cursor_highlight()
|
restore_cursor_highlight()
|
||||||
help.close()
|
help.close()
|
||||||
ui.close()
|
|
||||||
virtual_text.disable()
|
if dapui_initialized then require("dapui").close() end
|
||||||
|
if vt_initialized then require("nvim-dap-virtual-text").disable() end
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.toggle()
|
function M.toggle()
|
||||||
if active then
|
if active then M.exit() else M.enter() end
|
||||||
M.exit()
|
|
||||||
else
|
|
||||||
M.enter()
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
function M.is_active()
|
function M.is_active()
|
||||||
|
|||||||
@@ -1,42 +0,0 @@
|
|||||||
local config = require("tiny-debugger.config")
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local initialized = false
|
|
||||||
|
|
||||||
local function ensure_setup()
|
|
||||||
if initialized then return end
|
|
||||||
initialized = true
|
|
||||||
|
|
||||||
local dapui = require("dapui")
|
|
||||||
dapui.setup(config.opts.dapui)
|
|
||||||
|
|
||||||
-- auto-exit debug mode when session ends
|
|
||||||
local dap = require("dap")
|
|
||||||
local mode = require("tiny-debugger.mode")
|
|
||||||
|
|
||||||
dap.listeners.after.event_terminated["tiny-debugger"] = function()
|
|
||||||
if mode.is_active() then
|
|
||||||
mode.exit()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
dap.listeners.after.event_exited["tiny-debugger"] = function()
|
|
||||||
if mode.is_active() then
|
|
||||||
mode.exit()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.open()
|
|
||||||
ensure_setup()
|
|
||||||
require("dapui").open()
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.close()
|
|
||||||
if initialized then
|
|
||||||
require("dapui").close()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
local config = require("tiny-debugger.config")
|
|
||||||
|
|
||||||
local M = {}
|
|
||||||
|
|
||||||
local initialized = false
|
|
||||||
|
|
||||||
function M.setup()
|
|
||||||
if initialized then return end
|
|
||||||
initialized = true
|
|
||||||
require("nvim-dap-virtual-text").setup(config.opts.virtual_text)
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.enable()
|
|
||||||
M.setup()
|
|
||||||
require("nvim-dap-virtual-text").enable()
|
|
||||||
end
|
|
||||||
|
|
||||||
function M.disable()
|
|
||||||
if initialized then
|
|
||||||
require("nvim-dap-virtual-text").disable()
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
return M
|
|
||||||
@@ -1,22 +1,21 @@
|
|||||||
-- tiny-debugger.nvim — entry point
|
-- tiny-debugger.nvim — entry point
|
||||||
|
|
||||||
-- require Neovim 0.12+
|
|
||||||
if vim.fn.has("nvim-0.12") ~= 1 then
|
if vim.fn.has("nvim-0.12") ~= 1 then
|
||||||
vim.notify("tiny-debugger.nvim requires Neovim >= 0.12", vim.log.levels.ERROR)
|
vim.notify("tiny-debugger.nvim requires Neovim >= 0.12", vim.log.levels.ERROR)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
-- load dependencies via vim.pack
|
|
||||||
vim.pack.add({ "mfussenegger/nvim-dap" })
|
vim.pack.add({ "mfussenegger/nvim-dap" })
|
||||||
vim.pack.add({ "rcarriga/nvim-dap-ui" })
|
vim.pack.add({ "rcarriga/nvim-dap-ui" })
|
||||||
vim.pack.add({ "nvim-neotest/nvim-nio" })
|
vim.pack.add({ "nvim-neotest/nvim-nio" })
|
||||||
vim.pack.add({ "theHamsta/nvim-dap-virtual-text" })
|
vim.pack.add({ "theHamsta/nvim-dap-virtual-text" })
|
||||||
vim.pack.add({ "Weissle/persistent-breakpoints.nvim" })
|
vim.pack.add({ "Weissle/persistent-breakpoints.nvim" })
|
||||||
|
|
||||||
-- initialize with defaults (user can call setup() to override)
|
-- defer setup so user has time to call setup() with overrides
|
||||||
require("tiny-debugger").setup()
|
vim.schedule(function()
|
||||||
|
require("tiny-debugger").setup()
|
||||||
|
end)
|
||||||
|
|
||||||
-- toggle debug mode
|
|
||||||
vim.keymap.set("n", "<leader>d", function()
|
vim.keymap.set("n", "<leader>d", function()
|
||||||
require("tiny-debugger").toggle()
|
require("tiny-debugger").toggle()
|
||||||
end, { silent = true, desc = "tiny-debugger: toggle debug mode" })
|
end, { silent = true, desc = "tiny-debugger: toggle debug mode" })
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
local M = {}
|
||||||
|
|
||||||
|
function M.assert_eq(a, b, msg)
|
||||||
|
if a ~= b then
|
||||||
|
error(string.format("FAIL: %s — expected %s, got %s", msg, vim.inspect(b), vim.inspect(a)))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
return M
|
||||||
@@ -1,2 +1,3 @@
|
|||||||
-- Minimal init for testing tiny-debugger.nvim
|
-- Minimal init for testing tiny-debugger.nvim
|
||||||
vim.opt.runtimepath:prepend(vim.fn.getcwd())
|
vim.opt.runtimepath:prepend(vim.fn.getcwd())
|
||||||
|
package.path = vim.fn.getcwd() .. "/tests/?.lua;" .. package.path
|
||||||
+11
-22
@@ -1,41 +1,30 @@
|
|||||||
-- tests for adapter loader and path resolution
|
local assert_eq = require("helpers").assert_eq
|
||||||
local loader = require("tiny-debugger.adapters.init")
|
local loader = require("tiny-debugger.adapters.init")
|
||||||
|
local dap = require("dap")
|
||||||
|
|
||||||
local function assert_eq(a, b, msg)
|
-- find_executable returns nil for non-existent binaries
|
||||||
if a ~= b then
|
|
||||||
error(string.format("FAIL: %s — expected %s, got %s", msg, vim.inspect(b), vim.inspect(a)))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- test 1: find_executable returns nil for non-existent binaries
|
|
||||||
assert_eq(loader.find_executable({ "totally-fake-binary-xyz" }), nil, "non-existent binary returns nil")
|
assert_eq(loader.find_executable({ "totally-fake-binary-xyz" }), nil, "non-existent binary returns nil")
|
||||||
|
|
||||||
-- test 2: find_executable finds common system binaries
|
-- find_executable finds common system binaries
|
||||||
local found = loader.find_executable({ "sh" })
|
assert_eq(loader.find_executable({ "sh" }) ~= nil, true, "find_executable finds sh")
|
||||||
assert_eq(found ~= nil, true, "find_executable finds sh")
|
|
||||||
|
|
||||||
-- test 3: python adapter module loads and has register function
|
-- all adapter modules load and have register()
|
||||||
local py = require("tiny-debugger.adapters.python")
|
for _, name in ipairs({ "python", "go", "c", "javascript" }) do
|
||||||
assert_eq(type(py.register), "function", "python adapter has register()")
|
assert_eq(type(require("tiny-debugger.adapters." .. name).register), "function", name .. " has register()")
|
||||||
|
end
|
||||||
|
|
||||||
-- test 4: go adapter module loads and has register function
|
-- register doesn't override existing adapter
|
||||||
local go = require("tiny-debugger.adapters.go")
|
|
||||||
assert_eq(type(go.register), "function", "go adapter has register()")
|
|
||||||
|
|
||||||
-- test 5: register doesn't override existing adapter
|
|
||||||
local dap = require("dap")
|
|
||||||
dap.adapters.test_adapter = { type = "executable", command = "test" }
|
dap.adapters.test_adapter = { type = "executable", command = "test" }
|
||||||
loader.register(dap, "test_adapter", { type = "executable", command = "new" }, {}, {})
|
loader.register(dap, "test_adapter", { type = "executable", command = "new" }, {}, {})
|
||||||
assert_eq(dap.adapters.test_adapter.command, "test", "register doesn't override existing")
|
assert_eq(dap.adapters.test_adapter.command, "test", "register doesn't override existing")
|
||||||
dap.adapters.test_adapter = nil
|
dap.adapters.test_adapter = nil
|
||||||
|
|
||||||
-- test 6: register sets adapter and configs for new adapter
|
-- register sets adapter and configs for new adapter
|
||||||
loader.register(dap, "test_new", { type = "executable", command = "test" }, {
|
loader.register(dap, "test_new", { type = "executable", command = "test" }, {
|
||||||
{ type = "test_new", request = "launch", name = "test" },
|
{ type = "test_new", request = "launch", name = "test" },
|
||||||
}, { "testlang" })
|
}, { "testlang" })
|
||||||
assert_eq(dap.adapters.test_new.command, "test", "new adapter registered")
|
assert_eq(dap.adapters.test_new.command, "test", "new adapter registered")
|
||||||
assert_eq(#dap.configurations.testlang, 1, "configs registered for filetype")
|
assert_eq(#dap.configurations.testlang, 1, "configs registered for filetype")
|
||||||
-- cleanup
|
|
||||||
dap.adapters.test_new = nil
|
dap.adapters.test_new = nil
|
||||||
dap.configurations.testlang = nil
|
dap.configurations.testlang = nil
|
||||||
|
|
||||||
|
|||||||
@@ -1,22 +1,14 @@
|
|||||||
-- tests for persistent breakpoints
|
local assert_eq = require("helpers").assert_eq
|
||||||
local td = require("tiny-debugger")
|
local td = require("tiny-debugger")
|
||||||
td.setup()
|
td.setup()
|
||||||
|
|
||||||
local function assert_eq(a, b, msg)
|
-- global x mapping exists
|
||||||
if a ~= b then
|
assert_eq(vim.fn.maparg("x", "n", false, true).desc, "tiny-debugger: toggle breakpoint", "global x mapping")
|
||||||
error(string.format("FAIL: %s — expected %s, got %s", msg, vim.inspect(b), vim.inspect(a)))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- test 1: global x mapping exists
|
-- global X mapping exists
|
||||||
local maparg = vim.fn.maparg("x", "n", false, true)
|
assert_eq(vim.fn.maparg("X", "n", false, true).desc, "tiny-debugger: conditional breakpoint", "global X mapping")
|
||||||
assert_eq(maparg.desc, "tiny-debugger: toggle breakpoint", "global x mapping exists")
|
|
||||||
|
|
||||||
-- test 2: global X mapping exists
|
-- persistent-breakpoints API is callable
|
||||||
maparg = vim.fn.maparg("X", "n", false, true)
|
|
||||||
assert_eq(maparg.desc, "tiny-debugger: conditional breakpoint", "global X mapping exists")
|
|
||||||
|
|
||||||
-- test 3: persistent-breakpoints API is callable
|
|
||||||
local api = require("persistent-breakpoints.api")
|
local api = require("persistent-breakpoints.api")
|
||||||
assert_eq(type(api.toggle_breakpoint), "function", "toggle_breakpoint is a function")
|
assert_eq(type(api.toggle_breakpoint), "function", "toggle_breakpoint is a function")
|
||||||
assert_eq(type(api.set_conditional_breakpoint), "function", "set_conditional_breakpoint is a function")
|
assert_eq(type(api.set_conditional_breakpoint), "function", "set_conditional_breakpoint is a function")
|
||||||
|
|||||||
+6
-13
@@ -1,36 +1,29 @@
|
|||||||
-- tests for config module
|
local assert_eq = require("helpers").assert_eq
|
||||||
local config = require("tiny-debugger.config")
|
local config = require("tiny-debugger.config")
|
||||||
|
|
||||||
local function assert_eq(a, b, msg)
|
-- defaults unchanged after empty merge
|
||||||
if a ~= b then
|
|
||||||
error(string.format("FAIL: %s — expected %s, got %s", msg, vim.inspect(b), vim.inspect(a)))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- test 1: defaults unchanged after empty merge
|
|
||||||
local opts = config.merge({})
|
local opts = config.merge({})
|
||||||
assert_eq(opts.keys.continue, "c", "default continue key")
|
assert_eq(opts.keys.continue, "c", "default continue key")
|
||||||
assert_eq(opts.help_on_enter, true, "default help_on_enter")
|
assert_eq(opts.help_on_enter, true, "default help_on_enter")
|
||||||
assert_eq(opts.builtin_adapters, true, "default builtin_adapters")
|
assert_eq(opts.builtin_adapters, true, "default builtin_adapters")
|
||||||
assert_eq(#opts.dapui.layouts, 2, "default layout count")
|
|
||||||
|
|
||||||
-- test 2: partial key override
|
-- partial key override
|
||||||
opts = config.merge({ keys = { continue = "g" } })
|
opts = config.merge({ keys = { continue = "g" } })
|
||||||
assert_eq(opts.keys.continue, "g", "overridden continue key")
|
assert_eq(opts.keys.continue, "g", "overridden continue key")
|
||||||
assert_eq(opts.keys.step_over, "s", "preserved step_over key")
|
assert_eq(opts.keys.step_over, "s", "preserved step_over key")
|
||||||
assert_eq(opts.keys.terminate, "q", "preserved terminate key")
|
assert_eq(opts.keys.terminate, "q", "preserved terminate key")
|
||||||
|
|
||||||
-- test 3: disable a key with false
|
-- disable a key with false
|
||||||
opts = config.merge({ keys = { continue = false } })
|
opts = config.merge({ keys = { continue = false } })
|
||||||
assert_eq(opts.keys.continue, false, "disabled continue key")
|
assert_eq(opts.keys.continue, false, "disabled continue key")
|
||||||
assert_eq(opts.keys.step_over, "s", "preserved step_over after disable")
|
assert_eq(opts.keys.step_over, "s", "preserved step_over after disable")
|
||||||
|
|
||||||
-- test 4: override help_on_enter
|
-- override help_on_enter
|
||||||
opts = config.merge({ help_on_enter = false })
|
opts = config.merge({ help_on_enter = false })
|
||||||
assert_eq(opts.help_on_enter, false, "overridden help_on_enter")
|
assert_eq(opts.help_on_enter, false, "overridden help_on_enter")
|
||||||
assert_eq(opts.keys.continue, "c", "keys preserved after help override")
|
assert_eq(opts.keys.continue, "c", "keys preserved after help override")
|
||||||
|
|
||||||
-- test 5: merge doesn't mutate defaults
|
-- merge doesn't mutate defaults
|
||||||
config.merge({ keys = { continue = "z" } })
|
config.merge({ keys = { continue = "z" } })
|
||||||
assert_eq(config.defaults.keys.continue, "c", "defaults not mutated")
|
assert_eq(config.defaults.keys.continue, "c", "defaults not mutated")
|
||||||
|
|
||||||
|
|||||||
+7
-21
@@ -1,40 +1,26 @@
|
|||||||
-- tests for help box
|
local assert_eq = require("helpers").assert_eq
|
||||||
local help = require("tiny-debugger.help")
|
local help = require("tiny-debugger.help")
|
||||||
local config = require("tiny-debugger.config")
|
local config = require("tiny-debugger.config")
|
||||||
|
|
||||||
local function assert_eq(a, b, msg)
|
-- open creates a valid window
|
||||||
if a ~= b then
|
|
||||||
error(string.format("FAIL: %s — expected %s, got %s", msg, vim.inspect(b), vim.inspect(a)))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- test 1: open creates a valid window
|
|
||||||
help.open()
|
help.open()
|
||||||
assert_eq(help.is_open(), true, "help window is open")
|
assert_eq(help.is_open(), true, "help window is open")
|
||||||
|
|
||||||
-- test 2: close removes the window
|
-- close removes the window
|
||||||
help.close()
|
help.close()
|
||||||
assert_eq(help.is_open(), false, "help window is closed")
|
assert_eq(help.is_open(), false, "help window is closed")
|
||||||
|
|
||||||
-- test 3: remapped key shows in help content
|
-- remapped key shows in help content
|
||||||
config.merge({ keys = { continue = "g" } })
|
config.merge({ keys = { continue = "g" } })
|
||||||
help.open()
|
help.open()
|
||||||
-- read the buffer content
|
|
||||||
local wins = vim.api.nvim_list_wins()
|
|
||||||
local found_g = false
|
local found_g = false
|
||||||
for _, w in ipairs(wins) do
|
for _, w in ipairs(vim.api.nvim_list_wins()) do
|
||||||
local buf = vim.api.nvim_win_get_buf(w)
|
for _, line in ipairs(vim.api.nvim_buf_get_lines(vim.api.nvim_win_get_buf(w), 0, -1, false)) do
|
||||||
local lines = vim.api.nvim_buf_get_lines(buf, 0, -1, false)
|
if line:find("g%s+Continue") then found_g = true end
|
||||||
for _, line in ipairs(lines) do
|
|
||||||
if line:find("g%s+Continue") then
|
|
||||||
found_g = true
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
assert_eq(found_g, true, "remapped key 'g' appears in help")
|
assert_eq(found_g, true, "remapped key 'g' appears in help")
|
||||||
help.close()
|
help.close()
|
||||||
|
|
||||||
-- reset config
|
|
||||||
config.merge({})
|
config.merge({})
|
||||||
|
|
||||||
print("ALL HELP TESTS PASSED")
|
print("ALL HELP TESTS PASSED")
|
||||||
+2
-9
@@ -1,15 +1,8 @@
|
|||||||
-- tests for keybinding stash/restore
|
local assert_eq = require("helpers").assert_eq
|
||||||
local td = require("tiny-debugger")
|
local td = require("tiny-debugger")
|
||||||
|
|
||||||
local function assert_eq(a, b, msg)
|
|
||||||
if a ~= b then
|
|
||||||
error(string.format("FAIL: %s — expected %s, got %s", msg, vim.inspect(b), vim.inspect(a)))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
-- set a custom mapping for 'c'
|
-- set a custom mapping for 'c'
|
||||||
local custom_called = false
|
vim.keymap.set("n", "c", function() end, { buffer = 0, desc = "custom c" })
|
||||||
vim.keymap.set("n", "c", function() custom_called = true end, { buffer = 0, desc = "custom c" })
|
|
||||||
|
|
||||||
-- verify custom mapping exists
|
-- verify custom mapping exists
|
||||||
local maparg = vim.fn.maparg("c", "n", false, true)
|
local maparg = vim.fn.maparg("c", "n", false, true)
|
||||||
|
|||||||
+13
-23
@@ -1,42 +1,32 @@
|
|||||||
-- tests for debug mode enter/exit
|
local assert_eq = require("helpers").assert_eq
|
||||||
local td = require("tiny-debugger")
|
local td = require("tiny-debugger")
|
||||||
local mode = require("tiny-debugger.mode")
|
|
||||||
|
|
||||||
local function assert_eq(a, b, msg)
|
|
||||||
if a ~= b then
|
|
||||||
error(string.format("FAIL: %s — expected %s, got %s", msg, vim.inspect(b), vim.inspect(a)))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
local function has_buf_map(key)
|
local function has_buf_map(key)
|
||||||
local maps = vim.api.nvim_buf_get_keymap(0, "n")
|
for _, m in ipairs(vim.api.nvim_buf_get_keymap(0, "n")) do
|
||||||
for _, m in ipairs(maps) do
|
|
||||||
if m.lhs == key then return true end
|
if m.lhs == key then return true end
|
||||||
end
|
end
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
-- test 1: starts inactive
|
-- starts inactive
|
||||||
assert_eq(td.active(), false, "starts inactive")
|
assert_eq(td.active(), false, "starts inactive")
|
||||||
|
|
||||||
-- test 2: toggle enters debug mode
|
-- toggle enters debug mode
|
||||||
td.toggle()
|
td.toggle()
|
||||||
assert_eq(td.active(), true, "active after toggle")
|
assert_eq(td.active(), true, "active after toggle")
|
||||||
|
|
||||||
-- test 3: keymaps are set
|
-- keymaps are set
|
||||||
assert_eq(has_buf_map("c"), true, "continue key mapped")
|
for _, key in ipairs({ "c", "s", "d", "r", "q" }) do
|
||||||
assert_eq(has_buf_map("s"), true, "step_over key mapped")
|
assert_eq(has_buf_map(key), true, key .. " key mapped")
|
||||||
assert_eq(has_buf_map("d"), true, "step_into key mapped")
|
end
|
||||||
assert_eq(has_buf_map("r"), true, "step_out key mapped")
|
|
||||||
assert_eq(has_buf_map("q"), true, "terminate key mapped")
|
|
||||||
|
|
||||||
-- test 4: toggle exits debug mode
|
-- toggle exits debug mode
|
||||||
td.toggle()
|
td.toggle()
|
||||||
assert_eq(td.active(), false, "inactive after second toggle")
|
assert_eq(td.active(), false, "inactive after second toggle")
|
||||||
|
|
||||||
-- test 5: keymaps are removed
|
-- keymaps are removed
|
||||||
assert_eq(has_buf_map("c"), false, "continue key removed")
|
for _, key in ipairs({ "c", "s", "d", "r", "q" }) do
|
||||||
assert_eq(has_buf_map("s"), false, "step_over key removed")
|
assert_eq(has_buf_map(key), false, key .. " key removed")
|
||||||
assert_eq(has_buf_map("q"), false, "terminate key removed")
|
end
|
||||||
|
|
||||||
print("ALL MODE TESTS PASSED")
|
print("ALL MODE TESTS PASSED")
|
||||||
Reference in new issue
Block a user