Commit Graph

9 Commits

Author SHA1 Message Date
Levi Neuwirth 4282b1c333 feat(injections): multi-language injection layers
Teach the syntax engine that one buffer can hold more than one
language. After the root parse, run the grammar's injections.scm, parse
each embedded region with the injected language, and merge every
layer's highlight spans. First consumer: markdown fenced code + inline
(zero new grammars — the block grammar already ships an injection query
and the injected langs already have grammars from #118).

Engine (src/syntax.rs):
- ParseTreeBundle now holds Vec<Layer> (root layer 0 + injected
  children, depth-ascending); installed atomically so the existing
  Arc::ptr_eq style gate and highlight cache keep working (Q#IJ1).
- run_parse builds layers on the worker: run injections.scm, resolve
  the injected language, compute Vec<Range> (exclude NAMED children,
  intersect the parent's ranges), set_included_ranges cold-parse,
  recurse — bounded by depth (3), a layer backstop (4096), and a
  (lang,ranges) visited guard; any child failure drops that child only
  (Q#IJ3/IJ5). LanguageEntry gains injections_query; markdown_inline is
  registered (retires the M9.7 block-only floor); markdown/rust carry
  injection queries.
- Injected languages resolve off the static BUILTIN_LANGUAGES table
  (Send loaders + query sources), preserving lazy loading. Dynamic
  fence names go through a case-folded alias map seeded with defaults
  and Lua-extensible via pmacs.parse.injection_aliases, snapshotted into
  ParseRequest at dispatch so the worker never touches the Rc registry
  or a Lua table (Q#IJ2/IJ4). Highlight queries are resolved at settle
  (resolve_layer_queries), keeping query compilation main-thread/cached.

Producers:
- SyntaxHighlightView (grid) iterates layers shallow-to-deep so a
  deeper layer's styling wins within its region (Q#IJ6/IJ7).
- scoped_style_spans (wire) flattens all layers into DISJOINT effective
  spans via a boundary sweep, since the GPU re-sorts spans by start
  (replace_style_spans / merge_style_spans) and would otherwise destroy
  producer order. The GPU source_color_at consumer is fixed to fold all
  covering spans (matching semantic_client's effective_style_at) rather
  than returning the first.

Named-children exclusion: content ranges exclude only NAMED children
(matching tree-sitter-md's own inline splitter) — excluding a block
inline node's anonymous text tokens would shred the paragraph into
unparseable fragments.

13 acceptance gates (framing docs/multi-language-injections-framing.md):
layer structure, absolute child offsets, alias resolution (static +
case-folded dynamic + unknown-skip + Lua-async override), multi-range
inline, recursion bounds, wire + grid + GPU producers, incremental edit
/ new fence, many-paragraph settle budget with tail coverage, and the
single-layer regression guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YJ9FQ832QwftJXCD9LeFan
2026-07-15 12:07:21 +01:00
Levi Neuwirth 7975eeda87 feat(themes): named UI faces + ThemeFacts channel (protocol v16)
Arc 4 stage 1 (docs/theme-faces-framing.md, revision 4). Faces are
theme entries under the reserved ui/ui.* namespace -- zero new Lua
API. Theme::face() resolves with the dotted-prefix walk but never
falls back to default_style; each face applies owns-surface within
its stage-1 component mask, identical on both frontends.

Substrate: two monotonic theme mutation counters (syntax/face) with
transactional set/merge/clear/default (parse before locking, commit
all-or-nothing, bump from the prior value); the StyleGate and the
minimap summary key on the counters -- fixing the pre-existing bug
where a mid-session pmacs.theme.set never re-shipped StyleSpans --
with the summary gaining payload-equality suppression that still
advances its key on computation.

Wire: InstanceMessage::ThemeFacts appended after CompletionPopup
(postcard discriminants are ordinal; a byte pin guards placement),
PROTOCOL_VERSION 15 -> 16, daemon-gated >= 16, one authoritative
table per attachment (None-seeded baselines), TUI silent-drop arm.

Grid: paint_frame resolves ui.modeline / ui.statusline /
ui.minibuffer(.candidate) / ui.gutter / ui.selection faces;
SearchView and DiagnosticView take the theme handle through the real
attachment paths (EditorCore injection, install_diag threading); the
canonical severity color resolves ui.diag.* with the Default ->
built-in policy that keeps the minimap presence encoding sound.

GPU: exact-name face table applied per draw with the Q#TH5 Default
mapping (plain text / window bg, reverse swap), local/peer wash
split, candidate-dropdown glyph site, and the status-band
shaping-cache invalidation without which a diag-face recolor with
constant counts kept stale counter colors.

Tests: 18-test acceptance suite (grid, wire, daemon gate, atomicity,
monotonicity, late join), 7 GPU headless tests incl. decoded vertex
colors, units for the face walk / transactional commits / producer
caches; protocol pins for v16 + the CompletionPopup byte pin.
Bites vs 3cbb9de (scripts/bite): semantic_render.rs (8 runtime test
failures), editor.rs (5 runtime), daemon.rs (v15 gate, runtime);
lua_bindings/mod.rs, pmacs-gpu/main.rs, search.rs, diag.rs, and
highlight.rs bite as compile failures (weaker evidence, disclosed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VoiEyuPjoBhvwACf8HAnLB
2026-07-14 16:11:09 +01:00
Levi Neuwirth c1122691ad feat(tui): diagnostic gutter signs riding the line-number gutter (sub-arc 2)
Sub-arc 2 of the UX arc, TUI half. When a window reserves a line-number
gutter, lines with diagnostics get a severity-colored sign glyph (E/W/I/H)
in the gutter's leading column — closing the last deferred Task #23 item.
No protocol/daemon change: the per-line severity is already frontend-side
(the diag store the DiagnosticView already reads).

- `Viewport` gains `gutter_w` so overlays can reach the gutter's leading
  column at `cell_origin.col - gutter_w`; the text area is already shifted
  past it, so viewport-relative painters stay gutter-agnostic.
- The gutter's number pass now runs *before* the overlays (was after), so
  the DiagnosticView can draw its sign into the gutter's blanked leading
  column without the number pass erasing it.
- DiagnosticView: with a gutter, draw the severity sign glyph colored by
  `underline_color()`; without one, keep the legacy column-0 background
  marker (the "fake gutter" that predates a real gutter column). Extracted
  to `paint_line_markers` to keep `render` under the line cap.

The number never reaches column 0 (>=1 leading pad by construction), so
sign and number coexist. Diagnostic signs currently ride the line-number
gutter (visible when line numbers are on); a signs-without-numbers mode is
deferred.

Test: gutter_sign_replaces_the_column_marker_when_a_gutter_is_reserved.
Validated: fmt + clippy --all-targets clean both flavors; 1441 lib + 22
diag tests pass. Needs a TUI eyeball before the GPU half.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014TXbAwk27agwhrNNrhLi2U
2026-07-06 19:17:43 -04:00
Levi Neuwirth 9718958c4c Fix stale TUI styling after edits 2026-05-26 11:15:23 -04:00
Levi Neuwirth 0935efd454 M_B3: tree-sitter-cpp/c + dual-authority TUI styling
Drops the policy-A exclusivity that left grammar-backed languages
without LSP semantic refinement. Adds tree-sitter-c (.c/.h) and
tree-sitter-cpp (.cpp/.cc/.cxx/.hpp/...) to the bundle so the grid
TUI gets lexical highlighting (keywords / strings / operators) on
first open. The Lua attach in builtin/runtime/lsp.lua now pushes
LspStyleView whenever an LSP server is up, regardless of grammar
presence; with both views attached the cell-painter pipeline runs
SyntaxHighlightView first (lexical) then LspStyleView (semantic)
and their styles compose through crate::overlay::merge_styles. The
result is the VSCode / Zed "TextMate + LSP semantic tokens" model
on a terminal grid: keywords colored by tree-sitter, identifiers
refined by clangd's semantic tokens.

`.h` is ambiguous C / C++; the `c` BUILTIN_LANGUAGES entry claims it
to match the LSP filetype map's default. Users who want `.h` parsed
as C++ can override via Lua (extension → language map).

Note the tree-sitter-c / -cpp crates expose `HIGHLIGHT_QUERY`
(singular), matching tree-sitter-md's `HIGHLIGHT_QUERY_BLOCK`
convention; tree-sitter-rust / -lua use `HIGHLIGHTS_QUERY` (plural).
Same bundled highlights.scm either way.

Regression guard: builtin_languages_include_c_and_cpp asserts the
language entries exist and claim their canonical extensions. The
LspStyleView module doc rewritten to reflect dual-authority
composition; the existing headline test's comment updated (the
test fixture still attaches only LspStyleView directly, so its
asserted cells reflect the LSP authority alone — Lua-level
attach_buffer is what exercises composition end-to-end).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:39:07 -04:00
Levi Neuwirth 1652837486 M_B1.1: theme + modifier polish for LSP styling
The grid TUI's M_B1 LspStyleView shipped functional but the default
theme only covered tree-sitter capture names. clangd / rust-analyzer
/ gopls emit LSP-spec SemanticTokenTypes that didn't intersect — so
`LLAMA_LOG_ERROR`, namespace names, parameters, fields, etc. fell
through to default and rendered uncolored. This brings the default
theme up to the LSP vocabulary so what the LSP layer ships actually
paints.

Theme additions (Theme::default_dark): macro, namespace, parameter,
property, class, struct, enum, interface, enumMember, modifier,
decorator, regexp, typeParameter. Refactored default_dark to a
data-driven (name, style) table — cuts the function from ~170 lines
to ~70, and adding a new theme entry now means appending one row
rather than 6 lines of `by_capture.insert(...)`.

LspStyleView::render now builds the lookup name as `<type>.<first-
modifier>` when modifiers are set, else just `<type>`. Theme::lookup's
dotted-prefix walk falls back to base if a refined entry isn't
defined, so the change is a strict refinement: themes that want to
target e.g. `function.defaultLibrary` (clangd's standard-library
modifier) can, themes that don't see no behavior change. Allocation
is skipped in the no-modifier case via `Cow::Borrowed`.

Tests: default_dark_covers_lsp_token_types regression-guards the
LSP-vocabulary coverage; lsp_style_view_uses_modifier_in_capture_lookup
proves a modifier-refined theme entry wins over the base.

The bigger polish (real C++ keyword/string coloring) needs
tree-sitter-cpp + dropping policy A's exclusivity so grammar-backed
languages get both views — separate thread, intentionally out of
scope here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:17:14 -04:00
Levi Neuwirth c660d55881 M_B1: LspStyleView — LSP-driven syntax coloring in the TUI
Closes the visible "C++ has no syntax coloring in the grid TUI" gap.
Sibling of SyntaxHighlightView: a View impl that paints LSP semantic
tokens as cell styles, attached for buffers with no bundled
tree-sitter grammar. Same policy A (one styling authority per buffer)
the semantic-frontend producer arc enforces, applied to the grid
renderer the user actually uses today.

Mechanics: every render re-derives the buffer's URI from
buf.file_path() and pulls (encoding, legend) via the existing
LspManager::semantic_style_context plus tokens via for_uri. Per
visible line, tokens are converted from LSP encoding units to byte
ranges via char_to_byte, then to display columns via the existing
byte_range_to_display_cols (UTF-8 + tab aware). Theme::lookup
resolves token type names through the same dotted-prefix mechanism
the tree-sitter capture names use, so "function", "variable",
"type", "keyword" land on the existing theme vocabulary with no new
style names. Default-styled spans skip the per-cell loop, matching
SyntaxHighlightView's short-circuit.

Wiring: pmacs.lsp._attach_style binding pushes the overlay on the
active window (mirrors pmacs.parse._attach_highlight). install_lsp
and make_lsp_manager take SharedSyntaxRegistry so the binding can
hand the LspStyleView the shared ThemeHandle; editor.rs caller
updated. builtin/runtime/lsp.lua's attach_buffer attaches the view
when pmacs.parse.language_for_path returns nil (grammar-less
signal), dedup'd via a styled_buffers set that mirrors syntax.lua's
highlighted_buffers.

Test: lsp_style_view_paints_cells_from_semantic_tokens — seeds an
Initialized fake LSP client (using the cfg(test) helper from the
producer arc) on a /tmp/x.cpp buffer with one token, asserts the
expected cells are styled per the theme face and the cell just past
the token range is unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 19:48:50 -04:00
Levi Neuwirth 587a2a15de M9 ship gate
Land the Model Context Protocol (MCP) integration as a transport binding,
not a built-in feature. Six Lua functions plus userdata methods expose
the substance of three MCP feature areas (resources, tools, prompts), a
notification dispatcher, and a non-trivial AI-assistance example package
that meets the architectural ship gate (spec/pmacs-spec.tex:1572): zero
direct calls into the Rust core, zero special-cased MCP handling outside
the public API, source under 2000 lines of Lua.

The M9.5 -> M9.6 -> M9.7 -> M9.8 layered composition validates the claim
"AI is a transport binding, not a feature" -- pmacs-mcp-ai composes with
pmacs-mcp-prompts.render and inherits notification handling transitively
through M9.7's package, demonstrating that the AI domain is a layer
above MCP, not a thread woven through the core.

Subtask shape:
  M9.1 stdio transport + initialize handshake + restart policy
  M9.2 resources with in-flight + settled cache and per-uri invalidation
  M9.3 tools with isError-vs-JSON-RPC-error semantics + cancellation
  M9.4 prompts with required-argument validation
  M9.5 notification dispatcher (on_notification, off_notification)
  M9.6 tools-as-commands fixture package + 12 audit findings disposed
  M9.7 prompts-as-result-buffers fixture package + tree-sitter-md grammar
  M9.8 AI-assistance fixture package (363+ LoC; 17/17 acceptance tests)
  M9.9 formal package audit -- PASS on all three criteria
  M9.10 release: TRANSITION-M9.md + MCP-for-package-authors guide

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 15:04:23 -04:00
Levi Neuwirth 4da4b09d5d Initial commit: v0.1.0 2026-05-03 19:51:06 -04:00