feat(hint): advertise the optional seed count in the hint panel (#26)
At `seed <table> ▮` the hint showed only the `set`/`--seed` chips and
never mentioned the optional row count — a bare positional number with no
candidate, on an already-complete command, so neither the candidate
ladder nor the resolver surfaced it. (A prior IntroProse attempt was
reverted: pending_hint_mode is cleared by the trailing optionals.)
Carry a skipped Optional's IntroProse hint: walk_optional stashes the
inner's key into a new WalkContext.surviving_intro_hint (key + position)
before the empty match clears pending_hint_mode; the snapshot keeps it
only when the skip position is the cursor (so it never leaks past a
later-consumed `set …` clause, nor once the count is given); the
resolver returns it ahead of the empty-expected short-circuit. The seed
count is wrapped Hinted{IntroProse("hint.seed_count")}; the prose names
the count (default 20), the `.column` column-fill form, and `set` /
`--seed`. Tab still cycles the keywords.
Only IntroProse is carried; ProseOnly/ForceProse and the CREATE-TABLE
element (a required Repeated) are untouched. No AmbientHint/renderer
change. Fires in both modes.
ADR-0022 Amendment 7; +3 tests.
This commit is contained in:
@@ -1356,6 +1356,93 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
fn seed_cache() -> crate::completion::SchemaCache {
|
||||
use crate::completion::TableColumn;
|
||||
use crate::dsl::types::Type;
|
||||
let mut cache = crate::completion::SchemaCache::default();
|
||||
cache.tables.push("users".to_string());
|
||||
cache.columns.push("email".to_string());
|
||||
cache
|
||||
.table_columns
|
||||
.insert("users".to_string(), vec![TableColumn::new("email", Type::Text)]);
|
||||
cache
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seed_count_is_advertised_at_the_optional_position() {
|
||||
// Issue #26: `seed users ▮` is a complete command, so the hint
|
||||
// ladder shows only the `set` / `--seed` continuation chips —
|
||||
// the optional row count (a bare number with no candidate) was
|
||||
// invisible. An IntroProse hint that survives the trailing
|
||||
// optionals now advertises it; Tab still cycles the keywords.
|
||||
let cache = seed_cache();
|
||||
let input = "seed users ";
|
||||
match ambient_hint_in_mode(input, input.len(), None, &cache, Mode::Simple) {
|
||||
Some(AmbientHint::Prose(p)) => {
|
||||
assert!(
|
||||
p.contains("row count") && p.contains("20"),
|
||||
"prose must mention the row count and the default; got: {p:?}",
|
||||
);
|
||||
assert!(
|
||||
p.contains("set") && p.contains("--seed") && p.contains(".column"),
|
||||
"prose should fold in the keyword + column-fill options; got: {p:?}",
|
||||
);
|
||||
}
|
||||
other => panic!("expected a Prose count hint; got: {other:?}"),
|
||||
}
|
||||
// Tab candidates remain available (completion is independent).
|
||||
let comp = crate::completion::candidates_at_cursor_in_mode(
|
||||
input, input.len(), &cache, Mode::Simple,
|
||||
)
|
||||
.expect("completion remains available");
|
||||
let texts: Vec<&str> = comp.candidates.iter().map(|c| c.text.as_str()).collect();
|
||||
assert!(
|
||||
texts.contains(&"set") && texts.contains(&"--seed"),
|
||||
"Tab must still cycle `set` / `--seed`; got {texts:?}",
|
||||
);
|
||||
|
||||
// `seed` runs in both modes (ADR-0048), so the hint must fire in
|
||||
// advanced mode too — not only simple.
|
||||
match ambient_hint_in_mode(input, input.len(), None, &cache, Mode::Advanced) {
|
||||
Some(AmbientHint::Prose(p)) => assert!(
|
||||
p.contains("row count"),
|
||||
"count hint must also fire in advanced mode; got: {p:?}",
|
||||
),
|
||||
other => panic!("expected the count hint in advanced mode; got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seed_count_hint_does_not_leak_once_the_count_or_a_clause_is_given() {
|
||||
// Position guard: the hint shows only while the cursor sits at
|
||||
// the count slot. Once the count is supplied — or a later clause
|
||||
// consumes input past it — it must not reappear.
|
||||
let cache = seed_cache();
|
||||
for input in ["seed users 50 ", "seed users set email = 'x' "] {
|
||||
let hint = ambient_hint_in_mode(input, input.len(), None, &cache, Mode::Simple);
|
||||
let is_count_prose = matches!(
|
||||
&hint,
|
||||
Some(AmbientHint::Prose(p)) if p.contains("row count")
|
||||
);
|
||||
assert!(!is_count_prose, "count hint must not show for {input:?}; got {hint:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn seed_count_hint_also_fires_after_a_column_fill_target() {
|
||||
// The count is valid after `seed users.email` too, so the hint
|
||||
// fires there — `.email` is a real column (no diagnostic).
|
||||
let cache = seed_cache();
|
||||
let input = "seed users.email ";
|
||||
match ambient_hint_in_mode(input, input.len(), None, &cache, Mode::Simple) {
|
||||
Some(AmbientHint::Prose(p)) => assert!(
|
||||
p.contains("row count"),
|
||||
"count hint expected after a column-fill target; got: {p:?}",
|
||||
),
|
||||
other => panic!("expected a Prose count hint; got: {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn genuine_column_typo_in_complete_select_still_hints_via_diagnostic() {
|
||||
// Issue #6 trade-off lockdown: dropping the typing-time
|
||||
|
||||
Reference in New Issue
Block a user