runtime-analysis.nvim · Debug & inspect · vimdoc
:help runtime-analysis
Runtime truth for Neovim plugins
doc/runtime-analysis.txt — rendered from the plugin's own vimdoc
*runtime-analysis.txt* Runtime truth for Neovim plugins *runtime-analysis.nvim*
CONTENTS
1. Introduction ................................... |runtime-analysis-intro| 2. Requirements ............................ |runtime-analysis-requirements| 3. Installation ............................ |runtime-analysis-installation| 4. Configuration .................................. |runtime-analysis-config| 5. Commands ...................................... |runtime-analysis-commands| 6. Telemetry ................................... |runtime-analysis-telemetry| 7. Integration with documentation.nvim ....... |runtime-analysis-integration| 8. Health check .................................. |runtime-analysis-health| 9. Further reading .............................. |runtime-analysis-roadmap|
1. INTRODUCTION
runtime-analysis.nvim answers what documentation.nvim (https://github.com/StefanBartl/documentation.nvim) cannot: not what a codebase *declares*, but what actually happens when it runs. See that plugin's docs/ECOSYSTEM.md for the architecture this pairing is part of. The main pieces: - An in-editor HTTP request runner (|:RARequest|, |:RASend|) — no browser, no server, no CORS, no token, because none of those problems exist for a request Neovim itself sends viacurl. -runtime-analysis.telemetry(|:RATelemetry|) — opt-in call counting and usage statistics for any Lua/Neovim plugin, moved here from lib.nvim. -runtime-analysis.loaded— whatpackage.loadedactually holds right now, the live half of a loaded-vs-declared diff (see |runtime-analysis-loaded|). - Main-loop stall detection (|:RA-startup|) — a libuv timer measuring its own lateness, so a freeze shows up whatever caused it. - Startup profiling (|:RA-startup|profile) — repeated--startuptimeruns, averaged, so a per-file cost is a median rather than one sample. Plus |:RA-provenance|, |:RA-inspect|, |:RA-usage| andruntime-analysis.bench(|runtime-analysis-bench|) — see the sections below.
2. REQUIREMENTS
- Neovim 0.10 or later (vim.system) - lib.nvim (https://github.com/StefanBartl/lib.nvim) — hard dependency:net.curl'sfetch_raw/fetch_raw_blockingfor the request runner,usercmd.composerfor |:RA|,progressfor the sending/cancel indicator and telemetry's own reports,fs.project_key+cache.diskfor request history (and telemetry's own persistence),fs.find_root+fs.jsonfor environment files (|:RA-env|), plusgit,ui.kit,usercmd,notifyandautocmd. -curlon PATH — every |:RASend| shells out to it via lib.nvim. Optional: - mdview.nvim (https://github.com/StefanBartl/mdview.nvim) — renders a telemetry report as a live browser tab (report_style = "mdview"). Soft dependency throughout,pcall-guarded. Run |:checkhealth-runtime-analysis| to verify all of the above.
3. INSTALLATION
lazy.nvim:
{
"StefanBartl/runtime-analysis.nvim",
dependencies = { "StefanBartl/lib.nvim" },
cmd = { "RARequest", "RASend", "RATelemetry" },
opts = {
-- split = "vsplit",
-- request_filetype = "http",
-- telemetry = { plugins = {...}, lib_nvim = {...} }, -- lazy.nvim only
},
}
opts = {...}callsrequire("runtime-analysis").setup(opts)for you, the same as:
require("runtime-analysis").setup({...})
4. CONFIGURATION
require("runtime-analysis").setup({
split = "vsplit", -- default; "split" for a horizontal one
request_filetype = "http", -- default
deps_popup = true, -- default
history_max_entries = 200, -- default
telemetry = nil, -- see |runtime-analysis-telemetry|
})
*runtime-analysis-opts.split*
opts.split
Type:string. Default:"vsplit". Where the response pane opens relative to the request buffer:"vsplit"or"split". *runtime-analysis-opts.request_filetype*
opts.request_filetype
Type:string. Default:"http". Filetype set on a new |:RARequest| buffer. Left at"http"rather than a plugin-specific name so VS Code's REST Client / IntelliJ's HTTP Client syntax highlighting, already installed by most readers, applies unchanged. *runtime-analysis-opts.deps_popup*
opts.deps_popup
Type:boolean. Default:true. The one-time "which CLI tools does this plugin want, and why" popup on the firstsetup()after install, via lib.nvim'sdepsmodule.falsedisables it for this plugin alone, right here in its own spec. *runtime-analysis-opts.history_max_entries*
opts.history_max_entries
Type:integer. Default:200. How many past sends |:RA-history| keeps, per project. A ring: the file's size stays a function of how much the plugin is used, not of how long ago it was first started. *runtime-analysis-opts.telemetry*
opts.telemetry
Type:table|nil. Default:nil(no auto-instrumentation). Forwarded verbatim toruntime-analysis.telemetry.lazy'ssetup()— see |runtime-analysis-telemetry| and that module's own README for the full shape. A no-op if lazy.nvim is not the caller's plugin manager.
5. COMMANDS
:RAis a compound verb built via lib.nvim.bindings.usercmd.composer — the same verb-first shape |:DocMap| and |:MDView| already use,<Tab>-completed.:RARequest/:RASendare flat aliases for:RA request/:RA send, kept because they predate:RAand are this plugin's most-referenced surface — see docs/BINDINGS.md in the repository for the full reasoning. *:RA* :RA {subcommand} Dispatch to one of the subcommands below.<Tab>after:RAcompletes them. *:RA-request* *:RARequest* :RA request Open a new request buffer, in the shape VS :RARequest Code's REST Client / IntelliJ's HTTP Client already use:
METHOD url
Header-Name: value
{"optional": "body", "after": "one blank line"}
Always a fresh, unnamed scratch buffer — for a committed file with several requests, open it directly with:einstead, see |runtime-analysis-multi-request| below. One header name is a shorthand:Auth: Bearer <token>orAuth: Basic <user>:<pass>resolve into a realAuthorizationheader, base64-encoding Basic credentials for you. See |runtime-analysis-auth-shorthand| below. *:RA-send* *:RASend* :RA send Parses and sends whichever###block the :RASend cursor is in (or nearest above it — see |runtime-analysis-multi-request| below), sends it via lib.nvim.net.curl, and shows the response (status, headers, body) in a persistent split beside it. Non-blocking: the editor stays responsive while curl runs. A "-> sending METHOD url ..." placeholder appears immediately, replaced by the real response, an error, or a cancelled message once one of those happens. A visible sending/cancel indicator via lib.nvim.progress if available (soft dependency — sending still works with no spinner if it is not). Firing a second |:RA-send| before the first replies supersedes it — a monotonic token, not a queue, not an "already in flight" refusal; see |:RA-cancel| below, which does the same thing on request. The response pane is reused across sends and never steals focus from the request buffer. AContent-Type: application/jsonbody is pretty-printed with realjsonfiletype/folding, reset to plain on the next call if that response is not JSON. *runtime-analysis-multi-request*
Multiple requests, and real files
A buffer (or a real, committed.http/.restfile) may hold more than one request,###-separated — the same delimiter both VS Code's REST Client and IntelliJ's HTTP Client use:
GET https://api.example.com/users
###
POST https://api.example.com/users
Content-Type: application/json
{"name": "Bob"}
runtime-analysis.parse.splitcuts the buffer on every###line (excluded from both sides — a pure separator);parse.block_atthen resolves which block a cursor line belongs to. |:RA-send|/|:RASend| always run this, even on a single-block buffer (no###at all is one block covering everything) — one code path, not a special case for the common single-request buffer. Always the block under the cursor, never the whole buffer, never a picker.*.httpresolves to filetypehttpnatively in Neovim;*.rest(IntelliJ HTTP Client's own extension for the identical shape) via this plugin's ownftdetect/runtime-analysis.lua. Either way, a real file opened directly with:eworks exactly like a scratch buffer from |:RA-request| —:RA sendreads "the current buffer", origin-agnostic. *runtime-analysis-assertions*
Response assertions
# @expect status 200(or// @expect status 200), a comment line anywhere in a###block, checked once |:RA-send|'s response arrives. Deliberately narrow — one directive, one thing it checks ("is this endpoint still 200"), not a general assertion language. At most one per block: a second is a real error, not "last one wins" silently. A match is a plain vim.notify (-> expect status 200). A mismatch — including a transport failure, itself an automatic mismatch when a status was expected — replaces the quickfix list (:copento see it, never auto-opened) with one entry pointing at the directive's own line, naming both the expected and actual status. Module:lua/runtime-analysis/assertions.lua. *:RA-yank* :RA yank Yank just the response body (not the status line or headers) to the unnamed register. Warns rather than erroring when there is no response yet, or the last one had no body. *:RA-cancel* :RA cancel Cancel the in-flight request, if any: shows "x cancelled" in the response pane immediately and marks its eventual result as stale, so a late reply never overwrites that message. Warns rather than erroring when nothing is in flight. A logical cancel, not a process kill: lib.nvim.net.curl.fetch_raw does not hand back the vim.SystemObj a hard kill would need, so the underlying curl process keeps running to completion in the background — only this plugin's own interest in its result is withdrawn. Extending lib.nvim.net.curl for a real kill is separate work in a different repository, not attempted here. *:RA-history* :RA history Open a vim.ui.select picker (whichever picker UI is already configured) over every send this project has recorded, newest first: "date status METHOD url". Picking one reopens it via open_request — see |runtime-analysis-history| below for exactly what is and is not recorded. Reports (does not error) when nothing has been recorded yet. *:RA-history-clear* :RA history clear Clear this project's recorded history. No confirmation prompt. *:RA-env* :RA env [name] Withname(<Tab>-completed), select it as the active environment{{var}}placeholders resolve against. With none, vim.ui.select over every name the project's env files define. See |runtime-analysis-environments| below. Session-scoped, not persisted across restarts. *:RA-import* :RA import Parse acurlcommand line into a new request buffer. Reads the system clipboard by default, falling back to the unnamed register; given a real range instead (e.g.'<,'>RA importafter visually selecting one), reads those lines instead. See |runtime-analysis-curl-import-export| below. *:RA-export* :RA export The reverse: format whichever###block the cursor is in as a shareablecurlcommand line, yanked to the unnamed register — the same convention |:RA-yank| already uses. *:RA-provenance* :RA provenance {path} Who wrapped this function right now. {path} is a dotted string, e.g.vim.notifyorlib.nvim.notify.create— resolved via a global-table walk first, then require() of the whole prefix. Exact for this plugin's own telemetry wraps (named by namespace); best-effort otherwise (a debug.getinfo source location). See |runtime-analysis-provenance| below. *:RA-usage* :RA usage Report keymap/command press counts collected since |:RA-usage-start|. A kit float ifui.kitis available,vim.notifyotherwise. *:RA-usage-start* :RA usage start Start counting — opt-in, nothing runs before this. *:RA-usage-stop* :RA usage stop Stop counting. Collected counts stay readable via |:RA-usage|. See |runtime-analysis-usage| below. *:RA-inspect* :RA inspect {module} Walk a livepackage.loaded[{module}]table and render it: functions (upvalue counts, source location), nested tables (their own shape), metatables, and which direct keys *shadow* a table__index(reported, never called).<Tab>-completes againstpackage.loaded, live. The deeper companion to |:RA-provenance| ("who wrapped this one function") — this answers "what does this whole table contain, right now". *:RA-startup* :RA startup start Watch the main loop for stalls and report after 12s. A libuv timer measures its own lateness, so a block is caught wherever it comes from — including libuv callbacks, which:profilecannot see. :RA startup watch The same measurement, kept running until |:RA-startup|report. :RA startup report Stop measuring and show the timeline: plugin loads (with lazy's load time AND the reason it loaded),VimEnter,VeryLazy,LspAttach, LSP progress, all on one clock. Written tora-startup.login the cwd. :RA startup probe Print and yank the--cmdline that measures a startup from before the config runs — the timer has to tick before any plugin manager exists, which a lazily loaded plugin cannot arrange for itself. :RA startup profile [runs] Start Neovim {runs} times (default 5, sequentially — parallel starts inflate each other) under--startuptime, parse every log, and report the per-file cost as a median with its spread beside it. The one measurement here that runs--startuptimerather than working around it: it sees every file sourced from the first millisecond, where the wrapper in |:RATelemetry|startupcan only see what loads after it armed. A row whose spread rivals its median is scatter, not a finding.gfopens the file on the row. *:RA-loaded-snapshot* :RA loaded snapshot {prefix} [name] Persist every currently-loaded module under {prefix} (itself, or anything beginning{prefix}.— the same scopingwrap_loaded(prefix)already uses) as a named snapshot, so it can be read later — or from a different process entirely, e.g. documentation.nvim's own Loaded Analysis panel over:DocMap serve— rather than only in the live session that took it. {name} defaults to a timestamp. Always explicit: nothing here ever snapshots on its own. *:RA-loaded-snapshots* :RA loaded snapshots {prefix} List every saved snapshot for {prefix}, newest first. See |runtime-analysis-loaded| below. *runtime-analysis-history*
Request history
Every |:RA-send|/|:RASend| records one entry via runtime-analysis.history: **method, url, status, timestamp — nothing else, on either side.** No headers, no body. A header is very often where the real secret actually lives (anAuthorization: Bearer ...value — the whole reason |runtime-analysis-auth-shorthand| exists), so it is left out on the request side too. Persisted via lib.nvim.cache.disk, namespaced per project by lib.nvim.fs.project_key() (the Git root, or the cwd) — a.httpcollection in one repository never shows up in another's history. Capped athistory.MAX_ENTRIES(200) total, oldest dropped first. Every outcome is recorded exactly once: a real response, a transport failure, an explicit |:RA-cancel|, and even a superseded send's real eventual result once it is known. Never recorded twice for the same send. Honest limit: the url is stored verbatim — a secret embedded in a query string is not stripped, since doing so generically and correctly is a real, separate problem. *runtime-analysis-environments*
Variables and environments
{{name}}inside a request buffer's url, header values or body — e.g.{{baseUrl}}/users/:id— resolves against the environment selected by |:RA-env|. Names and values come from two per-project JSON files at the project root, the same split IntelliJ's HTTP Client already uses:
http-client.env.json shared, safe to commit — non-secret
defaults (a baseUrl, a tenant id)
http-client.private.env.json gitignored, per-machine — the file a
real token belongs in
Both optional, merged per environment name with the private file's own keys winning on overlap.runtime-analysis.envwarns once per session if the private file exists on disk but its name is not found anywhere in the project's own.gitignore(a substring check, not a real gitignore matcher). Resolution happens exactly once, immediately before a request is handed to curl: request history (above) and the "-> sending ..." placeholder both keep the literal{{token}}, never the value it resolved to. A request with no placeholders at all is unaffected even with no environment selected. Referencing an undefined variable, or one with no environment selected at all, is avim.notifyerror naming exactly what is missing — never a silent{{name}}sent to a real server as a literal string. *runtime-analysis-curl-import-export*
curl import/export
|:RA-import| parses a realcurlcommand line — the same shape every API's own docs and every browser's "copy as cURL" already produce — viaruntime-analysis.curl.parse: a genuine, if bounded, tokenizer (single- and double-quoted strings, an unquoted backslash escaping the next character, bash line continuations joined first), not string templating. Recognizes-X/--request,-H/--header(repeatable),-d/--data/--data-raw/--data-binary(repeatable, joined with&, curl's own behavior),-u/--user(a real base64-encodedAuthorization: Basicheader),-b/--cookie,-A/--user-agent,-e/--referer, and drops flags meaningless to a request this plugin sends itself (-s,-v,-L,-o,--compressed, timeouts, TLS material, …) without ever mistaking one's value for the URL. Any-d/--datawith no explicit-XimpliesPOST, mirroring real curl's own default. |:RA-export| is the reverse,runtime-analysis.curl.format: headers sorted for deterministic output, single quotes escaped with the standard POSIX'\''trick — deliberately notvim.fn.shellescape, which escapes for Neovim's own&shell(cmd.exe/PowerShell syntax on Windows), the wrong grammar for acurlcommand meant to be pasted into any real shell or shared verbatim in a doc. **Neither resolves{{var}}placeholders** — the identical trap |runtime-analysis-environments| above names, closed the identical way: both are handed the raw, unresolved requestruntime-analysis.parseproduces, the same one request history and the "sending ..." placeholder already keep unresolved. Exporting is sharing, and a{{token}}must render as{{token}}there too. *runtime-analysis-provenance*
Wrapper provenance
|:RA-provenance| — "who wrapped this function": the narrow slice of:RA inspect(§5.1) worth shipping on its own first.{path}is a dotted string, resolved via a global-table walk first, then require() of the whole prefix — and stops there; it does not guess where a module boundary sits inside a longer path. Two different answers, and the output states which one it is giving: this plugin's own telemetry wrapper is **exact** (runtime-analysis.telemetry.registryis the one shared wrap layer every instance goes through, so it genuinely knows every subscribing namespace by name); anyone else's wrapper (lib.nvim.system.proc_trace, any plugin that monkey-patchesvim.notify) is **best-effort** — adebug.getinfosource location, the only honest signal available with no registry to consult. It cannot say who installed a wrapper or when, only where the function currently there was actually defined. *runtime-analysis-usage*
Keymap and command usage
|:RA-usage| — which of your own keymaps and typed commands you actually press. The one feature in this plugin that records *what the person did* rather than *what the code did*, and it ships with exactly the posture that caveat demands: opt-in (|:RA-usage-start|/|:RA-usage-stop|, nothing runs onsetup()alone) and local-only, no account, no upload, no "share this" feature. Built onruntime-analysis.telemetryitself, the same wrap mechanism §7.2's cost-vs-use report already reads for code — one real instance underneath.:RA usage startwrapsvim.keymap.setso a function- callback mapping's own callback is wrapped exactly once, at the moment it is set, not per press; aCmdlineLeaveautocmd counts a typed command once it actually commits (an<Esc>-aborted line counts nothing). Honest limits: only mappings set after:RA usage startare ever seen; a string-rhs mapping (`vim.keymap.set('n', 'x', ':SomeCommand<CR>')`) has no function to wrap; a buffer-local mapping sharing the same mode+lhs as a different mapping elsewhere is combined into one count, not tracked per buffer; a typed command's name is read heuristically and can occasionally misparse an unusual range. *runtime-analysis-auth-shorthand*
Auth: shorthand
Auth: Bearer <token>passes through verbatim as `Authorization: Bearer <token>— the shorthand's value is thatBearer/Basic` read as a matched pair, not that Bearer itself got shorter.Auth: Basic <user>:<pass>base64-encodes the credentials intoAuthorization: Basic <...>— the actual value-add: RFC 7617 requires the base64 form, and computing it by hand is the annoyance this removes.Auth: Basic <already-base64>(no:in the value) passes through unencoded, since base64's own alphabet never contains one. Any other scheme afterAuth:passes through as theAuthorizationvalue unmodified — a generic fallback, not an error. A literalAuthorization:header, written directly, is untouched by any of this. *:RATelemetry* :RATelemetry [args] See |runtime-analysis-telemetry| below — a second, separate compound command rather than:RA telemetry ...(the same split documentation.nvim draws between |:DocMap| and |:DocBrowse|). Registered by the samesetup()call above, opt-in in the sense that requiring the telemetry module alone registers nothing;setup()always wires the command up.
6. TELEMETRY
runtime-analysis.telemetry counts how often functions are called and
persists the counts across restarts. Full API — instances, wrap/wrap_loaded,
auto(), the lazy.nvim adapter, argument profiling, report_style, the
lifecycle reminder — is documented in this repository's own:
lua/runtime-analysis/telemetry/README.md
rather than duplicated here; that file is long and changes independently of
the rest of this plugin. Command surface:
:RATelemetry report across every live instance
:RATelemetry lsp.nvim report for one namespace
:RATelemetry status the fleet board: one aligned row per namespace
this plugin knows about -- live this session or
only ever persisted -- with state, mode,
counts, size on disk and start date. <CR> on a
row opens its full report; keys are listed in
the window's winbar and under ?
:RATelemetry start [ns] every instance, or just one
:RATelemetry stop [ns] every instance, or just one
:RATelemetry reset [ns] back up (one prompt, only if anything would
be lost), then drop collected data -- every
instance, or just one
:RATelemetry disable [ns] stop + persist "off" across restarts
:RATelemetry enable [ns] clear a persisted disable, resume now
:RATelemetry disabled list namespaces currently disabled
:RATelemetry coverage which wrapped functions were never called
:RATelemetry export [path] JSON; Markdown if .md; PDF if .pdf (needs
pdfport.nvim, optional dependency)
:RATelemetry export-all <dir> one Markdown file per namespace found on
disk into <dir> — not limited to this
session's live instances
:RATelemetry open [ns] render + open externally
:RATelemetry compare [ns] [days] "this window vs the one before it"
:RATelemetry startup [top] which module a plugin's startup cost sits in
:RATelemetry cost startup cost vs. call count, worst first
:RATelemetry snapshot <ns> [name] save a named, device-tagged capture
:RATelemetry snapshots <ns> list ns's saved snapshots, newest first
:RATelemetry snapshot-compare <ns> <a> <b> diff two named snapshots'
call counts directly (not a calendar window)
:RATelemetryStartAll standalone alias for :RATelemetry start (bare)
:RATelemetryStopAll standalone alias for :RATelemetry stop (bare)
:RATelemetryResetAll standalone alias for :RATelemetry reset
(bare) -- same backup prompt
:RATelemetrySetupAll for every plugin opts.telemetry.plugins
configures that is loaded right now: back up
existing data (one prompt for the whole run),
reset, re-wrap (picks up any submodule loaded
after the first wrap -- the fix if some
functions never show argument data), start
with that plugin's own profile_args/timing
:RATelemetrySetupAllFull same as SetupAll, forcing profile_args and
timing on for every plugin regardless of its
own configured policy
Startup attribution (|:RATelemetry|startup) is opt-in, from this plugin's own lazy.nvim spec —initruns for every plugin before lazy loads any of them, so it is early enough, and it disappears with the spec if you ever remove the plugin:
{
"StefanBartl/runtime-analysis.nvim",
init = function()
require("runtime-analysis.telemetry.startup").autostart()
end,
}
It wraps the globalrequire, times every cache miss (apackage.loadedhit is not a load), and reports self-vs-total time per module and per module root, then stops itself atUIEnter. Only modules required after it starts are ever seen. See the telemetry README for the full set of honest limits, and for why this is aninithook rather than a line in your own init.lua.:RATelemetry costcombinesstartupwith a live instance's own call count into one number: what a plugin costs to load versus how much it is used. The join is on real module paths (resolved_modules()), never a name comparison between a startup module root and a telemetry namespace — those are only sometimes the same string, and guessing would risk attributing one plugin's cost to another.startup_msreads "unknown", not "0", whenever no real module path can be matched. See the telemetry README's own "Plugin cost vs. use" section for the full reasoning. Reading a namespace does not need an editor session at all — scripts/telemetry.lua in the repository is a headless CLI counterpart, built on the sametelemetry.load()a live instance never had to exist for:
nvim --headless -l scripts/telemetry.lua report <namespace>
nvim --headless -l scripts/telemetry.lua export <namespace> <path>
export's format is inferred from<path>'s own extension, the same rule |:RATelemetry|exportuses. Nocoveragesubcommand here: "which registered functions were never called" needs a live instance's ownwrap()calls to know the registered set, which a coldtelemetry.load()read cannot answer. Measuring instrumentation's own overhead — a separate, one-time script, never a runtime feature (see the "not a general profiler" thesis for why that distinction matters):
nvim --headless -l scripts/bench_overhead.lua
nvim --headless -l scripts/bench_overhead.lua --calls=1000000
Run it yourself for numbers specific to your own machine — see the
telemetry README's "Off costs nothing — literally" section for what it
measures and why it is shaped as a script rather than a command.
*runtime-analysis-bench*
Benchmark comparisons
runtime-analysis.bench answers a different question from telemetry
itself: not "how often was this called", but "which of these candidate
functions is actually faster, right now, on this machine". Deliberately
NOT built on telemetry's own wrap/count machinery — the overhead numbers
above (~10-700ns per call depending on features) would swamp the very
comparison a benchmark exists to make. Every candidate is called
directly, unwrapped.
local bench = require("runtime-analysis.bench")
local result = bench.compare({
{ name = "impl_a", fn = impl_a },
{ name = "impl_b", fn = impl_b },
}, { iterations = 10000 })
print(result.fastest)
for _, line in ipairs(bench.lines(result)) do
print(line)
end
opts.warmup(defaultmin(100, iterations)) runs each candidate untimed first, so comparison order does not bias the result. Pure Lua API, no usercmd — a candidate is a real function value, and there is no command-line shape for "type the two closures you want compared". Compare-now only: no persistence, no named runs. Seedocs/FEATURES/BENCH.mdand the decision record indocs/FEATURE_LOG.md(§3.6).
7. INTEGRATION WITH documentation.nvim
M.open_request(lines)is this plugin's one public integration surface, written against a small named interface rather than another plugin reaching into this one's internal files (per documentation.nvim's docs/ECOSYSTEM.md §7). documentation.nvim's |:DocBrowse| Endpoints mode is the first consumer: pressinggson a route it found by static analysis callsrequire("runtime-analysis").open_request({"METHOD path", ""})— a soft dependency (pcall(require, "runtime-analysis")), absent with a clear message when this plugin is not installed. Deliberately not an immediate send: a route's path (/users/:id) is relative and may contain unfilled:params — genuinely nothing documentation.nvim's static analysis could send correctly on its own — so the method and path are pre-filled and the reader completes the base URL (and any params) before running |:RASend| themselves.
local M = {}
---@field opts { split: string, request_filetype: string, deps_popup: boolean, history_max_entries: integer }
---@field open_request fun(lines?: string[])
---@field setup fun(opts?: table)
*runtime-analysis-loaded*
Loaded-vs-declared
runtime-analysis.loadedanswers what documentation.nvim's own static scan structurally cannot: what is actually on a module's table right now, in the current process — as opposed to what the source *declares*.M.functions(module_id)/M.is_loaded(module_id)are the live read; documentation.nvim's owncore/loaded_diff.luajoins them against its IR for |:DocBrowse|loadedmode. The one honest limit that shapes all of it: this readspackage.loadedin the current process only. A tree analyzed from a process that never loaded it sees nothing, which must render as "not loaded here", never as "declared but dead". |:RA-loaded-snapshot| persists what a live read like the above would say, under a name, so it can be read later — or from a different process entirely. documentation.nvim's Loaded Analysis panel (:DocMap serve,GET /api/loaded) is exactly that second process: a server route answering a browser tab has no livepackage.loadedof its own, so it only ever reads named snapshots, never a live aggregate the way its Telemetry panel counterpart can.
8. HEALTH CHECK
*:checkhealth-runtime-analysis*
:checkhealth runtime-analysis
Reports: Neovim version,curlon PATH, every required lib.nvim module loading, live telemetry instances and persistently disabled namespaces in this session, the telemetry cache directory and whether it is writable, this project's history entry count, this project's defined environments and which one (if any) is active, whether keymap/command usage tracking is running and how many presses it has counted so far, whether mdview.nvim and lib.nvim.progress are available, and whethersetup()has registered its commands yet in this session.
9. FURTHER READING
What has shipped, and why: docs/FEATURE_LOG.md. Ideas that only exist between this plugin and its siblings (documentation.nvim, mdview.nvim, lib.nvim): docs/IDEAS.md.