ADR-0022 stage 8 follow-up r2: completion UX fixes from real testing
Two concrete behaviour changes from the user's second testing
round:
1. **Single vs multi commit paths.** Previously every Tab,
even single-candidate, created a memo so Esc/Backspace could
undo. The downside: with one candidate, repeated Tab "cycled"
through the same item invisibly — looked stuck. Now:
- Single candidate → insert with trailing space, no memo.
The user can keep typing or hit Tab again to fresh-complete
at the new cursor. (Trade-off: Esc/Backspace no longer
whole-span undo for unique completions; the user accepted
this for the chained-Tab fluency.)
- Multi candidate → insert WITHOUT trailing space, create
memo for cycling. The natural commit gesture is space —
pressing it clears the memo and inserts the space normally,
producing "<chosen> " ready for the next position.
The "stuck on unique" symptom goes away, and the missing
trailing space on multi-Tab signals "you're picking; press
space when you're done" without needing modal affordances.
2. **Keyword candidates in grammar order.** Dropped the
alphabetical sort in `describe_expected` in favour of
chumsky's native source-order traversal of `or_not`/`choice`
chains — empirically this matches the canonical command
shape. Result: `add column ` now offers `to` before
`table` (as `add column [to] [table] <Table>:…` reads),
not `table` before `to` which previously suggested the
nonsensical `add column table to ...`. Identifiers still
alphabetised within their group; entry-keyword fallback
for the no-prefix case stays alphabetical (no source order
when 10 separate command branches).
Tests: 750 passing, 0 failing, 1 ignored (747 baseline →
+3 net: replaced single-candidate Esc/Backspace tests with
new multi-candidate variants; added the unique-Tab-chains-
naturally case that drove the round-2 fix; kept the
keywords-in-grammar-order test updated to assert
`to`/`table`/identifiers ordering).
This commit is contained in:
+16
-12
@@ -126,15 +126,18 @@ pub fn candidates_at_cursor(
|
||||
let matches_prefix = |s: &str| s.to_lowercase().starts_with(&lowered_prefix);
|
||||
|
||||
// Source 1: keyword candidates from the parser's
|
||||
// expected-set.
|
||||
// expected-set. Preserve `expected`'s order — it reflects
|
||||
// chumsky's source-order traversal of `or_not` / `choice`
|
||||
// chains, which matches the canonical command shape (e.g.
|
||||
// `to` before `table` for `add column [to] [table] …`).
|
||||
let mut keywords: Vec<String> = expected
|
||||
.iter()
|
||||
.filter_map(|item| strip_backticks(item))
|
||||
.filter_map(|name| Keyword::from_word(name).map(|_| name.to_string()))
|
||||
.filter(|name| matches_prefix(name))
|
||||
.collect();
|
||||
keywords.sort();
|
||||
keywords.dedup();
|
||||
let mut seen_kw = std::collections::HashSet::new();
|
||||
keywords.retain(|k| seen_kw.insert(k.clone()));
|
||||
|
||||
// Source 2: schema identifiers — accumulated across every
|
||||
// matching known-set slot. `NewName` slots return `&[]`.
|
||||
@@ -444,24 +447,25 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn keywords_come_before_identifiers_in_candidate_order() {
|
||||
// "add column " has both keyword candidates (`to`,
|
||||
// `table`) and schema-identifier candidates. Per the
|
||||
// user feedback after stage 8: keywords first
|
||||
// (alphabetical within), identifiers second
|
||||
// (alphabetical within).
|
||||
fn keywords_come_before_identifiers_in_grammar_order() {
|
||||
// "add column " has both keyword candidates and
|
||||
// schema-identifier candidates. Per the user's stage-8
|
||||
// feedback round 2: keywords first in *grammar order*
|
||||
// (so `to` before `table` because the canonical shape
|
||||
// is `add column [to] [table] <Table>:…`), identifiers
|
||||
// after, alphabetised. The grammar order falls out of
|
||||
// chumsky's source-order expected-set traversal — we
|
||||
// preserve that order through `describe_expected`.
|
||||
let cache = SchemaCache {
|
||||
tables: vec!["Customers".to_string(), "Orders".to_string()],
|
||||
..SchemaCache::default()
|
||||
};
|
||||
let kinds = cand_kinds_with("add column ", 11, &cache);
|
||||
// Expect: ["table", "to"] keywords, then ["Customers",
|
||||
// "Orders"] identifiers.
|
||||
assert_eq!(
|
||||
kinds,
|
||||
vec![
|
||||
("table".to_string(), CandidateKind::Keyword),
|
||||
("to".to_string(), CandidateKind::Keyword),
|
||||
("table".to_string(), CandidateKind::Keyword),
|
||||
("Customers".to_string(), CandidateKind::Identifier),
|
||||
("Orders".to_string(), CandidateKind::Identifier),
|
||||
],
|
||||
|
||||
Reference in New Issue
Block a user