diff --git a/src/input_render.rs b/src/input_render.rs index 1b80d43..fb01cd8 100644 --- a/src/input_render.rs +++ b/src/input_render.rs @@ -24,6 +24,7 @@ use ratatui::style::{Modifier, Style}; +use crate::dsl::parser::parse_command_with_schema; use crate::dsl::walker; use crate::dsl::{ParseError, parse_command}; use crate::theme::Theme; @@ -66,7 +67,7 @@ pub fn render_input_runs( cache: &crate::completion::SchemaCache, ) -> Vec { let mut runs = lex_to_runs(input, theme); - if let InputState::DefiniteErrorAt(pos) = classify_input(input) { + if let InputState::DefiniteErrorAt(pos) = classify_input_with_schema(input, cache) { overlay_error(&mut runs, pos, theme); } if let Some(inv) = crate::completion::invalid_ident_at_cursor(input, cursor_byte, cache) { @@ -98,13 +99,45 @@ pub enum InputState { } /// Classify `input` into one of the three mid-typing states. -/// Cheap (lex + parse) per ADR-0022 §13. +/// +/// Schemaless. Wrong-count / wrong-type value-list cases that +/// only the schema-aware parser would catch surface as `Valid` +/// here. For UX-correct classification at typing time prefer +/// [`classify_input_with_schema`] — `render_input_runs` always +/// does. Kept public because handoff-11/12 regression tests use +/// it for schema-independent assertions (cheap, predictable). +/// +/// ADR-0022 §13: cheap (lex + parse). #[must_use] pub fn classify_input(input: &str) -> InputState { if input.trim().is_empty() { return InputState::Empty; } - match parse_command(input) { + classify_parse_result(parse_command(input)) +} + +/// Schema-aware variant of [`classify_input`]. +/// +/// Threads the `SchemaCache` through `parse_command_with_schema` +/// so that typed-slot rejections (Phase D — wrong-count Form B +/// value lists, wrong-type column values, etc.) surface as +/// `DefiniteErrorAt`/`IncompleteAtEof` at typing time, before +/// the user submits. +#[must_use] +pub fn classify_input_with_schema( + input: &str, + cache: &crate::completion::SchemaCache, +) -> InputState { + if input.trim().is_empty() { + return InputState::Empty; + } + classify_parse_result(parse_command_with_schema(input, cache)) +} + +fn classify_parse_result( + result: Result, +) -> InputState { + match result { Ok(_) => InputState::Valid, Err(ParseError::Empty) => InputState::Empty, Err(err @ ParseError::Invalid { position, .. }) => {