Commit Graph

52 Commits

Author SHA1 Message Date
claude@clouddev1 51a8d9ac44 ADR-0022 stage 8c: IdentSlot propagation + SchemaCache API
`IdentSlot` gains `expected_label()` and the round-trip
`from_expected_label()`. The four slot kinds map to the
user-facing labels "identifier" (NewName), "table name",
"column name", "relationship name".

`ident_ctx(slot)` now actually applies `slot.expected_label()`
as the chumsky label (was documentation-only after stage 6).
Parser errors and the hint panel's "expected: …" prose now
read with the slot-specific name: "expected table name"
instead of the generic "expected identifier". One parser
test updated accordingly; the four catalog `parse.token.*`
keys are unaffected (the slot labels are a parallel surface).

New `completion::SchemaCache { tables, columns,
relationships }` struct + `for_slot(slot) -> &[String]`
accessor. Empty by default; runtime wiring lands in a
follow-on substage. NewName slots return `&[]`
unconditionally.

`candidates_at_cursor` extended to accept `&SchemaCache`:
when the parser's expected-set includes a slot label,
schema candidates from the cache are added alongside the
keyword candidates. Both sources are then prefix-filtered,
combined, sorted, deduplicated. App::schema_cache field
threaded into both the App-side completion paths and the
ambient_hint computation in ui.

Tests: 738 passing, 0 failing, 1 ignored (730 baseline →
+8: 2 IdentSlot label round-trip tests, 6 completion-with-cache
cases covering table/column/relationship slots, prefix
filtering, empty cache, and NewName-no-candidates).
Clippy clean.

User-visible: identifier completion infrastructure is in
place but the cache is always empty — runtime wiring (the
next substage) will populate it on project load and after
successful DDL, at which point Tab on identifier slots
starts offering schema names.
2026-05-11 20:53:19 +00:00
claude@clouddev1 06e8d1e769 ADR-0022 stage 8a: non-modal keyword completion + Esc/Backspace undo
Per the user's framing decision: there is no "completion
mode." Tab is just an action that consumes whatever is
expected at the cursor, and the existing always-on hint
panel (stage 5) tells the user what's available.

New `completion` module: `candidates_at_cursor(input,
cursor)` returns a `Completion { replaced_range,
partial_prefix, candidates }` based on the parser's
expected-token set at the cursor position. Filters to bare
keyword candidates only (no punctuation, no descriptive
labels), narrowed by the typed prefix (case-insensitive).

`LastCompletion` memo struct on `App::last_completion`
carries the cycle state: inserted_range, original_text,
candidates, selection_idx. Wrap-around forward/backward
indices.

App key handling (added before the existing matcher):
  - Tab → cycle forward if memo present; else insert first
    candidate; create / advance memo.
  - Shift-Tab → cycle backward if memo present; else
    insert last candidate (alphabetically) so the user can
    jump to the end without cycling through everything.
  - Esc / Backspace while memo alive → restore
    original_text in inserted_range, place cursor at the
    pre-Tab position, clear memo.
  - Any other key → clear memo, then process normally.

The user's symmetry preference was load-bearing here:
"insert with one keystroke, remove with one keystroke."
Both Esc and Backspace honour that — multiple Tab cycles
collapse into one undo. Documented inline.

A single-candidate completion still creates a memo so
Esc/Backspace can undo it. Multiple Tabs in a row cycle
through the candidate list with wrap-around at both ends
(per the user's #2).

Tests: 728 passing, 0 failing, 1 ignored (705 baseline →
+23: 13 completion module + 10 app integration tests
covering Tab, Shift-Tab, cycling, wrap-around, Esc-undo,
Backspace-undo, multi-Tab-then-Esc, memo invalidation by
typing or cursor movement). Clippy clean.

Stage 8b will add multi-candidate hint-panel rendering
with scroll markers (`<` `>`) per the user's #2. Stage 8c
will plumb in identifier completion + invalid-identifier
detection.
2026-05-11 20:43:06 +00:00