NORMAL ~/wkd/p/debugging/help :set skin=modern utf-8

debugging.txt

One :Debug command for Neovim debugging — debugging.nvim

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 *debugging-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-intro*

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 *debugging-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 *debugging-installation*

cmd = "Debug" lazy-loads the plugin on first use of the :Debug command — no
event or lazy = false needed.

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 *debugging-config*

  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 of
  quarantine/safety is a module name to require, or a table used directly
  — so the bridge works without the private config.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 inside features, 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*

  :Debug [category] [action] [args]
With no arguments, :Debug shows an overview of the enabled categories — in a
scrollable floating window by default (q/<Esc> closes it), or as a single
notification when overview = "notify". An unknown or disabled category yields
an error/notification.

5.1 Categories *debugging-categories*

messages

  show     Open the :messages window.
  capture  Capture :messages to a file and the clipboard.
  clear    Close all debug windows.

noice

  all      Show all Noice messages.
  errors   Show Noice errors.

report

  buf      Buffer report to :messages.
  tab      Tab 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 (or terminals.keylogger.logfile set) the keys are
                  also appended to that file for later review.
  stop          Stop 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 a sudo, ssh or gpg prompt. 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

  show                  Print indentation-related buffer options.
  treesitter [true|false]  Prefer Tree-sitter indent (disable cindent/
                             smartindent), or restore with false.

markdown

  inline   Gather markdown inline-highlight debug information.
  log      Open the most recent debug log.

module

  reload   Clear package.loaded (and vim.loader when available) for the
             Lua module that corresponds to the current buffer, then re-require
             it. The buffer must be a .lua file inside a lua/ directory on
             the runtimepath; the module name is derived from the file path.

             Example: while editing lua/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.
  stop                  Restore the original functions.
  status                Print whether tracing is active + the log path.
  log                   Open 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; needs features.neotree = true)
  status exit restart backup-list backup-clean dryrun-toggle
  dryrun-report queue-status queue-clear
  Bridges to a user-specific Neo-tree safety layer (injectable via the
  neotree config); degrades gracefully with a notification when that layer is
  absent.

health

  Run |:checkhealth| debugging.

6. DIAGNOSING UI FREEZES *debugging-proc*

:Debug proc answers "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/log wraps the process-spawning Lua APIs (see
     proc under |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 through vim.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_trace only sees calls through the exact API tables it wraps — a
plugin that cached a local reference (local system = vim.fn.system) before
proc start ran bypasses it; start as early as possible (ideally the first
line of init.lua) to minimize this. proc watch has no bundled equivalent
on Linux/macOS; pstree -p <nvim_pid> or a watch-looped ps covers the
same ground there.

7. TAB COMPLETION *debugging-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 *debugging-health*

  :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 *debugging-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.

keys

j / k
next / previous line
gg / G
first / last line
⏎
open the line under the cursor
/
search the plugins
:
command line — Tab completes
:help x
vimdoc of a plugin (:e x = plugin page)
:ls · :log · :stack
plugin list · activity stream · dependency graph
:colo x
colorscheme
:set skin=
modern | tui
?
this help
esc
close