Hint: pedagogical Form-A pointer at Form B's first value slot
Handoff-12 §2.2: Form B `insert into T values (…)` silently skips auto-generated columns from the value list, so a user who wants to set a serial/shortid column explicitly could only discover Form A by reading help. Now the hint at the first Form B value slot appends a note naming the skipped column(s) and pointing at the explicit-column form. hint_resolution_at_input derives the skipped columns from the post-walk WalkContext (Form B = no user_listed_columns + table has serial/shortid columns) and reports them on HintResolution; the note fires only at the first slot so it doesn't repeat at every comma. ambient_hint composes it onto the per-column prose.
This commit is contained in:
+114
-23
@@ -74,6 +74,15 @@ pub fn hint_mode_at_input_with_schema(
|
|||||||
pub struct HintResolution {
|
pub struct HintResolution {
|
||||||
pub mode: crate::dsl::grammar::HintMode,
|
pub mode: crate::dsl::grammar::HintMode,
|
||||||
pub column: Option<String>,
|
pub column: Option<String>,
|
||||||
|
/// Auto-generated columns (serial / shortid) that Form B
|
||||||
|
/// `insert into <T> values (…)` silently skips from the
|
||||||
|
/// value list (ADR-0018 §3). Populated *only* at the first
|
||||||
|
/// value slot of a Form B insert whose table has such
|
||||||
|
/// columns — empty everywhere else. The renderer appends a
|
||||||
|
/// pedagogical note pointing the user at Form A so the
|
||||||
|
/// skipped column is discoverable without reading help
|
||||||
|
/// (handoff-12 §2.2).
|
||||||
|
pub form_b_autogen_skipped: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Single-walk hint resolver (ADR-0024 §Phase D §typed-value-slots).
|
/// Single-walk hint resolver (ADR-0024 §Phase D §typed-value-slots).
|
||||||
@@ -89,16 +98,22 @@ pub fn hint_resolution_at_input(
|
|||||||
use crate::dsl::grammar::{HintMode, IdentSource};
|
use crate::dsl::grammar::{HintMode, IdentSource};
|
||||||
use crate::dsl::walker::outcome::Expectation;
|
use crate::dsl::walker::outcome::Expectation;
|
||||||
|
|
||||||
let (expected, pending_type, pending_column) =
|
let snap = expected_for_hint_snapshot(source, schema);
|
||||||
expected_for_hint_with_full_ctx(source, schema);
|
if snap.expected.is_empty() {
|
||||||
if expected.is_empty() {
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
let expected = snap.expected;
|
||||||
|
|
||||||
if let Some(ty) = pending_type {
|
if let Some(ty) = snap.pending_value_type {
|
||||||
return Some(HintResolution {
|
return Some(HintResolution {
|
||||||
mode: HintMode::ProseOnly(catalog_key_for_value_type(ty)),
|
mode: HintMode::ProseOnly(catalog_key_for_value_type(ty)),
|
||||||
column: pending_column,
|
form_b_autogen_skipped: form_b_autogen_skipped(
|
||||||
|
source,
|
||||||
|
snap.user_listed_columns.as_ref(),
|
||||||
|
snap.current_table_columns.as_ref(),
|
||||||
|
snap.pending_value_column.as_deref(),
|
||||||
|
),
|
||||||
|
column: snap.pending_value_column,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -116,6 +131,7 @@ pub fn hint_resolution_at_input(
|
|||||||
return Some(HintResolution {
|
return Some(HintResolution {
|
||||||
mode: HintMode::ProseOnly("hint.value_literal_slot"),
|
mode: HintMode::ProseOnly("hint.value_literal_slot"),
|
||||||
column: None,
|
column: None,
|
||||||
|
form_b_autogen_skipped: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,12 +148,60 @@ pub fn hint_resolution_at_input(
|
|||||||
return Some(HintResolution {
|
return Some(HintResolution {
|
||||||
mode: HintMode::ForceProse("hint.ambient_typing_name"),
|
mode: HintMode::ForceProse("hint.ambient_typing_name"),
|
||||||
column: None,
|
column: None,
|
||||||
|
form_b_autogen_skipped: Vec::new(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Auto-generated columns a Form B insert skips from its value
|
||||||
|
/// list — but only when the cursor sits at the *first* value
|
||||||
|
/// slot, so the pedagogical note fires once per command rather
|
||||||
|
/// than at every comma.
|
||||||
|
///
|
||||||
|
/// Returns empty unless: the command is an `insert`; no explicit
|
||||||
|
/// column list was given (Form B — `user_listed` is `None`); the
|
||||||
|
/// table has serial / shortid columns; and `pending_column` is
|
||||||
|
/// the first non-auto-generated column (the first slot).
|
||||||
|
fn form_b_autogen_skipped(
|
||||||
|
source: &str,
|
||||||
|
user_listed: Option<&Vec<String>>,
|
||||||
|
table_columns: Option<&Vec<crate::completion::TableColumn>>,
|
||||||
|
pending_column: Option<&str>,
|
||||||
|
) -> Vec<String> {
|
||||||
|
use crate::dsl::types::Type;
|
||||||
|
|
||||||
|
// Form A (explicit column list) and non-insert commands
|
||||||
|
// (`update T set …` value slots also leave user_listed
|
||||||
|
// None) are excluded — the note is insert-Form-B only.
|
||||||
|
if user_listed.is_some() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
if !source.trim_start().to_ascii_lowercase().starts_with("insert") {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
let Some(cols) = table_columns else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
let is_auto = |t: Type| matches!(t, Type::Serial | Type::ShortId);
|
||||||
|
let skipped: Vec<String> = cols
|
||||||
|
.iter()
|
||||||
|
.filter(|c| is_auto(c.user_type))
|
||||||
|
.map(|c| c.name.clone())
|
||||||
|
.collect();
|
||||||
|
if skipped.is_empty() {
|
||||||
|
return Vec::new();
|
||||||
|
}
|
||||||
|
// Fire only at the first value slot — i.e. when the slot's
|
||||||
|
// column is the first non-auto-generated column.
|
||||||
|
let first_non_auto = cols.iter().find(|c| !is_auto(c.user_type));
|
||||||
|
match (first_non_auto, pending_column) {
|
||||||
|
(Some(first), Some(pending)) if first.name == pending => skipped,
|
||||||
|
_ => Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn hint_mode_at_input_inner(
|
fn hint_mode_at_input_inner(
|
||||||
source: &str,
|
source: &str,
|
||||||
schema: Option<&crate::completion::SchemaCache>,
|
schema: Option<&crate::completion::SchemaCache>,
|
||||||
@@ -350,41 +414,62 @@ pub fn expected_at_input(source: &str) -> Vec<outcome::Expectation> {
|
|||||||
/// surfaced. Used by the hint resolver to distinguish "must
|
/// surfaced. Used by the hint resolver to distinguish "must
|
||||||
/// type more" from "could continue", and to dispatch per-type
|
/// type more" from "could continue", and to dispatch per-type
|
||||||
/// prose when the cursor is inside a typed value slot.
|
/// prose when the cursor is inside a typed value slot.
|
||||||
|
/// Post-walk snapshot the hint resolver needs: the strict
|
||||||
|
/// expected set plus the `WalkContext` fields that survive the
|
||||||
|
/// walk and feed per-column / pedagogical prose.
|
||||||
|
struct HintWalkSnapshot {
|
||||||
|
expected: Vec<outcome::Expectation>,
|
||||||
|
pending_value_type: Option<crate::dsl::types::Type>,
|
||||||
|
pending_value_column: Option<String>,
|
||||||
|
current_table_columns: Option<Vec<crate::completion::TableColumn>>,
|
||||||
|
/// `Some` when the input used Form A's explicit column list.
|
||||||
|
/// `None` for Form B (`insert into T values …`) and for
|
||||||
|
/// every non-insert command.
|
||||||
|
user_listed_columns: Option<Vec<String>>,
|
||||||
|
}
|
||||||
|
|
||||||
fn expected_for_hint_with_ctx(
|
fn expected_for_hint_with_ctx(
|
||||||
source: &str,
|
source: &str,
|
||||||
schema: Option<&crate::completion::SchemaCache>,
|
schema: Option<&crate::completion::SchemaCache>,
|
||||||
) -> (Vec<outcome::Expectation>, Option<crate::dsl::types::Type>) {
|
) -> (Vec<outcome::Expectation>, Option<crate::dsl::types::Type>) {
|
||||||
let (expected, ty, _col) = expected_for_hint_with_full_ctx(source, schema);
|
let snap = expected_for_hint_snapshot(source, schema);
|
||||||
(expected, ty)
|
(snap.expected, snap.pending_value_type)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn expected_for_hint_with_full_ctx(
|
fn expected_for_hint_snapshot(
|
||||||
source: &str,
|
source: &str,
|
||||||
schema: Option<&crate::completion::SchemaCache>,
|
schema: Option<&crate::completion::SchemaCache>,
|
||||||
) -> (
|
) -> HintWalkSnapshot {
|
||||||
Vec<outcome::Expectation>,
|
|
||||||
Option<crate::dsl::types::Type>,
|
|
||||||
Option<String>,
|
|
||||||
) {
|
|
||||||
use crate::dsl::grammar::REGISTRY;
|
use crate::dsl::grammar::REGISTRY;
|
||||||
|
|
||||||
if source.trim().is_empty() {
|
let entry_words = || -> Vec<outcome::Expectation> {
|
||||||
let expected = REGISTRY
|
REGISTRY
|
||||||
.iter()
|
.iter()
|
||||||
.map(|c| outcome::Expectation::Word(c.entry.primary))
|
.map(|c| outcome::Expectation::Word(c.entry.primary))
|
||||||
.collect();
|
.collect()
|
||||||
return (expected, None, None);
|
};
|
||||||
|
|
||||||
|
if source.trim().is_empty() {
|
||||||
|
return HintWalkSnapshot {
|
||||||
|
expected: entry_words(),
|
||||||
|
pending_value_type: None,
|
||||||
|
pending_value_column: None,
|
||||||
|
current_table_columns: None,
|
||||||
|
user_listed_columns: None,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
let mut ctx = schema.map_or_else(context::WalkContext::new, |s| {
|
let mut ctx = schema.map_or_else(context::WalkContext::new, |s| {
|
||||||
context::WalkContext::with_schema(s)
|
context::WalkContext::with_schema(s)
|
||||||
});
|
});
|
||||||
let (result, _cmd) = walk(source, outcome::WalkBound::EndOfInput, &mut ctx);
|
let (result, _cmd) = walk(source, outcome::WalkBound::EndOfInput, &mut ctx);
|
||||||
let Some(result) = result else {
|
let Some(result) = result else {
|
||||||
let expected = REGISTRY
|
return HintWalkSnapshot {
|
||||||
.iter()
|
expected: entry_words(),
|
||||||
.map(|c| outcome::Expectation::Word(c.entry.primary))
|
pending_value_type: None,
|
||||||
.collect();
|
pending_value_column: None,
|
||||||
return (expected, None, None);
|
current_table_columns: None,
|
||||||
|
user_listed_columns: None,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
let expected = match result.outcome {
|
let expected = match result.outcome {
|
||||||
outcome::WalkOutcome::Match { .. } | outcome::WalkOutcome::ValidationFailed { .. } => {
|
outcome::WalkOutcome::Match { .. } | outcome::WalkOutcome::ValidationFailed { .. } => {
|
||||||
@@ -393,7 +478,13 @@ fn expected_for_hint_with_full_ctx(
|
|||||||
outcome::WalkOutcome::Incomplete { expected, .. }
|
outcome::WalkOutcome::Incomplete { expected, .. }
|
||||||
| outcome::WalkOutcome::Mismatch { expected, .. } => expected,
|
| outcome::WalkOutcome::Mismatch { expected, .. } => expected,
|
||||||
};
|
};
|
||||||
(expected, ctx.pending_value_type, ctx.pending_value_column)
|
HintWalkSnapshot {
|
||||||
|
expected,
|
||||||
|
pending_value_type: ctx.pending_value_type,
|
||||||
|
pending_value_column: ctx.pending_value_column,
|
||||||
|
current_table_columns: ctx.current_table_columns,
|
||||||
|
user_listed_columns: ctx.user_listed_columns,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Public walk entry. `bound` is `EndOfInput` for parse;
|
/// Public walk entry. `bound` is `EndOfInput` for parse;
|
||||||
|
|||||||
@@ -150,6 +150,7 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
|||||||
("hint.value_slot_shortid", &[]),
|
("hint.value_slot_shortid", &[]),
|
||||||
("hint.value_slot_text", &[]),
|
("hint.value_slot_text", &[]),
|
||||||
("hint.value_slot_for_column", &["column", "detail"]),
|
("hint.value_slot_for_column", &["column", "detail"]),
|
||||||
|
("hint.value_slot_autogen_skipped", &["columns"]),
|
||||||
// ---- Parse error rendering ----
|
// ---- Parse error rendering ----
|
||||||
("parse.available_commands", &["commands"]),
|
("parse.available_commands", &["commands"]),
|
||||||
("parse.caret", &["padding"]),
|
("parse.caret", &["padding"]),
|
||||||
|
|||||||
@@ -312,6 +312,11 @@ hint:
|
|||||||
# actual column name so the user sees "for `Email`: Type a
|
# actual column name so the user sees "for `Email`: Type a
|
||||||
# quoted string …" instead of the generic type prose.
|
# quoted string …" instead of the generic type prose.
|
||||||
value_slot_for_column: "for `{column}`: {detail}"
|
value_slot_for_column: "for `{column}`: {detail}"
|
||||||
|
# Pedagogical note appended at the first value slot of a
|
||||||
|
# Form B `insert into T values (…)` when T has auto-generated
|
||||||
|
# columns the value list skips — points the user at the
|
||||||
|
# explicit-column form so the skipped column is discoverable.
|
||||||
|
value_slot_autogen_skipped: "({columns} auto-generated — skipped here; list columns explicitly, e.g. `insert into T (...) values (...)`, to set it.)"
|
||||||
|
|
||||||
parse:
|
parse:
|
||||||
# Wrapper around chumsky's structural error message. The
|
# Wrapper around chumsky's structural error message. The
|
||||||
|
|||||||
+21
-1
@@ -237,7 +237,8 @@ pub fn ambient_hint(
|
|||||||
// candidate completion (e.g. `n` → `null`) applies.
|
// candidate completion (e.g. `n` → `null`) applies.
|
||||||
if cursor_partial_is_empty(input, cursor) {
|
if cursor_partial_is_empty(input, cursor) {
|
||||||
let detail = crate::friendly::translate(key, &[]);
|
let detail = crate::friendly::translate(key, &[]);
|
||||||
let composed = match resolution.and_then(|r| r.column) {
|
let resolution = resolution.expect("matched on resolution.mode");
|
||||||
|
let mut composed = match resolution.column {
|
||||||
Some(column) => crate::t!(
|
Some(column) => crate::t!(
|
||||||
"hint.value_slot_for_column",
|
"hint.value_slot_for_column",
|
||||||
column = column,
|
column = column,
|
||||||
@@ -245,6 +246,25 @@ pub fn ambient_hint(
|
|||||||
),
|
),
|
||||||
None => detail,
|
None => detail,
|
||||||
};
|
};
|
||||||
|
// Form B pedagogical note: when the first value
|
||||||
|
// slot of `insert into T values (…)` is reached
|
||||||
|
// and T has auto-generated columns the value list
|
||||||
|
// skips, point the user at the explicit-column
|
||||||
|
// form so the skipped column is discoverable
|
||||||
|
// (handoff-12 §2.2).
|
||||||
|
if !resolution.form_b_autogen_skipped.is_empty() {
|
||||||
|
let columns = resolution
|
||||||
|
.form_b_autogen_skipped
|
||||||
|
.iter()
|
||||||
|
.map(|c| format!("`{c}`"))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
.join(", ");
|
||||||
|
composed.push(' ');
|
||||||
|
composed.push_str(&crate::t!(
|
||||||
|
"hint.value_slot_autogen_skipped",
|
||||||
|
columns = columns
|
||||||
|
));
|
||||||
|
}
|
||||||
return Some(AmbientHint::Prose(composed));
|
return Some(AmbientHint::Prose(composed));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,15 +40,18 @@ fn form_b_first_value_skips_serial_column() {
|
|||||||
let prose = hint_prose(&a).unwrap_or_else(|| {
|
let prose = hint_prose(&a).unwrap_or_else(|| {
|
||||||
panic!("expected Prose at first Form B value slot, got {:?}", a.hint)
|
panic!("expected Prose at first Form B value slot, got {:?}", a.hint)
|
||||||
});
|
});
|
||||||
// First column slot must name `Name`, not the skipped
|
// The value slot itself must be keyed on `Name` — the first
|
||||||
// `id`.
|
// non-auto column — not on the skipped `id`.
|
||||||
assert!(
|
assert!(
|
||||||
prose.contains("Name"),
|
prose.starts_with("for `Name`"),
|
||||||
"Form B should advance to first non-auto column `Name`, got prose: {prose:?}",
|
"Form B's first value slot should be for `Name`, got prose: {prose:?}",
|
||||||
);
|
);
|
||||||
|
// `id` appears only inside the trailing pedagogical note
|
||||||
|
// (`id` auto-generated — skipped here …), never as the slot
|
||||||
|
// the user is being prompted to fill.
|
||||||
assert!(
|
assert!(
|
||||||
!prose.contains("`id`"),
|
!prose.starts_with("for `id`"),
|
||||||
"Form B should NOT prompt for the auto-gen `id`, got prose: {prose:?}",
|
"Form B must not prompt for the auto-gen `id` as a value slot, got prose: {prose:?}",
|
||||||
);
|
);
|
||||||
crate::snap!("form_b_first_value_serial_pk", a);
|
crate::snap!("form_b_first_value_serial_pk", a);
|
||||||
}
|
}
|
||||||
@@ -218,6 +221,87 @@ fn form_b_text_pk_with_correct_values_parses() {
|
|||||||
// position.
|
// position.
|
||||||
// =========================================================
|
// =========================================================
|
||||||
|
|
||||||
|
// =========================================================
|
||||||
|
// Pedagogical Form-A pointer (handoff-12 §2.2).
|
||||||
|
//
|
||||||
|
// At the FIRST value slot of a Form B insert whose table has
|
||||||
|
// auto-generated columns, the hint must mention the skipped
|
||||||
|
// column(s) and point at the explicit-column form.
|
||||||
|
// =========================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn form_b_first_slot_mentions_skipped_serial_column() {
|
||||||
|
let schema = schema_serial_pk();
|
||||||
|
let a = assess_at_end("insert into Customers values (", &schema);
|
||||||
|
let prose = hint_prose(&a).unwrap_or_else(|| {
|
||||||
|
panic!("expected Prose at first Form B slot, got {:?}", a.hint)
|
||||||
|
});
|
||||||
|
// Names the skipped auto-gen column.
|
||||||
|
assert!(
|
||||||
|
prose.contains("`id`"),
|
||||||
|
"first-slot hint should mention skipped `id`, got: {prose:?}",
|
||||||
|
);
|
||||||
|
// Points at the explicit-column escape hatch.
|
||||||
|
assert!(
|
||||||
|
prose.contains("auto-generated") && prose.contains("list columns"),
|
||||||
|
"first-slot hint should explain the Form-A escape, got: {prose:?}",
|
||||||
|
);
|
||||||
|
crate::snap!("form_b_first_slot_skip_note", a);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn form_b_second_slot_omits_skip_note() {
|
||||||
|
// The note fires once, at the first slot only — not at
|
||||||
|
// every comma.
|
||||||
|
let schema = schema_serial_pk();
|
||||||
|
let a = assess_at_end(
|
||||||
|
"insert into Customers values ('Alice', ",
|
||||||
|
&schema,
|
||||||
|
);
|
||||||
|
let prose = hint_prose(&a).unwrap_or_else(|| {
|
||||||
|
panic!("expected Prose at second slot, got {:?}", a.hint)
|
||||||
|
});
|
||||||
|
assert!(
|
||||||
|
!prose.contains("auto-generated"),
|
||||||
|
"second-slot hint must NOT repeat the skip note, got: {prose:?}",
|
||||||
|
);
|
||||||
|
crate::snap!("form_b_second_slot_no_skip_note", a);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn form_b_text_pk_has_no_skip_note() {
|
||||||
|
// No auto-gen columns → no skip note.
|
||||||
|
let schema = schema_text_pk();
|
||||||
|
let a = assess_at_end("insert into Items values (", &schema);
|
||||||
|
let prose = hint_prose(&a).unwrap_or_else(|| {
|
||||||
|
panic!("expected Prose, got {:?}", a.hint)
|
||||||
|
});
|
||||||
|
assert!(
|
||||||
|
!prose.contains("auto-generated"),
|
||||||
|
"text-PK table has no auto-gen column — no skip note expected, got: {prose:?}",
|
||||||
|
);
|
||||||
|
crate::snap!("form_b_text_pk_no_skip_note", a);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn form_a_first_slot_has_no_skip_note() {
|
||||||
|
// Form A lists columns explicitly — the user is in control,
|
||||||
|
// no pedagogical pointer needed.
|
||||||
|
let schema = schema_serial_pk();
|
||||||
|
let a = assess_at_end(
|
||||||
|
"insert into Customers (Name) values (",
|
||||||
|
&schema,
|
||||||
|
);
|
||||||
|
let prose = hint_prose(&a).unwrap_or_else(|| {
|
||||||
|
panic!("expected Prose, got {:?}", a.hint)
|
||||||
|
});
|
||||||
|
assert!(
|
||||||
|
!prose.contains("auto-generated"),
|
||||||
|
"Form A must not show the Form-B skip note, got: {prose:?}",
|
||||||
|
);
|
||||||
|
crate::snap!("form_a_no_skip_note", a);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn form_b_advances_through_every_type_first_to_real() {
|
fn form_b_advances_through_every_type_first_to_real() {
|
||||||
// Things' second column is `r:real`. After typing the
|
// Things' second column is `r:real`. After typing the
|
||||||
|
|||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
source: tests/typing_surface/insert_form_b.rs
|
||||||
|
description: "input=\"insert into Customers (Name) values (\" cursor=37"
|
||||||
|
expression: "& a"
|
||||||
|
---
|
||||||
|
Assessment {
|
||||||
|
input: "insert into Customers (Name) values (",
|
||||||
|
cursor: 37,
|
||||||
|
state: IncompleteAtEof,
|
||||||
|
hint: Some(
|
||||||
|
Prose(
|
||||||
|
"for `Name`: Type a quoted string (e.g. 'Alice') or null",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
completion: Some(
|
||||||
|
Completion {
|
||||||
|
replaced_range: (
|
||||||
|
37,
|
||||||
|
37,
|
||||||
|
),
|
||||||
|
partial_prefix: "",
|
||||||
|
candidates: [
|
||||||
|
Candidate {
|
||||||
|
text: "null",
|
||||||
|
kind: Keyword,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
parse_result: Err(
|
||||||
|
"Invalid(at_eof)",
|
||||||
|
),
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
source: tests/typing_surface/insert_form_b.rs
|
||||||
|
description: "input=\"insert into Customers values (\" cursor=30"
|
||||||
|
expression: "& a"
|
||||||
|
---
|
||||||
|
Assessment {
|
||||||
|
input: "insert into Customers values (",
|
||||||
|
cursor: 30,
|
||||||
|
state: IncompleteAtEof,
|
||||||
|
hint: Some(
|
||||||
|
Prose(
|
||||||
|
"for `Name`: Type a quoted string (e.g. 'Alice') or null (`id` auto-generated — skipped here; list columns explicitly, e.g. `insert into T (...) values (...)`, to set it.)",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
completion: Some(
|
||||||
|
Completion {
|
||||||
|
replaced_range: (
|
||||||
|
30,
|
||||||
|
30,
|
||||||
|
),
|
||||||
|
partial_prefix: "",
|
||||||
|
candidates: [
|
||||||
|
Candidate {
|
||||||
|
text: "null",
|
||||||
|
kind: Keyword,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
parse_result: Err(
|
||||||
|
"Invalid(at_eof)",
|
||||||
|
),
|
||||||
|
}
|
||||||
+1
-1
@@ -9,7 +9,7 @@ Assessment {
|
|||||||
state: IncompleteAtEof,
|
state: IncompleteAtEof,
|
||||||
hint: Some(
|
hint: Some(
|
||||||
Prose(
|
Prose(
|
||||||
"for `k`: Type an integer (e.g. 42, -7) or null",
|
"for `k`: Type an integer (e.g. 42, -7) or null (`sid`, `auto` auto-generated — skipped here; list columns explicitly, e.g. `insert into T (...) values (...)`, to set it.)",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
completion: Some(
|
completion: Some(
|
||||||
|
|||||||
+1
-1
@@ -9,7 +9,7 @@ Assessment {
|
|||||||
state: IncompleteAtEof,
|
state: IncompleteAtEof,
|
||||||
hint: Some(
|
hint: Some(
|
||||||
Prose(
|
Prose(
|
||||||
"for `Name`: Type a quoted string (e.g. 'Alice') or null",
|
"for `Name`: Type a quoted string (e.g. 'Alice') or null (`id` auto-generated — skipped here; list columns explicitly, e.g. `insert into T (...) values (...)`, to set it.)",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
completion: Some(
|
completion: Some(
|
||||||
|
|||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
source: tests/typing_surface/insert_form_b.rs
|
||||||
|
description: "input=\"insert into Customers values ('Alice', \" cursor=39"
|
||||||
|
expression: "& a"
|
||||||
|
---
|
||||||
|
Assessment {
|
||||||
|
input: "insert into Customers values ('Alice', ",
|
||||||
|
cursor: 39,
|
||||||
|
state: IncompleteAtEof,
|
||||||
|
hint: Some(
|
||||||
|
Prose(
|
||||||
|
"for `Email`: Type a quoted string (e.g. 'Alice') or null",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
completion: Some(
|
||||||
|
Completion {
|
||||||
|
replaced_range: (
|
||||||
|
39,
|
||||||
|
39,
|
||||||
|
),
|
||||||
|
partial_prefix: "",
|
||||||
|
candidates: [
|
||||||
|
Candidate {
|
||||||
|
text: "null",
|
||||||
|
kind: Keyword,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
parse_result: Err(
|
||||||
|
"Invalid(at_eof)",
|
||||||
|
),
|
||||||
|
}
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
---
|
||||||
|
source: tests/typing_surface/insert_form_b.rs
|
||||||
|
description: "input=\"insert into Items values (\" cursor=26"
|
||||||
|
expression: "& a"
|
||||||
|
---
|
||||||
|
Assessment {
|
||||||
|
input: "insert into Items values (",
|
||||||
|
cursor: 26,
|
||||||
|
state: IncompleteAtEof,
|
||||||
|
hint: Some(
|
||||||
|
Prose(
|
||||||
|
"for `Code`: Type a quoted string (e.g. 'Alice') or null",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
completion: Some(
|
||||||
|
Completion {
|
||||||
|
replaced_range: (
|
||||||
|
26,
|
||||||
|
26,
|
||||||
|
),
|
||||||
|
partial_prefix: "",
|
||||||
|
candidates: [
|
||||||
|
Candidate {
|
||||||
|
text: "null",
|
||||||
|
kind: Keyword,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
parse_result: Err(
|
||||||
|
"Invalid(at_eof)",
|
||||||
|
),
|
||||||
|
}
|
||||||
+1
-1
@@ -11,7 +11,7 @@ Assessment {
|
|||||||
),
|
),
|
||||||
hint: Some(
|
hint: Some(
|
||||||
Prose(
|
Prose(
|
||||||
"for `Name`: Type a quoted string (e.g. 'Alice') or null",
|
"for `Name`: Type a quoted string (e.g. 'Alice') or null (`id` auto-generated — skipped here; list columns explicitly, e.g. `insert into T (...) values (...)`, to set it.)",
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
completion: Some(
|
completion: Some(
|
||||||
|
|||||||
Reference in New Issue
Block a user