app: mode-threaded completion, overlay, and validity indicator
The dispatch-layer mode gate (previous commit) made the submit behaviour correct — `select` runs in advanced mode and shows the SQL hint in simple mode. This commit extends that gating to the ambient assistance layer so simple-mode users do not see SQL leak through Tab completion, the live error overlay, or the `[ERR]`/`[WRN]` validity indicator either. `_in_mode` walker variants -------------------------- - `completion_probe_in_mode`, `expected_at_input_in_mode`, `input_verdict_in_mode`. Each sets `ctx.mode` before walking. The empty-input / unknown-entry fallback in `completion_probe` and `expected_at_input` filters the `REGISTRY` listing by `is_advanced_only` so Tab does not offer `select` in simple mode. Old signatures keep delegating to `Mode::Advanced` (back-compat for tests + other callers). `_in_mode` completion variants ------------------------------ - `candidates_at_cursor_in_mode`, `candidates_at_cursor_with_in_mode`. Internally they route the `parse_command` completeness probe through `parse_command_in_mode(input, mode)`, the `completion_probe` call through `completion_probe_in_mode`, and the `expected_at` fallback through `expected_at_input_in_mode`. Old signatures default to `Mode::Advanced`. `EffectiveMode::as_mode` ------------------------ - Collapses the persistent / one-shot distinction the UI cares about into the plain `Mode` the walker reads from `WalkContext::mode`. App-level call sites that thread mode into the walker chain use this. App / input-render wiring ------------------------- - `App::input_validity_verdict` runs only when effective mode is plain `Simple` (per ADR-0027), so it hardcodes `Mode::Simple` into the new `input_verdict_in_mode` call rather than threading. - `App::start_or_complete_at` / `_last` (the Tab handlers) pass `self.effective_mode().as_mode()` into `candidates_at_cursor_in_mode`, so a `:` one-shot or persistent advanced gives full SQL completion, persistent simple does not offer SQL. - `input_render::render_input_runs` and `ambient_hint` are invoked from `ui.rs` only when effective mode is plain `Simple` (advanced rendering uses `plain_input_spans` and skips ambient hinting per ADR-0022 §12). Their internal `classify_input_with_schema` / `candidates_at_cursor` / `parse_command` calls now go through the mode-aware variants with `Mode::Simple` hardcoded — a SQL form in simple mode surfaces as a definite-error overlay and the hint panel does not offer it. After this commit a simple-mode user typing `select` or `sel<Tab>` sees nothing SQL-shaped: no live highlight, no Tab completion candidate, the `[ERR]` indicator lit, and the on- submit hint that names the recovery paths. An advanced-mode user or a `:` one-shot sees the full SQL surface.
This commit is contained in:
+39
-10
@@ -18,6 +18,8 @@ use crate::dsl::grammar::IdentSource;
|
||||
use crate::dsl::types::Type;
|
||||
use crate::dsl::walker::outcome::Expectation;
|
||||
use crate::dsl::{ParseError, parse_command};
|
||||
use crate::dsl::parser::parse_command_in_mode;
|
||||
use crate::mode::Mode;
|
||||
|
||||
/// Composite literal candidates whose lexed shape is more than
|
||||
/// one token but which the user types as a single fluent piece.
|
||||
@@ -97,8 +99,8 @@ impl SchemaCache {
|
||||
/// expressed as structured `Expectation`s direct from the
|
||||
/// walker (ADR-0024 §architecture, Phase F walker-driven
|
||||
/// completion). Replaces the `ParseError`-string round-trip.
|
||||
fn expected_at(leading: &str) -> Vec<Expectation> {
|
||||
crate::dsl::walker::expected_at_input(leading)
|
||||
fn expected_at(leading: &str, mode: Mode) -> Vec<Expectation> {
|
||||
crate::dsl::walker::expected_at_input_in_mode(leading, mode)
|
||||
}
|
||||
|
||||
/// A single Tab-insertable item with its source (so the
|
||||
@@ -184,7 +186,20 @@ pub fn candidates_at_cursor(
|
||||
cursor: usize,
|
||||
cache: &SchemaCache,
|
||||
) -> Option<Completion> {
|
||||
candidates_at_cursor_with(input, cursor, cache, identity_ranker)
|
||||
candidates_at_cursor_in_mode(input, cursor, cache, Mode::Advanced)
|
||||
}
|
||||
|
||||
/// Mode-aware [`candidates_at_cursor`] (ADR-0030 §2). Tab in
|
||||
/// simple mode no longer offers advanced-mode-only commands;
|
||||
/// the walker's mode gate flows through the probe inside.
|
||||
#[must_use]
|
||||
pub fn candidates_at_cursor_in_mode(
|
||||
input: &str,
|
||||
cursor: usize,
|
||||
cache: &SchemaCache,
|
||||
mode: Mode,
|
||||
) -> Option<Completion> {
|
||||
candidates_at_cursor_with_in_mode(input, cursor, cache, identity_ranker, mode)
|
||||
}
|
||||
|
||||
/// Variant of [`candidates_at_cursor`] that applies a custom
|
||||
@@ -197,6 +212,18 @@ pub fn candidates_at_cursor_with(
|
||||
cursor: usize,
|
||||
cache: &SchemaCache,
|
||||
ranker: Ranker,
|
||||
) -> Option<Completion> {
|
||||
candidates_at_cursor_with_in_mode(input, cursor, cache, ranker, Mode::Advanced)
|
||||
}
|
||||
|
||||
/// Mode-aware [`candidates_at_cursor_with`].
|
||||
#[must_use]
|
||||
pub fn candidates_at_cursor_with_in_mode(
|
||||
input: &str,
|
||||
cursor: usize,
|
||||
cache: &SchemaCache,
|
||||
ranker: Ranker,
|
||||
mode: Mode,
|
||||
) -> Option<Completion> {
|
||||
let cursor = cursor.min(input.len());
|
||||
|
||||
@@ -225,20 +252,22 @@ pub fn candidates_at_cursor_with(
|
||||
// `pk` at the end of `create table T with pk`. The
|
||||
// optional-suffix case (`save ` → `as`) is preserved
|
||||
// because there `partial_prefix` is empty.
|
||||
let input_parses_complete = parse_command(input).is_ok();
|
||||
let input_parses_complete = parse_command_in_mode(input, mode).is_ok();
|
||||
|
||||
// Schema-aware probe: one walk yields both the expected set
|
||||
// and the table-context snapshot (ADR-0024 §Phase D
|
||||
// §column-narrowing). The engine reads
|
||||
// `current_table_columns` to narrow column candidates to the
|
||||
// active table rather than the flat `cache.columns` (which
|
||||
// unions every table's columns).
|
||||
let probe = crate::dsl::walker::completion_probe(leading, cache);
|
||||
// unions every table's columns). ADR-0030 §2: the probe runs
|
||||
// with the active mode so simple-mode users don't see SQL
|
||||
// commands offered.
|
||||
let probe = crate::dsl::walker::completion_probe_in_mode(leading, cache, mode);
|
||||
let current_table_columns: Option<&[TableColumn]> =
|
||||
probe.current_table_columns.as_deref();
|
||||
|
||||
let expected = if probe.expected.is_empty() {
|
||||
expected_at(leading)
|
||||
expected_at(leading, mode)
|
||||
} else {
|
||||
probe.expected.clone()
|
||||
};
|
||||
@@ -548,7 +577,7 @@ pub fn value_literal_hint_at_cursor(input: &str, cursor: usize) -> Option<String
|
||||
return None;
|
||||
}
|
||||
let leading = &input[..start];
|
||||
let expected = expected_at(leading);
|
||||
let expected = expected_at(leading, Mode::Advanced);
|
||||
if !is_value_literal_signature(&expected) {
|
||||
return None;
|
||||
}
|
||||
@@ -608,7 +637,7 @@ pub fn typing_name_at_cursor(input: &str, cursor: usize) -> Option<TypingName> {
|
||||
}
|
||||
}
|
||||
let leading = &input[..start];
|
||||
let expected = expected_at(leading);
|
||||
let expected = expected_at(leading, Mode::Advanced);
|
||||
let is_new_name_slot = expected.iter().any(|e| {
|
||||
matches!(
|
||||
e,
|
||||
@@ -697,7 +726,7 @@ pub fn invalid_ident_at_cursor(
|
||||
return None;
|
||||
}
|
||||
let leading = &input[..start];
|
||||
let expected = expected_at(leading);
|
||||
let expected = expected_at(leading, Mode::Advanced);
|
||||
if expected.is_empty() {
|
||||
return None;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user