Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

28. Hover text is compiled from the specification, not read at runtime

  • Status: Accepted
  • Date: 2026-09-04

Context

textDocument/hover needs somewhere to pull explanatory text from for a builtin function (print) or a built-in method (str.trim). Korrin has one: docs/spec/08-builtins.md. Its free functions (§8.1) are each a ### \name(sig)`` heading followed by prose and an example; its methods (§8.2-8.4) are markdown tables, one row per method, no heading at all — two different shapes in one file, both needing a parser.

The question is where that file is read from when korrin-lsp actually runs. The binary's install location has no fixed relationship to a checkout of the Korrin repository: it might be on $PATH after cargo install, sitting wherever a VS Code extension chose to bundle it, or run straight out of target/debug/ during development. A runtime path read (docs/spec/ relative to something) means solving "relative to what" for every one of those cases, and shipping the markdown file as a separate asset alongside the binary.

Decision

hover.rs embeds docs/spec/08-builtins.md at compile time with include_str!, and parses it once, lazily, into two lookup tables — one for free functions, one for methods (a method name maps to every type that has one, since Korrin has no static types to disambiguate by receiver: hovering .len() shows str, list, and map together rather than guessing).

No new dependency for the parsing itself: the heading and table-row structure is mechanical enough (### \...`for a heading, `` |...` `` for a table data row) that splitting on those markers is simpler than reaching for a markdown-parsing crate for two shapes this specific.

Every documented name must be a real one, checked automatically: korrin-lsp's own test suite compares the parsed function and method names against korrin::builtins::names() and korrin::interpreter::methods::names() — the same interpreter functions crates/korrin/tests/builtins.rs already uses to hold the spec accountable to the code. This is a second copy of that check because korrin-lsp now depends on the doc's structure being accurate, not just its prose, and a spec/code drift that the existing test would catch should be caught here too, before it silently produces wrong or missing hover text.

Consequences

  • korrin-lsp is fully self-contained: copy the binary anywhere and hover works, with no path discovery, no bundled sidecar file, no "cannot find docs/spec" failure mode to handle.
  • The spec file's prose is now also product copy shown directly in an editor, not only documentation read on the website. It was already held to a high bar by ADR 0002; this raises the cost of a sloppy edit slightly further, which is the right direction.
  • Changing docs/spec/08-builtins.md's structure (adding a new subsection shape, say) means updating hover.rs's parser in the same change, or hover text silently goes missing for whatever no longer parses. The anti-drift test catches exactly this — a name in the interpreter with no matching parsed entry fails the build — but only for missing/extra names, not for a correctly-named entry whose body parsed wrong.
  • korrin-lsp must be rebuilt for hover text to reflect a spec edit; a doc change alone (with no crate rebuild) has no effect on a running server, since the file is compiled in rather than watched.

Alternatives considered

  • Read docs/spec/08-builtins.md at runtime, from a path discovered relative to the binary or a workspace root. Solves nothing that include_str! doesn't already solve for free, and adds real path-discovery logic (search upward for a docs/ directory? require an environment variable? fail gracefully?) purely to reproduce compile-time embedding by hand.
  • Duplicate the hover text as Rust doc comments or a separate data file maintained independently of the spec. Two sources of truth for the same information, which is exactly the kind of drift ADR 0002's documentation discipline exists to prevent elsewhere in this project.
  • A real markdown-parsing dependency, for correctness beyond the two mechanical shapes this file actually uses. Not justified yet — revisit if the spec's structure grows more varied than headings and tables.