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
+30 -27
View File
@@ -1,21 +1,24 @@
//! Matrix coverage for completion-candidate *ordering*.
//!
//! The order candidates appear in is load-bearing for the hint
//! panel: it reads left-to-right, so the sequence must match
//! how the command is spoken. Two invariants the user called
//! out (handoff-14 ranker discussion):
//! panel: the candidate line is single-row and window-scrolls
//! when it overflows, so whatever sits first is what stays
//! visible by default. Two invariants:
//!
//! 1. Connective keywords appear in grammar-declaration / reading
//! order — `to` before `table` so `add column to table T`
//! reads correctly, never the jarring `table` / `to`.
//! 2. Command-part keywords appear before schema identifiers
//! (table / column names) — grammar parts are read before
//! the content that fills them.
//! 2. Schema identifiers (table / column names) appear *before*
//! command-part keywords. A name the user would otherwise
//! have to look up is the highest-value completion — valuable
//! even to experts, who come to know the keywords over time —
//! so it must stay visible by default rather than scroll off
//! behind the keyword run. (ADR-0022 Amendment 2; reverses the
//! earlier handoff-14 keywords-first call, which held only
//! while candidate lists were short.)
//!
//! These hold today via declaration-order preservation +
//! keywords-first sectioning in `candidates_at_cursor`. Nothing
//! pinned them until now, so a future grammar/sort change could
//! silently break the reading order.
//! These hold via declaration-order preservation +
//! identifiers-first sectioning in `candidates_at_cursor`.
use crate::typing_surface::*;
@@ -78,30 +81,30 @@ fn change_column_lists_in_before_table() {
}
// =========================================================
// Keywords-before-identifiers: at a position where both a
// Identifiers-before-keywords: at a position where both a
// connective keyword and schema table names are valid, the
// keyword comes first.
// schema identifiers come first (ADR-0022 Amendment 2).
// =========================================================
#[test]
fn add_column_keyword_precedes_table_identifiers() {
fn add_column_identifiers_precede_table_keywords() {
let schema = schema_multi_table();
let a = assess_at_end("add column ", &schema);
// `to` / `table` are command parts; Customers / Orders are
// schema identifiers — every keyword precedes every ident.
assert_before(&a, "table", "Customers");
assert_before(&a, "to", "Customers");
assert_before(&a, "to", "Orders");
crate::snap!("add_column_keyword_then_idents", a);
// Customers / Orders are schema identifiers; `to` / `table`
// are command parts — every ident precedes every keyword.
assert_before(&a, "Customers", "table");
assert_before(&a, "Customers", "to");
assert_before(&a, "Orders", "to");
crate::snap!("add_column_idents_then_keyword", a);
}
#[test]
fn drop_column_keyword_precedes_table_identifiers() {
fn drop_column_identifiers_precede_table_keywords() {
let schema = schema_multi_table();
let a = assess_at_end("drop column ", &schema);
assert_before(&a, "table", "Customers");
assert_before(&a, "from", "Orders");
crate::snap!("drop_column_keyword_then_idents", a);
assert_before(&a, "Customers", "table");
assert_before(&a, "Orders", "from");
crate::snap!("drop_column_idents_then_keyword", a);
}
#[test]
@@ -116,14 +119,14 @@ fn insert_into_table_keyword_precedes_nothing_when_only_idents() {
}
// =========================================================
// After consuming the first connective, the second still
// surfaces ahead of identifiers.
// After consuming the first connective, identifiers still
// surface ahead of the remaining keyword.
// =========================================================
#[test]
fn add_column_after_to_lists_table_before_identifiers() {
fn add_column_after_to_lists_identifiers_before_table() {
let schema = schema_multi_table();
let a = assess_at_end("add column to ", &schema);
assert_before(&a, "table", "Customers");
assert_before(&a, "Customers", "table");
crate::snap!("add_column_after_to", a);
}