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:
+25
-3
@@ -110,6 +110,18 @@ impl EffectiveMode {
|
|||||||
pub const fn is_advanced(self) -> bool {
|
pub const fn is_advanced(self) -> bool {
|
||||||
matches!(self, Self::AdvancedPersistent | Self::AdvancedOneShot)
|
matches!(self, Self::AdvancedPersistent | Self::AdvancedOneShot)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Collapse the persistent/one-shot distinction the UI cares
|
||||||
|
/// about into the plain [`Mode`] the walker reads from
|
||||||
|
/// `WalkContext::mode` (ADR-0030 §2). Both advanced variants
|
||||||
|
/// map to `Mode::Advanced`.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn as_mode(self) -> Mode {
|
||||||
|
match self {
|
||||||
|
Self::Simple => Mode::Simple,
|
||||||
|
Self::AdvancedPersistent | Self::AdvancedOneShot => Mode::Advanced,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -378,7 +390,15 @@ impl App {
|
|||||||
if !matches!(self.effective_mode(), EffectiveMode::Simple) {
|
if !matches!(self.effective_mode(), EffectiveMode::Simple) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
crate::dsl::walker::input_verdict(&self.input, Some(&self.schema_cache))
|
// ADR-0030 §2: the indicator is shown only in plain
|
||||||
|
// simple mode (the guard above), so the verdict reads
|
||||||
|
// the simple-mode walker view — a SQL form lights up
|
||||||
|
// ERROR via the walker's mode gate.
|
||||||
|
crate::dsl::walker::input_verdict_in_mode(
|
||||||
|
&self.input,
|
||||||
|
Some(&self.schema_cache),
|
||||||
|
Mode::Simple,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process one event from the runtime, mutating state and
|
/// Process one event from the runtime, mutating state and
|
||||||
@@ -782,10 +802,11 @@ impl App {
|
|||||||
|
|
||||||
fn start_or_complete_at(&mut self, multi_start_idx: usize) {
|
fn start_or_complete_at(&mut self, multi_start_idx: usize) {
|
||||||
let cursor = self.input_cursor.min(self.input.len());
|
let cursor = self.input_cursor.min(self.input.len());
|
||||||
let Some(comp) = crate::completion::candidates_at_cursor(
|
let Some(comp) = crate::completion::candidates_at_cursor_in_mode(
|
||||||
&self.input,
|
&self.input,
|
||||||
cursor,
|
cursor,
|
||||||
&self.schema_cache,
|
&self.schema_cache,
|
||||||
|
self.effective_mode().as_mode(),
|
||||||
) else {
|
) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -799,10 +820,11 @@ impl App {
|
|||||||
|
|
||||||
fn start_or_complete_last(&mut self) {
|
fn start_or_complete_last(&mut self) {
|
||||||
let cursor = self.input_cursor.min(self.input.len());
|
let cursor = self.input_cursor.min(self.input.len());
|
||||||
let Some(comp) = crate::completion::candidates_at_cursor(
|
let Some(comp) = crate::completion::candidates_at_cursor_in_mode(
|
||||||
&self.input,
|
&self.input,
|
||||||
cursor,
|
cursor,
|
||||||
&self.schema_cache,
|
&self.schema_cache,
|
||||||
|
self.effective_mode().as_mode(),
|
||||||
) else {
|
) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|||||||
+39
-10
@@ -18,6 +18,8 @@ use crate::dsl::grammar::IdentSource;
|
|||||||
use crate::dsl::types::Type;
|
use crate::dsl::types::Type;
|
||||||
use crate::dsl::walker::outcome::Expectation;
|
use crate::dsl::walker::outcome::Expectation;
|
||||||
use crate::dsl::{ParseError, parse_command};
|
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
|
/// Composite literal candidates whose lexed shape is more than
|
||||||
/// one token but which the user types as a single fluent piece.
|
/// 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
|
/// expressed as structured `Expectation`s direct from the
|
||||||
/// walker (ADR-0024 §architecture, Phase F walker-driven
|
/// walker (ADR-0024 §architecture, Phase F walker-driven
|
||||||
/// completion). Replaces the `ParseError`-string round-trip.
|
/// completion). Replaces the `ParseError`-string round-trip.
|
||||||
fn expected_at(leading: &str) -> Vec<Expectation> {
|
fn expected_at(leading: &str, mode: Mode) -> Vec<Expectation> {
|
||||||
crate::dsl::walker::expected_at_input(leading)
|
crate::dsl::walker::expected_at_input_in_mode(leading, mode)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A single Tab-insertable item with its source (so the
|
/// A single Tab-insertable item with its source (so the
|
||||||
@@ -184,7 +186,20 @@ pub fn candidates_at_cursor(
|
|||||||
cursor: usize,
|
cursor: usize,
|
||||||
cache: &SchemaCache,
|
cache: &SchemaCache,
|
||||||
) -> Option<Completion> {
|
) -> 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
|
/// Variant of [`candidates_at_cursor`] that applies a custom
|
||||||
@@ -197,6 +212,18 @@ pub fn candidates_at_cursor_with(
|
|||||||
cursor: usize,
|
cursor: usize,
|
||||||
cache: &SchemaCache,
|
cache: &SchemaCache,
|
||||||
ranker: Ranker,
|
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> {
|
) -> Option<Completion> {
|
||||||
let cursor = cursor.min(input.len());
|
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
|
// `pk` at the end of `create table T with pk`. The
|
||||||
// optional-suffix case (`save ` → `as`) is preserved
|
// optional-suffix case (`save ` → `as`) is preserved
|
||||||
// because there `partial_prefix` is empty.
|
// 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
|
// Schema-aware probe: one walk yields both the expected set
|
||||||
// and the table-context snapshot (ADR-0024 §Phase D
|
// and the table-context snapshot (ADR-0024 §Phase D
|
||||||
// §column-narrowing). The engine reads
|
// §column-narrowing). The engine reads
|
||||||
// `current_table_columns` to narrow column candidates to the
|
// `current_table_columns` to narrow column candidates to the
|
||||||
// active table rather than the flat `cache.columns` (which
|
// active table rather than the flat `cache.columns` (which
|
||||||
// unions every table's columns).
|
// unions every table's columns). ADR-0030 §2: the probe runs
|
||||||
let probe = crate::dsl::walker::completion_probe(leading, cache);
|
// 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]> =
|
let current_table_columns: Option<&[TableColumn]> =
|
||||||
probe.current_table_columns.as_deref();
|
probe.current_table_columns.as_deref();
|
||||||
|
|
||||||
let expected = if probe.expected.is_empty() {
|
let expected = if probe.expected.is_empty() {
|
||||||
expected_at(leading)
|
expected_at(leading, mode)
|
||||||
} else {
|
} else {
|
||||||
probe.expected.clone()
|
probe.expected.clone()
|
||||||
};
|
};
|
||||||
@@ -548,7 +577,7 @@ pub fn value_literal_hint_at_cursor(input: &str, cursor: usize) -> Option<String
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let leading = &input[..start];
|
let leading = &input[..start];
|
||||||
let expected = expected_at(leading);
|
let expected = expected_at(leading, Mode::Advanced);
|
||||||
if !is_value_literal_signature(&expected) {
|
if !is_value_literal_signature(&expected) {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
@@ -608,7 +637,7 @@ pub fn typing_name_at_cursor(input: &str, cursor: usize) -> Option<TypingName> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
let leading = &input[..start];
|
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| {
|
let is_new_name_slot = expected.iter().any(|e| {
|
||||||
matches!(
|
matches!(
|
||||||
e,
|
e,
|
||||||
@@ -697,7 +726,7 @@ pub fn invalid_ident_at_cursor(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let leading = &input[..start];
|
let leading = &input[..start];
|
||||||
let expected = expected_at(leading);
|
let expected = expected_at(leading, Mode::Advanced);
|
||||||
if expected.is_empty() {
|
if expected.is_empty() {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|||||||
+72
-18
@@ -250,26 +250,46 @@ pub fn completion_probe(
|
|||||||
source: &str,
|
source: &str,
|
||||||
schema: &crate::completion::SchemaCache,
|
schema: &crate::completion::SchemaCache,
|
||||||
) -> CompletionProbe {
|
) -> CompletionProbe {
|
||||||
use crate::dsl::grammar::REGISTRY;
|
completion_probe_in_mode(source, schema, crate::mode::Mode::Advanced)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mode-aware [`completion_probe`] (ADR-0030 §2).
|
||||||
|
///
|
||||||
|
/// In `Mode::Simple` the empty-input / fall-through fallback
|
||||||
|
/// omits advanced-only entry words so Tab does not offer SQL
|
||||||
|
/// commands in simple mode, and the walker — running with
|
||||||
|
/// `ctx.mode = mode` — gates SQL-only forms inline.
|
||||||
|
pub fn completion_probe_in_mode(
|
||||||
|
source: &str,
|
||||||
|
schema: &crate::completion::SchemaCache,
|
||||||
|
mode: crate::mode::Mode,
|
||||||
|
) -> CompletionProbe {
|
||||||
|
use crate::dsl::grammar::{REGISTRY, is_advanced_only};
|
||||||
|
|
||||||
|
let mode_filtered_entries = || -> Vec<outcome::Expectation> {
|
||||||
|
REGISTRY
|
||||||
|
.iter()
|
||||||
|
.filter(|c| {
|
||||||
|
mode == crate::mode::Mode::Advanced
|
||||||
|
|| !is_advanced_only(c.entry.primary)
|
||||||
|
})
|
||||||
|
.map(|c| outcome::Expectation::Word(c.entry.primary))
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
if source.trim().is_empty() {
|
if source.trim().is_empty() {
|
||||||
return CompletionProbe {
|
return CompletionProbe {
|
||||||
expected: REGISTRY
|
expected: mode_filtered_entries(),
|
||||||
.iter()
|
|
||||||
.map(|c| outcome::Expectation::Word(c.entry.primary))
|
|
||||||
.collect(),
|
|
||||||
current_table_columns: None,
|
current_table_columns: None,
|
||||||
pending_hint_mode: None,
|
pending_hint_mode: None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
let mut ctx = context::WalkContext::with_schema(schema);
|
let mut ctx = context::WalkContext::with_schema(schema);
|
||||||
|
ctx.mode = mode;
|
||||||
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 {
|
||||||
return CompletionProbe {
|
return CompletionProbe {
|
||||||
expected: REGISTRY
|
expected: mode_filtered_entries(),
|
||||||
.iter()
|
|
||||||
.map(|c| outcome::Expectation::Word(c.entry.primary))
|
|
||||||
.collect(),
|
|
||||||
current_table_columns: None,
|
current_table_columns: None,
|
||||||
pending_hint_mode: None,
|
pending_hint_mode: None,
|
||||||
};
|
};
|
||||||
@@ -320,6 +340,22 @@ pub fn completion_probe(
|
|||||||
pub fn input_verdict(
|
pub fn input_verdict(
|
||||||
source: &str,
|
source: &str,
|
||||||
schema: Option<&crate::completion::SchemaCache>,
|
schema: Option<&crate::completion::SchemaCache>,
|
||||||
|
) -> Option<outcome::Severity> {
|
||||||
|
input_verdict_in_mode(source, schema, crate::mode::Mode::Advanced)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mode-aware [`input_verdict`] (ADR-0030 §2).
|
||||||
|
///
|
||||||
|
/// The `[ERR]` / `[WRN]` indicator reads this through
|
||||||
|
/// `App::input_verdict_for_indicator` passing the line's
|
||||||
|
/// effective mode, so a simple-mode `select` lights up ERROR
|
||||||
|
/// (the SQL-hint validation failure) and an advanced-mode
|
||||||
|
/// `select` does not.
|
||||||
|
#[must_use]
|
||||||
|
pub fn input_verdict_in_mode(
|
||||||
|
source: &str,
|
||||||
|
schema: Option<&crate::completion::SchemaCache>,
|
||||||
|
mode: crate::mode::Mode,
|
||||||
) -> Option<outcome::Severity> {
|
) -> Option<outcome::Severity> {
|
||||||
use outcome::Severity;
|
use outcome::Severity;
|
||||||
if source.trim().is_empty() {
|
if source.trim().is_empty() {
|
||||||
@@ -329,6 +365,7 @@ pub fn input_verdict(
|
|||||||
context::WalkContext::new,
|
context::WalkContext::new,
|
||||||
context::WalkContext::with_schema,
|
context::WalkContext::with_schema,
|
||||||
);
|
);
|
||||||
|
ctx.mode = mode;
|
||||||
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 {
|
||||||
// The first token is not a registered command word —
|
// The first token is not a registered command word —
|
||||||
@@ -685,24 +722,41 @@ fn pair_type_mismatch(
|
|||||||
/// produces.
|
/// produces.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn expected_at_input(source: &str) -> Vec<outcome::Expectation> {
|
pub fn expected_at_input(source: &str) -> Vec<outcome::Expectation> {
|
||||||
use crate::dsl::grammar::REGISTRY;
|
expected_at_input_in_mode(source, crate::mode::Mode::Advanced)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Mode-aware [`expected_at_input`] (ADR-0030 §2). Filters the
|
||||||
|
/// empty / unknown-entry fallback by mode so simple mode does
|
||||||
|
/// not surface advanced-only entry words.
|
||||||
|
#[must_use]
|
||||||
|
pub fn expected_at_input_in_mode(
|
||||||
|
source: &str,
|
||||||
|
mode: crate::mode::Mode,
|
||||||
|
) -> Vec<outcome::Expectation> {
|
||||||
|
use crate::dsl::grammar::{REGISTRY, is_advanced_only};
|
||||||
|
|
||||||
|
let mode_filtered = || -> Vec<outcome::Expectation> {
|
||||||
|
REGISTRY
|
||||||
|
.iter()
|
||||||
|
.filter(|c| {
|
||||||
|
mode == crate::mode::Mode::Advanced
|
||||||
|
|| !is_advanced_only(c.entry.primary)
|
||||||
|
})
|
||||||
|
.map(|c| outcome::Expectation::Word(c.entry.primary))
|
||||||
|
.collect()
|
||||||
|
};
|
||||||
|
|
||||||
if source.trim().is_empty() {
|
if source.trim().is_empty() {
|
||||||
return REGISTRY
|
return mode_filtered();
|
||||||
.iter()
|
|
||||||
.map(|c| outcome::Expectation::Word(c.entry.primary))
|
|
||||||
.collect();
|
|
||||||
}
|
}
|
||||||
let mut ctx = context::WalkContext::new();
|
let mut ctx = context::WalkContext::new();
|
||||||
|
ctx.mode = mode;
|
||||||
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 {
|
||||||
// Walker didn't engage (unknown entry word): the
|
// Walker didn't engage (unknown entry word): the
|
||||||
// completion engine should still surface the available
|
// completion engine should still surface the available
|
||||||
// entry words so the user can recover.
|
// entry words so the user can recover.
|
||||||
return REGISTRY
|
return mode_filtered();
|
||||||
.iter()
|
|
||||||
.map(|c| outcome::Expectation::Word(c.entry.primary))
|
|
||||||
.collect();
|
|
||||||
};
|
};
|
||||||
match result.outcome {
|
match result.outcome {
|
||||||
// On Match, surface the outer-shape's skipped-Optional
|
// On Match, surface the outer-shape's skipped-Optional
|
||||||
|
|||||||
+21
-4
@@ -24,7 +24,8 @@
|
|||||||
|
|
||||||
use ratatui::style::{Color, Modifier, Style};
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
|
|
||||||
use crate::dsl::parser::parse_command_with_schema;
|
use crate::dsl::parser::{parse_command_in_mode, parse_command_with_schema, parse_command_with_schema_in_mode};
|
||||||
|
use crate::mode::Mode;
|
||||||
use crate::dsl::walker;
|
use crate::dsl::walker;
|
||||||
use crate::dsl::{ParseError, parse_command};
|
use crate::dsl::{ParseError, parse_command};
|
||||||
use crate::theme::Theme;
|
use crate::theme::Theme;
|
||||||
@@ -67,7 +68,15 @@ pub fn render_input_runs(
|
|||||||
cache: &crate::completion::SchemaCache,
|
cache: &crate::completion::SchemaCache,
|
||||||
) -> Vec<StyledRun> {
|
) -> Vec<StyledRun> {
|
||||||
let mut runs = lex_to_runs(input, theme);
|
let mut runs = lex_to_runs(input, theme);
|
||||||
if let InputState::DefiniteErrorAt(pos) = classify_input_with_schema(input, cache) {
|
// `render_input_runs` is invoked from `ui.rs` only when the
|
||||||
|
// effective mode is plain `Simple` (advanced rendering uses
|
||||||
|
// `plain_input_spans`), so the classification must use the
|
||||||
|
// simple-mode walker view (ADR-0030 §2): a SQL form here
|
||||||
|
// surfaces as a definite error overlay, consistent with the
|
||||||
|
// dispatch path's "this is SQL" hint on submit.
|
||||||
|
if let InputState::DefiniteErrorAt(pos) =
|
||||||
|
classify_parse_result(parse_command_with_schema_in_mode(input, cache, Mode::Simple))
|
||||||
|
{
|
||||||
overlay_error(&mut runs, pos, theme);
|
overlay_error(&mut runs, pos, theme);
|
||||||
}
|
}
|
||||||
if let Some(inv) = crate::completion::invalid_ident_at_cursor(input, cursor_byte, cache) {
|
if let Some(inv) = crate::completion::invalid_ident_at_cursor(input, cursor_byte, cache) {
|
||||||
@@ -320,7 +329,13 @@ pub fn ambient_hint(
|
|||||||
// Candidates win when any exist — the panel surfaces them
|
// Candidates win when any exist — the panel surfaces them
|
||||||
// directly because they're more actionable than prose
|
// directly because they're more actionable than prose
|
||||||
// framings.
|
// framings.
|
||||||
if let Some(comp) = crate::completion::candidates_at_cursor(input, cursor, cache) {
|
// `ambient_hint` is only called for `EffectiveMode::Simple`
|
||||||
|
// (ui.rs gates it), so completion runs through the
|
||||||
|
// simple-mode walker view — `select` does not surface here
|
||||||
|
// (ADR-0030 §2).
|
||||||
|
if let Some(comp) =
|
||||||
|
crate::completion::candidates_at_cursor_in_mode(input, cursor, cache, Mode::Simple)
|
||||||
|
{
|
||||||
return Some(AmbientHint::Candidates {
|
return Some(AmbientHint::Candidates {
|
||||||
items: comp.candidates,
|
items: comp.candidates,
|
||||||
selected: None,
|
selected: None,
|
||||||
@@ -348,7 +363,9 @@ pub fn ambient_hint(
|
|||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
// Otherwise fall back to the prose framings from stage 5.
|
// Otherwise fall back to the prose framings from stage 5.
|
||||||
match parse_command(input) {
|
// ADR-0030 §2: simple-mode hint uses the simple-mode walker
|
||||||
|
// view so a SQL form surfaces with the "this is SQL" hint.
|
||||||
|
match parse_command_in_mode(input, Mode::Simple) {
|
||||||
Ok(_) => Some(AmbientHint::Prose(crate::t!("hint.ambient_complete"))),
|
Ok(_) => Some(AmbientHint::Prose(crate::t!("hint.ambient_complete"))),
|
||||||
Err(ParseError::Empty) => None,
|
Err(ParseError::Empty) => None,
|
||||||
Err(ParseError::Invalid {
|
Err(ParseError::Invalid {
|
||||||
|
|||||||
Reference in New Issue
Block a user