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:
@@ -772,6 +772,58 @@ invalid_ident_does_not_fire_for_column_prefix_at_sql_expr_slot}`;
|
|||||||
`theme::function_colour_is_distinct_from_keyword_identifier_and_type`.
|
`theme::function_colour_is_distinct_from_keyword_identifier_and_type`.
|
||||||
See ADR-0031's status note for the grammar-side anchor.
|
See ADR-0031's status note for the grammar-side anchor.
|
||||||
|
|
||||||
|
## Amendment 7 — optional positional args reach the hint panel (2026-06-12)
|
||||||
|
|
||||||
|
Issue #26. At `seed <table> ▮` the hint panel showed only the
|
||||||
|
`set` / `--seed` continuation chips and never mentioned the
|
||||||
|
**optional row count** — even though a count (`seed users 50`) is
|
||||||
|
the most common next move. The count is a bare positional
|
||||||
|
`NumberLit` with no keyword/candidate text, so the candidate ladder
|
||||||
|
can't surface it; and `seed <table>` is already a *complete*
|
||||||
|
command, so the hint resolver short-circuits (empty expected set).
|
||||||
|
|
||||||
|
The existing `IntroProse` `HintMode` (ADR-0024 §HintMode-per-node;
|
||||||
|
issue #4's CREATE-TABLE element hint) is the right tool — it shows
|
||||||
|
prose that *introduces* a position whose first-class move has no
|
||||||
|
candidate, with the keyword alternatives folded into the prose and
|
||||||
|
Tab still cycling them. But it did not reach this position: a
|
||||||
|
`Node::Hinted`'s mode lives in `pending_hint_mode`, which the very
|
||||||
|
next match clears — including the **empty** match of a skipped
|
||||||
|
`Optional`. The CREATE-TABLE element survives only because it sits
|
||||||
|
in a *required* `Repeated(min:1)`; an optional positional followed
|
||||||
|
by more optionals (the seed count) is cleared before the resolver
|
||||||
|
reads it.
|
||||||
|
|
||||||
|
### Mechanism
|
||||||
|
|
||||||
|
A small, general carry: when `walk_optional` skips its inner (the
|
||||||
|
inner didn't engage), it stashes any `IntroProse` key the inner
|
||||||
|
left in `pending_hint_mode` into a new `WalkContext` field,
|
||||||
|
`surviving_intro_hint: Option<(key, position)>`, **before** the
|
||||||
|
empty match clears `pending_hint_mode`. The trailing optionals,
|
||||||
|
which are not `IntroProse`, don't overwrite it. The hint snapshot
|
||||||
|
keeps the key **only when `position == cursor`** (the slice end),
|
||||||
|
so it shows while the cursor sits at the count slot but not once a
|
||||||
|
later clause (`set …`) consumes input past it, nor once the count
|
||||||
|
itself is supplied. The resolver returns that `IntroProse` even for
|
||||||
|
an otherwise-complete command (ahead of the empty-expected
|
||||||
|
short-circuit).
|
||||||
|
|
||||||
|
The seed grammar wraps the count in
|
||||||
|
`Hinted { IntroProse("hint.seed_count"), NumberLit }`; the prose
|
||||||
|
names the count (with its default 20) plus the `.column`
|
||||||
|
column-fill form and the `set` / `--seed` keywords (user-chosen
|
||||||
|
scope: mention every option). Only `IntroProse` is carried —
|
||||||
|
`ProseOnly` / `ForceProse` mark *active* slots and reach the
|
||||||
|
resolver through the normal path, unchanged. The CREATE-TABLE
|
||||||
|
element (in a `Repeated`, not an `Optional`) is untouched.
|
||||||
|
|
||||||
|
This is a refinement of ADR-0024 §HintMode-per-node and a sibling
|
||||||
|
of issue #4; no `AmbientHint` / renderer change. Covered by
|
||||||
|
`input_render::{seed_count_is_advertised_at_the_optional_position,
|
||||||
|
seed_count_hint_does_not_leak_once_the_count_or_a_clause_is_given,
|
||||||
|
seed_count_hint_also_fires_after_a_column_fill_target}`.
|
||||||
|
|
||||||
## Out of scope
|
## Out of scope
|
||||||
|
|
||||||
Deliberately deferred to keep this ADR shippable as a single
|
Deliberately deferred to keep this ADR shippable as a single
|
||||||
|
|||||||
+1
-1
File diff suppressed because one or more lines are too long
+12
-1
@@ -438,6 +438,17 @@ const LIMIT_CLAUSE: Node = Node::Seq(LIMIT_CLAUSE_NODES);
|
|||||||
const SEED_COUNT: Node = Node::NumberLit {
|
const SEED_COUNT: Node = Node::NumberLit {
|
||||||
validator: Some(LIMIT_VALIDATOR),
|
validator: Some(LIMIT_VALIDATOR),
|
||||||
};
|
};
|
||||||
|
/// Issue #26: the row count is a bare positional number, so it produces
|
||||||
|
/// no Tab candidate and was invisible in the hint panel at
|
||||||
|
/// `seed <table> ▮` (only `set` / `--seed` showed). Wrapping it in
|
||||||
|
/// `IntroProse` advertises it (and the other options) in prose; the
|
||||||
|
/// skipped-optional carry (`surviving_intro_hint`) makes the hint reach
|
||||||
|
/// the resolver despite the trailing optionals. Tab still cycles the
|
||||||
|
/// keyword candidates.
|
||||||
|
const SEED_COUNT_HINTED: Node = Node::Hinted {
|
||||||
|
mode: crate::dsl::grammar::HintMode::IntroProse("hint.seed_count"),
|
||||||
|
inner: &SEED_COUNT,
|
||||||
|
};
|
||||||
/// `--seed <n>` — a reproducible-generation flag carrying a numeric
|
/// `--seed <n>` — a reproducible-generation flag carrying a numeric
|
||||||
/// seed (ADR-0048 D4). The only flag in the DSL that takes a value;
|
/// seed (ADR-0048 D4). The only flag in the DSL that takes a value;
|
||||||
/// `build_seed` reads the number immediately after the flag.
|
/// `build_seed` reads the number immediately after the flag.
|
||||||
@@ -567,7 +578,7 @@ const SEED_NODES: &[Node] = &[
|
|||||||
// against this table.
|
// against this table.
|
||||||
TABLE_NAME_WRITES,
|
TABLE_NAME_WRITES,
|
||||||
SEED_DOT_COLUMN,
|
SEED_DOT_COLUMN,
|
||||||
Node::Optional(&SEED_COUNT),
|
Node::Optional(&SEED_COUNT_HINTED),
|
||||||
Node::Optional(&SEED_SET_CLAUSE),
|
Node::Optional(&SEED_SET_CLAUSE),
|
||||||
Node::Optional(&SEED_FLAG),
|
Node::Optional(&SEED_FLAG),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -134,6 +134,17 @@ pub struct WalkContext<'a> {
|
|||||||
/// resolver reads this directly instead of inferring the
|
/// resolver reads this directly instead of inferring the
|
||||||
/// slot kind from the shape of the expected set.
|
/// slot kind from the shape of the expected set.
|
||||||
pub pending_hint_mode: Option<crate::dsl::grammar::HintMode>,
|
pub pending_hint_mode: Option<crate::dsl::grammar::HintMode>,
|
||||||
|
/// An `IntroProse` hint captured from an *optional* slot that
|
||||||
|
/// the walk skipped (issue #26). Unlike `pending_hint_mode`
|
||||||
|
/// (cleared on the very next match — including the empty match
|
||||||
|
/// of a skipped `Optional`), this survives the trailing
|
||||||
|
/// optional siblings so the hint reaches the resolver for a
|
||||||
|
/// position like `seed <table> ▮`, where the optional row
|
||||||
|
/// count is otherwise invisible. Carries the catalog key and
|
||||||
|
/// the byte position the optional was skipped at; the resolver
|
||||||
|
/// uses it only when that position is the cursor (so it doesn't
|
||||||
|
/// leak past a later-consumed clause).
|
||||||
|
pub surviving_intro_hint: Option<(&'static str, usize)>,
|
||||||
/// The columns the user explicitly listed in
|
/// The columns the user explicitly listed in
|
||||||
/// `insert into <T> (col1, col2, …) values (…)` (Form A),
|
/// `insert into <T> (col1, col2, …) values (…)` (Form A),
|
||||||
/// in declaration order.
|
/// in declaration order.
|
||||||
@@ -232,6 +243,7 @@ impl<'a> WalkContext<'a> {
|
|||||||
pending_value_type: None,
|
pending_value_type: None,
|
||||||
pending_value_column: None,
|
pending_value_column: None,
|
||||||
pending_hint_mode: None,
|
pending_hint_mode: None,
|
||||||
|
surviving_intro_hint: None,
|
||||||
user_listed_columns: None,
|
user_listed_columns: None,
|
||||||
subgrammar_depth: 0,
|
subgrammar_depth: 0,
|
||||||
from_scope_stack: vec![ScopeFrame::default()],
|
from_scope_stack: vec![ScopeFrame::default()],
|
||||||
@@ -254,6 +266,7 @@ impl<'a> WalkContext<'a> {
|
|||||||
pending_value_type: None,
|
pending_value_type: None,
|
||||||
pending_value_column: None,
|
pending_value_column: None,
|
||||||
pending_hint_mode: None,
|
pending_hint_mode: None,
|
||||||
|
surviving_intro_hint: None,
|
||||||
user_listed_columns: None,
|
user_listed_columns: None,
|
||||||
subgrammar_depth: 0,
|
subgrammar_depth: 0,
|
||||||
from_scope_stack: vec![ScopeFrame::default()],
|
from_scope_stack: vec![ScopeFrame::default()],
|
||||||
|
|||||||
@@ -990,6 +990,21 @@ fn walk_seq(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Issue #26: when an `Optional` is skipped (its inner didn't engage),
|
||||||
|
/// stash any `IntroProse` hint the inner left in `pending_hint_mode`
|
||||||
|
/// into the surviving slot before it is cleared by this empty match.
|
||||||
|
/// `position` is where the optional was skipped — the resolver compares
|
||||||
|
/// it to the cursor so the hint only shows while the cursor sits at that
|
||||||
|
/// optional, not after a later clause consumes input past it. Only
|
||||||
|
/// `IntroProse` is carried (it is the "introduce an optional position"
|
||||||
|
/// mode); `ProseOnly` / `ForceProse` mark active slots and reach the
|
||||||
|
/// resolver through the normal `pending_hint_mode` path.
|
||||||
|
const fn capture_skipped_intro_hint(ctx: &mut WalkContext, position: usize) {
|
||||||
|
if let Some(crate::dsl::grammar::HintMode::IntroProse(key)) = ctx.pending_hint_mode {
|
||||||
|
ctx.surviving_intro_hint = Some((key, position));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn walk_optional(
|
fn walk_optional(
|
||||||
source: &str,
|
source: &str,
|
||||||
position: usize,
|
position: usize,
|
||||||
@@ -1008,6 +1023,7 @@ fn walk_optional(
|
|||||||
// Inner didn't engage at all — skip the Optional
|
// Inner didn't engage at all — skip the Optional
|
||||||
// but carry the inner's expectations so the caller's
|
// but carry the inner's expectations so the caller's
|
||||||
// expected-set sees them.
|
// expected-set sees them.
|
||||||
|
capture_skipped_intro_hint(ctx, position);
|
||||||
path.items.truncate(saved_path_len);
|
path.items.truncate(saved_path_len);
|
||||||
per_byte.truncate(saved_byte_len);
|
per_byte.truncate(saved_byte_len);
|
||||||
NodeWalkResult::Matched {
|
NodeWalkResult::Matched {
|
||||||
@@ -1019,6 +1035,7 @@ fn walk_optional(
|
|||||||
// Inner reported Incomplete without consuming
|
// Inner reported Incomplete without consuming
|
||||||
// anything — same as NoMatch from the user's
|
// anything — same as NoMatch from the user's
|
||||||
// perspective. Roll back and skip.
|
// perspective. Roll back and skip.
|
||||||
|
capture_skipped_intro_hint(ctx, position);
|
||||||
path.items.truncate(saved_path_len);
|
path.items.truncate(saved_path_len);
|
||||||
per_byte.truncate(saved_byte_len);
|
per_byte.truncate(saved_byte_len);
|
||||||
let _ = p;
|
let _ = p;
|
||||||
|
|||||||
@@ -116,6 +116,19 @@ pub fn hint_resolution_at_input_in_mode(
|
|||||||
use crate::dsl::grammar::HintMode;
|
use crate::dsl::grammar::HintMode;
|
||||||
|
|
||||||
let snap = expected_for_hint_snapshot(source, schema, mode);
|
let snap = expected_for_hint_snapshot(source, schema, mode);
|
||||||
|
// Issue #26: an optional positional slot with no candidate text
|
||||||
|
// (the `seed <table>` row count) left an `IntroProse` hint that
|
||||||
|
// survived the trailing optionals. It is shown even for an
|
||||||
|
// otherwise-complete command (empty expected set) — that is exactly
|
||||||
|
// the `seed users ▮` case where the count is invisible. Checked
|
||||||
|
// first, before the complete-command short-circuit below.
|
||||||
|
if let Some(key) = snap.surviving_intro_hint {
|
||||||
|
return Some(HintResolution {
|
||||||
|
mode: HintMode::IntroProse(key),
|
||||||
|
column: None,
|
||||||
|
form_b_autogen_skipped: Vec::new(),
|
||||||
|
});
|
||||||
|
}
|
||||||
// Empty expected set means the command is already complete
|
// Empty expected set means the command is already complete
|
||||||
// (`WalkOutcome::Match`) — no slot to hint at.
|
// (`WalkOutcome::Match`) — no slot to hint at.
|
||||||
if snap.expected.is_empty() {
|
if snap.expected.is_empty() {
|
||||||
@@ -2599,6 +2612,11 @@ struct HintWalkSnapshot {
|
|||||||
/// The grammar-declared `HintMode` at the cursor's slot
|
/// The grammar-declared `HintMode` at the cursor's slot
|
||||||
/// (`Node::Hinted` annotation, ADR-0024 §HintMode-per-node).
|
/// (`Node::Hinted` annotation, ADR-0024 §HintMode-per-node).
|
||||||
pending_hint_mode: Option<crate::dsl::grammar::HintMode>,
|
pending_hint_mode: Option<crate::dsl::grammar::HintMode>,
|
||||||
|
/// An `IntroProse` catalog key for an *optional* positional slot at
|
||||||
|
/// the cursor that produced no candidate (issue #26 — `seed <table>`
|
||||||
|
/// row count). Survives the trailing optional siblings that clear
|
||||||
|
/// `pending_hint_mode`; already filtered to the cursor position.
|
||||||
|
surviving_intro_hint: Option<&'static str>,
|
||||||
current_table_columns: Option<Vec<crate::completion::TableColumn>>,
|
current_table_columns: Option<Vec<crate::completion::TableColumn>>,
|
||||||
/// `Some` when the input used Form A's explicit column list.
|
/// `Some` when the input used Form A's explicit column list.
|
||||||
/// `None` for Form B (`insert into T values …`) and for
|
/// `None` for Form B (`insert into T values …`) and for
|
||||||
@@ -2625,6 +2643,7 @@ fn expected_for_hint_snapshot(
|
|||||||
pending_value_type: None,
|
pending_value_type: None,
|
||||||
pending_value_column: None,
|
pending_value_column: None,
|
||||||
pending_hint_mode: None,
|
pending_hint_mode: None,
|
||||||
|
surviving_intro_hint: None,
|
||||||
current_table_columns: None,
|
current_table_columns: None,
|
||||||
user_listed_columns: None,
|
user_listed_columns: None,
|
||||||
};
|
};
|
||||||
@@ -2652,6 +2671,14 @@ fn expected_for_hint_snapshot(
|
|||||||
pending_value_type: ctx.pending_value_type,
|
pending_value_type: ctx.pending_value_type,
|
||||||
pending_value_column: ctx.pending_value_column,
|
pending_value_column: ctx.pending_value_column,
|
||||||
pending_hint_mode: ctx.pending_hint_mode,
|
pending_hint_mode: ctx.pending_hint_mode,
|
||||||
|
// Issue #26: only surface the skipped-optional hint when the
|
||||||
|
// optional was skipped *at the cursor* (the end of the walked
|
||||||
|
// slice). Captured earlier (before a later clause consumed past
|
||||||
|
// it) → stale, so drop it.
|
||||||
|
surviving_intro_hint: ctx
|
||||||
|
.surviving_intro_hint
|
||||||
|
.filter(|(_, pos)| *pos == source.len())
|
||||||
|
.map(|(key, _)| key),
|
||||||
current_table_columns: ctx.current_table_columns,
|
current_table_columns: ctx.current_table_columns,
|
||||||
user_listed_columns: ctx.user_listed_columns,
|
user_listed_columns: ctx.user_listed_columns,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
|||||||
// slot (`create table T (`) so the otherwise-invisible
|
// slot (`create table T (`) so the otherwise-invisible
|
||||||
// column-name role reads as the dominant first move.
|
// column-name role reads as the dominant first move.
|
||||||
("hint.create_table_element", &[]),
|
("hint.create_table_element", &[]),
|
||||||
|
("hint.seed_count", &[]),
|
||||||
("hint.value_literal_slot", &[]),
|
("hint.value_literal_slot", &[]),
|
||||||
(
|
(
|
||||||
"hint.ambient_typing_name_then",
|
"hint.ambient_typing_name_then",
|
||||||
|
|||||||
@@ -400,6 +400,12 @@ hint:
|
|||||||
# at `create table T (` so the column-name role is visible
|
# at `create table T (` so the column-name role is visible
|
||||||
# alongside the table-level constraint keywords.
|
# alongside the table-level constraint keywords.
|
||||||
create_table_element: "Type a column name, or a table-level constraint: `primary`, `unique`, `check`, `constraint`, `foreign`"
|
create_table_element: "Type a column name, or a table-level constraint: `primary`, `unique`, `check`, `constraint`, `foreign`"
|
||||||
|
# Issue #26: the `seed <table> ▮` position. The optional row count is
|
||||||
|
# a bare number with no Tab candidate, so it (and the `.column`
|
||||||
|
# column-fill form) would be invisible next to the `set` / `--seed`
|
||||||
|
# chips. Names every option so the most common next move (a count) is
|
||||||
|
# discoverable.
|
||||||
|
seed_count: "Optionally a row count, e.g. `50` (default 20); `.column` to fill one column on existing rows; `set` to pin a column; `--seed` to fix the RNG"
|
||||||
# Value-literal slot — `insert ... values (`, `update ... set
|
# Value-literal slot — `insert ... values (`, `update ... set
|
||||||
# col=`, `where col=`. Replaces the misleading "null true
|
# col=`, `where col=`. Replaces the misleading "null true
|
||||||
# false" keyword candidate list with format guidance for all
|
# false" keyword candidate list with format guidance for all
|
||||||
|
|||||||
@@ -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]
|
#[test]
|
||||||
fn genuine_column_typo_in_complete_select_still_hints_via_diagnostic() {
|
fn genuine_column_typo_in_complete_select_still_hints_via_diagnostic() {
|
||||||
// Issue #6 trade-off lockdown: dropping the typing-time
|
// Issue #6 trade-off lockdown: dropping the typing-time
|
||||||
|
|||||||
Reference in New Issue
Block a user