doc/debugging.txt — rendered from the plugin's own vimdoc
*debugging.txt* One :Debug command for Neovim debugging *debugging.nvim* Author: Stefan Bartl Version: 0.2.0
CONTENTS
1. Introduction .............. |debugging-intro| 2. Requirements .............. |debugging-requirements| 3. Installation .............. |debugging-installation| 4. Configuration ............. |debugging-config| 5. The :Debug command ........ |:Debug| 5.1 Categories ............ |debugging-categories| 6. Diagnosing UI freezes ..... |debugging-proc| 7. Tab completion ............ |debugging-completion| 8. Health check .............. |debugging-health| 9. Architecture .............. |debugging-architecture|
1. INTRODUCTION
debugging.nvim provides one :Debug {category} {action} command that groups
every debugging tool: message/Noice views, buffer/tab/window reports, autocmd
inspection (runtime, a Tree-sitter static source audit, and a combined
sources-vs-runtime view), buffer/window/tab/cursor/variable inspection, a
terminal keylogger, indent diagnostics, markdown inline-highlight debugging,
UI-freeze diagnosis (blocking-call tracing + an external process-tree watcher),
a startup-time benchmark, and an opt-in Neo-tree safety bridge.
Depends on lib.nvim (a deliberate shared dependency).
2. REQUIREMENTS
- Neovim 0.9 or later
- lib.nvim
- Optional: clipboard provider (messages capture), noice.nvim (noice views),
Tree-sitter (markdown / indent diagnostics), which-key.nvim (groups the
views keymap prefix)
3. INSTALLATION
cmd = "Debug"lazy-loads the plugin on first use of the :Debug command — noeventorlazy = falseneeded. lazy.nvim:
{
"StefanBartl/debugging.nvim",
cmd = "Debug",
dependencies = { "StefanBartl/lib.nvim" },
opts = {},
}
packer.nvim:
use({
"StefanBartl/debugging.nvim",
requires = { "StefanBartl/lib.nvim" },
cmd = "Debug",
config = function()
require("debugging").setup({})
end,
})
4. CONFIGURATION
require("debugging").setup({
features = {
views = true,
reports = true,
autocmds = true,
tools = true,
terminals = true,
nvim_options = true,
markdown = true,
module_reload = true, -- :Debug module reload
neotree = false, -- opt-in, config-specific
proc_trace = true, -- :Debug proc start|stop|status|log|watch
performance = true, -- :Debug performance startup
},
terminals = {
keylogger = { logfile = nil }, -- append recorded keys here; nil = notify only
},
neotree = {
quarantine = "config.neotree.watcher_quarantine",
safety = "config.neotree.safety",
},
views = {
keymaps = { enable = true, prefix = "<lt>" },
autocmds = { enable = true, group_name = "DebugViewsAuto", auto_refresh = true },
timings = { delay_messages_ms = 30, delay_noice_ms = 50, retry_delay_ms = 60, attempts = 3,
capture_timeout_ms = 500 },
capture = true,
output_dir = nil,
},
command = "Debug",
overview = "float", -- how `:Debug` (no args) renders: "float" or "notify"
})
A bare all = true enables every feature category.
features
Per-category enable flags. A disabled category has no route at all — it is hidden from completion and, if typed anyway, rejected with a generic "unknown subcommand" error (rather than a message naming the specific feature flag to enable).
terminals
keylogger.logfile— path to append recorded keys to (~/env vars expand). nil means notify only.:Debug keylogger start {path}overrides it per session.
neotree
Injectable Neo-tree bridge targets (opt-in via features.neotree). Each ofquarantine/safetyis a module name torequire, or a table used directly — so the bridge works without the privateconfig.neotree.*layout.
views
Keymaps (default prefix "<lt>"), auto-refresh autocmds, capture timings and output directory for the message/Noice views.
command
Name of the unified user command. Default: "Debug".
overview
How :Debug with no arguments renders the category list: "float" (a
scrollable floating window, q/<Esc> to close; the default) or "notify" (a
single notification). Falls back to a notification when no UI is attached.
validation
Options are checked before they are merged. An unknown key — at the top level or insidefeatures,terminals,neotree,views— is ignored with a warning naming the nearest known key, and an option table given as something other than a table falls back to that table's default. Both are listed again under|:checkhealth|debugging.
5. THE :Debug COMMAND
:Debug [category] [action] [args]
With no arguments,:Debugshows an overview of the enabled categories — in a scrollable floating window by default (q/<Esc> closes it), or as a single notification whenoverview = "notify". An unknown or disabled category yields an error/notification.
5.1 Categories
messages
showOpen the :messages window.captureCapture :messages to a file and the clipboard.clearClose all debug windows.
noice
allShow all Noice messages.errorsShow Noice errors.
report
bufBuffer report to :messages.tabTab report.win [id]Window report (optionally for a specific window id).
autocmds
runtime [event] [pattern]Live view via nvim_get_autocmds().sources [args]Static source-code audit of nvim_create_autocmd call sites. Uses a Tree-sitter parser when the Lua parser is available (robust against multi-line/nested calls), falling back to a text parser. Args: event=, sort= (source|event|frequency), impl=, summary=, freq=, root=, refresh=, qf=. Results are cached per root for a few seconds; refresh=true forces a rescan; qf=true sends path:line to the quickfix list (for jump-to-definition) instead of the scratch report.all [args]Combined view: for each event, where it is DEFINED (sources) vs currently REGISTERED (runtime), plus a diff of events registered at runtime with no source found (typically plugin-defined). Args: root=, refresh=, event=.
inspect
buffer [bufnr]Inspect buffer-scoped options and state.window [winid]Inspect window-scoped options and state.tab [tabnr]Inspect a tab page (1-based number) — its windows and the buffers they show.
cursor
state Print cursor / window / buffer state.
dump
[varname] Recursively dump a global Lua variable, or the word under the
cursor when no name is given.
keylogger
start [file]Log keys pressed in the current terminal buffer. With a file argument (orterminals.keylogger.logfileset) the keys are also appended to that file for later review.stopStop logging. Keys are observed via|vim.on_key()|, not consumed: what you type still reaches the terminal. Recorded in readable form (|keytrans()|), so<Esc>and<C-c>appear as such rather than as raw bytes. Leaving the logged buffer stops the logger. WARNING: every key in that terminal is captured, including what you type at asudo,sshorgpgprompt. A password prompt is just more keystrokes here -- the masking you see on screen happens after this reads them. The logfile is created 0600; notify-only mode is not safer, it puts the same keys in|:messages|. Stop the logger before authenticating.
indent
showPrint indentation-related buffer options.treesitter [true|false]Prefer Tree-sitter indent (disable cindent/ smartindent), or restore withfalse.
markdown
inlineGather markdown inline-highlight debug information.logOpen the most recent debug log.
module
reloadClearpackage.loaded(andvim.loaderwhen available) for the Lua module that corresponds to the current buffer, then re-require it. The buffer must be a .lua file inside alua/directory on the runtimepath; the module name is derived from the file path. Example: while editinglua/myplugin/feature.lua
:Debug module reload " → reloads "myplugin.feature"
proc
start [threshold_ms]Wrap vim.fn.system/systemlist, vim.system, and vim.fn.jobstart; log duration + traceback for calls at/above threshold_ms (default 200). Delegates to lib.nvim.system.proc_trace.stopRestore the original functions.statusPrint whether tracing is active + the log path.logOpen the log in a scratch tab.watch [seconds](Windows only) Open a terminal split running the bundled scripts/watch-nvim-procs.ps1: polls the Win32 process tree and reports every child process of this Neovim instance with start time + lifetime. Catches spawns proc_trace cannot see (LSP servers, any C-internal spawn).
performance
startup [runs]Benchmark startup time. Spawns a fresh headless Neovim with your real config under--startuptime, parses the log, and reports the total startup time plus the slowest sourced scripts (the practical "what loads at startup / which lazy-loads fire" overview). A run count averages the total over several launches (default 1, max 20). Each measurement runs in its own subprocess and does not affect the current session. neotree ~ (opt-in; needsfeatures.neotree = true)statusexitrestartbackup-listbackup-cleandryrun-toggledryrun-reportqueue-statusqueue-clearBridges to a user-specific Neo-tree safety layer (injectable via theneotreeconfig); degrades gracefully with a notification when that layer is absent.
health
Run |:checkhealth| debugging.
6. DIAGNOSING UI FREEZES
:Debug procanswers "what exactly is blocking Neovim right now?" — for the class of freeze caused by a slow or hung external process (a Git shell-out down an unresponsive network share, an LSP tool waiting on a subprocess, a plugin spawning far more processes than expected). Two complementary layers: 1.proc start/stop/status/logwraps the process-spawning Lua APIs (seeprocunder |debugging-categories| above) and logs slow calls with a full stack traceback — which plugin/config line triggered it. 2.proc watch [seconds](Windows only) drives an external PowerShell script that polls the Win32 process tree directly, catching spawns that never go throughvim.fn.*/vim.system(LSP servers, etc.). Typical session:
:Debug proc start 200
" ... reproduce the freeze ...
:Debug proc stop
:Debug proc log
Or to see every child process regardless of how it was spawned:
:Debug proc watch 60
" ... reproduce the freeze in this same Neovim instance ...
" Ctrl+C in the terminal split once the freeze is over → lifetime summary
LIMITS
proc_traceonly sees calls through the exact API tables it wraps — a plugin that cached a local reference (local system = vim.fn.system) beforeproc startran bypasses it; start as early as possible (ideally the first line of init.lua) to minimize this.proc watchhas no bundled equivalent on Linux/macOS;pstree -p <nvim_pid>or awatch-loopedpscovers the same ground there.
7. TAB COMPLETION
:Debug completes context-sensitively:
:Debug <Tab> categories
:Debug report <Tab> buf tab win
:Debug inspect <Tab> buffer window tab
:Debug autocmds <Tab> runtime sources all
:Debug autocmds sources <Tab> event= sort= impl= summary= freq= root= refresh= qf=
:Debug autocmds sources event=Buf<Tab> matching event names
:Debug indent treesitter <Tab> true false
:Debug module <Tab> reload
:Debug proc <Tab> start stop status log watch
:Debug performance <Tab> startup
8. HEALTH CHECK
:checkhealth debugging
Checks Neovim version, lib.nvim modules per feature, clipboard providers, optional Tree-sitter / Noice / which-key, write permissions, vim.loader availability (module_reload), the opt-in Neo-tree bridge, and (proc_trace) the bundled watcher script + PowerShell availability on Windows.
9. ARCHITECTURE
docs/BINDINGS.md Cheatsheet: every keymap, :Debug action, autocmd
scripts/watch-nvim-procs.ps1 Bundled external process-tree watcher (Windows)
plugin/debugging.lua Load guard
lua/debugging/
init.lua setup() — feature gating + bindings registration
@types/init.lua LuaLS type definitions
config/DEFAULTS.lua Immutable defaults
config/init.lua Merge + access to active config
commands.lua :Debug dispatch + two-level completion (logic only)
health.lua checkhealth provider
bindings/ Every user-facing trigger — registration only
@types/init.lua Dbg.ActionFn, Dbg.Bindings.RegistryEntry
init.lua orchestrates usercmds/keymaps/autocmds
usercmds.lua registers the single :Debug user command
keymaps.lua views subsystem normal-mode keymaps (declared
through lib.nvim's keymap registry, which also
supplies the optional which-key group label)
autocmds.lua views subsystem auto-refresh + close-window autocmds
views/ messages/Noice capture, display; timings/keymap/autocmd config
actions/reports.lua buf/tab/win reports
actions/module_reload.lua reload Lua module of current buffer
actions/neotree_safety.lua opt-in Neo-tree bridge (pcall-guarded)
autocmds/@types/init.lua Dbg.Autocmds.SourceItem/SourceOpts
autocmds/runtime.lua live nvim_get_autocmds view
autocmds/sources.lua static source-code audit (Tree-sitter + text
fallback; cached per root; sources-vs-runtime all)
tools/buffer_inspector/ buffer / window / tab option/state inspection
tools/cursor/state.lua cursor/window/buffer state
tools/vardump/ recursive Lua value dump
tools/startup.lua :Debug performance startup — --startuptime benchmark
tools/proc_trace.lua :Debug proc — freeze diagnosis (delegates to
lib.nvim.system.proc_trace; drives the bundled
watcher script for proc watch)
terminals/keylogger.lua terminal keylogger
nvim_options/indent_helpers.lua indent diagnostics
markdown/inline_debug.lua markdown inline-highlight debug
Each leaf exposes plain action functions; bindings/usercmds.lua is the only
place that registers a user command.