walker+completion: surface list trailing-optionals + identifiers-first ordering (ADR-0022 Amendment 2)

walk_repeated discarded the last matched item's trailing-optional
expectations at a clean item boundary, so a comma-separated list
offered no continuation after a complete item: `order by Name `
gave no asc/desc, `select Name ` no `as`, `create table …
Code(text) ` no not/unique/default/check. Capture the last item's
skipped set and surface it when the list ends at an item boundary
(the separator `,` itself is deliberately not surfaced).

That fix made expression-position candidate lists long, which
exposed a visibility problem: the hint panel's candidate line is
single-row and window-scrolls on overflow, centring on item 0 when
nothing is selected — so with keywords-first, schema identifiers
scrolled off behind the `>` marker. Reverse the ordering: schema
identifiers (table/column/relationship names) now sort before
keywords, since a name the user would have to look up is the
highest-value completion and must stay visible (keywords are
learned over time; the tok_identifier/tok_keyword colour split
marks the boundary). This reverses the handoff-14 keywords-first
call, now recorded in ADR-0022 Amendment 2.

Tests: walker expected-set + completion-layer regressions for the
trailing-optionals and the ordering; candidate_ordering.rs header
invariant inverted; ~20 typing-surface snapshots re-baselined; a
two-line hint box recorded as a deferred follow-up.
This commit is contained in:
claude@clouddev1
2026-05-21 21:52:49 +00:00
parent 43c49f4d1b
commit 7f68a53f86
28 changed files with 716 additions and 329 deletions
+20
View File
@@ -4844,6 +4844,26 @@ mod order_by_expected_set_tests {
);
}
#[test]
fn order_by_after_sort_item_offers_direction() {
// After a complete sort item (`order by Name`) the
// sort-direction keywords are valid continuations.
// walk_repeated used to discard the item's trailing
// optionals, so completion offered neither.
let words = expected_words("select Name from T order by Name ");
assert!(words.contains(&"asc"), "expected `asc`; got {words:?}");
assert!(words.contains(&"desc"), "expected `desc`; got {words:?}");
// The separator is deliberately not surfaced (user choice).
let full = expected_at_input_in_mode(
"select Name from T order by Name ",
Mode::Advanced,
);
assert!(
!full.iter().any(|e| matches!(e, Expectation::Punct(','))),
"`,` separator should not be surfaced; got {full:?}",
);
}
#[test]
fn order_by_still_offers_a_sort_item() {
// Guard against over-correction: the legitimate sort-item