ui: re-enable advanced-mode ambient assistance (ADR-0022 Amendment 1)
Advanced-mode hinting + completion-preview were dead: render_hint_panel returned None for advanced mode (stale ADR-0022 §12 gate, predating the SQL grammar) and the hint resolver/ambient_hint never threaded Mode, so a SQL statement was gated as "this is SQL". The unified walker (ADR-0030/ 0031/0032) speaks SQL, so this lifts the gate. - ambient_hint_in_mode + hint_resolution_at_input_in_mode + expected_for_hint_snapshot(mode); candidate/diagnostic/parse sub-calls run in the active mode. - render_hint_panel calls ambient for all modes; one-shot `:` sigil stripped (strip_one_shot_prefix) so `: sel` hints `select`. - ADR-0022 Amendment 1 + README index. Found by manual advanced-mode testing; Phase 2 marked SQL hint/completion green at the engine layer but never exercised the UI. App-level render test (advanced_mode_hint_panel_surfaces_sql_candidates) + ambient-layer regression locks. 1466 baseline green.
This commit is contained in:
+84
-13
@@ -215,8 +215,9 @@ pub enum AmbientHint {
|
||||
},
|
||||
}
|
||||
|
||||
/// Compute the ambient hint for the input panel
|
||||
/// (ADR-0022 §6).
|
||||
/// Compute the simple-mode ambient hint for the input panel
|
||||
/// (ADR-0022 §6). Thin wrapper over [`ambient_hint_in_mode`];
|
||||
/// advanced-mode callers pass the active mode instead.
|
||||
///
|
||||
/// Returns `None` for empty input — caller falls back to
|
||||
/// `panel.hint_empty`.
|
||||
@@ -226,6 +227,27 @@ pub fn ambient_hint(
|
||||
cursor: usize,
|
||||
memo: Option<&crate::completion::LastCompletion>,
|
||||
cache: &crate::completion::SchemaCache,
|
||||
) -> Option<AmbientHint> {
|
||||
ambient_hint_in_mode(input, cursor, memo, cache, Mode::Simple)
|
||||
}
|
||||
|
||||
/// Mode-aware ambient hint for the input panel (ADR-0022
|
||||
/// Amendment 1).
|
||||
///
|
||||
/// Walks the input in `mode` so advanced-mode SQL surfaces slot
|
||||
/// hints + completion candidates instead of the simple-mode
|
||||
/// "this is SQL" gate. The simple-mode entry point [`ambient_hint`]
|
||||
/// forwards here with `Mode::Simple`.
|
||||
///
|
||||
/// Returns `None` for empty input — caller falls back to
|
||||
/// `panel.hint_empty`.
|
||||
#[must_use]
|
||||
pub fn ambient_hint_in_mode(
|
||||
input: &str,
|
||||
cursor: usize,
|
||||
memo: Option<&crate::completion::LastCompletion>,
|
||||
cache: &crate::completion::SchemaCache,
|
||||
mode: Mode,
|
||||
) -> Option<AmbientHint> {
|
||||
if input.trim().is_empty() {
|
||||
return None;
|
||||
@@ -254,7 +276,7 @@ pub fn ambient_hint(
|
||||
// (the panel explains where the user is looking), else the
|
||||
// most severe one. The error overlay still marks every
|
||||
// flagged token; this panel carries the *why*.
|
||||
let diagnostics = crate::dsl::walker::input_diagnostics(input, Some(cache));
|
||||
let diagnostics = crate::dsl::walker::input_diagnostics_in_mode(input, Some(cache), mode);
|
||||
if let Some(diag) = pick_hint_diagnostic(&diagnostics, cursor.min(input.len())) {
|
||||
return Some(AmbientHint::Prose(diag.message.clone()));
|
||||
}
|
||||
@@ -275,7 +297,7 @@ pub fn ambient_hint(
|
||||
// (Date → "Type a date as 'YYYY-MM-DD'", etc.) and surface
|
||||
// the column name when the walker has it bound.
|
||||
let resolution =
|
||||
crate::dsl::walker::hint_resolution_at_input(leading, Some(cache));
|
||||
crate::dsl::walker::hint_resolution_at_input_in_mode(leading, Some(cache), mode);
|
||||
match resolution.as_ref().map(|r| r.mode) {
|
||||
Some(crate::dsl::grammar::HintMode::ProseOnly(key)) => {
|
||||
// The cursor sits at a slot where Tab candidates
|
||||
@@ -340,12 +362,12 @@ pub fn ambient_hint(
|
||||
// Candidates win when any exist — the panel surfaces them
|
||||
// directly because they're more actionable than prose
|
||||
// framings.
|
||||
// `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).
|
||||
// Candidate completion runs through the `mode`-aware walker
|
||||
// view (ADR-0022 Amendment 1): in advanced mode SQL keywords
|
||||
// and schema candidates surface; in simple mode `select` is
|
||||
// gated as "this is SQL" (ADR-0030 §2).
|
||||
if let Some(comp) =
|
||||
crate::completion::candidates_at_cursor_in_mode(input, cursor, cache, Mode::Simple)
|
||||
crate::completion::candidates_at_cursor_in_mode(input, cursor, cache, mode)
|
||||
{
|
||||
return Some(AmbientHint::Candidates {
|
||||
items: comp.candidates,
|
||||
@@ -373,10 +395,11 @@ pub fn ambient_hint(
|
||||
found = inv.found,
|
||||
)));
|
||||
}
|
||||
// Otherwise fall back to the prose framings from stage 5.
|
||||
// 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) {
|
||||
// Otherwise fall back to the prose framings from stage 5,
|
||||
// parsed in the active `mode` (ADR-0022 Amendment 1). In
|
||||
// simple mode a SQL form still surfaces the "this is SQL"
|
||||
// hint (ADR-0030 §2); in advanced mode it parses as SQL.
|
||||
match parse_command_in_mode(input, mode) {
|
||||
Ok(_) => Some(AmbientHint::Prose(crate::t!("hint.ambient_complete"))),
|
||||
Err(ParseError::Empty) => None,
|
||||
Err(ParseError::Invalid {
|
||||
@@ -750,6 +773,54 @@ mod tests {
|
||||
assert!(ambient_hint(" ", 3, None, &empty_cache()).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn advanced_mode_ambient_offers_sql_from_slot_candidate() {
|
||||
// ADR-0022 Amendment 1: advanced-mode ambient assistance
|
||||
// surfaces SQL completion candidates (here the FROM-slot
|
||||
// table) instead of the simple-mode "this is SQL" gate.
|
||||
let cache =
|
||||
schema_with_columns("Customers", &[("id", crate::dsl::types::Type::Int)]);
|
||||
let input = "select * from ";
|
||||
match ambient_hint_in_mode(
|
||||
input,
|
||||
input.len(),
|
||||
None,
|
||||
&cache,
|
||||
crate::mode::Mode::Advanced,
|
||||
) {
|
||||
Some(AmbientHint::Candidates { items, .. }) => assert!(
|
||||
items.iter().any(|c| c.text == "Customers"),
|
||||
"FROM slot should offer table `Customers`; got {items:?}",
|
||||
),
|
||||
other => panic!("expected candidates in advanced mode, got {other:?}"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn simple_mode_ambient_does_not_surface_sql_candidates() {
|
||||
// The simple-mode entry point keeps gating SQL — advanced
|
||||
// assistance is opt-in via mode, never leaked into simple.
|
||||
let cache =
|
||||
schema_with_columns("Customers", &[("id", crate::dsl::types::Type::Int)]);
|
||||
let input = "select * from ";
|
||||
let hint = ambient_hint_in_mode(
|
||||
input,
|
||||
input.len(),
|
||||
None,
|
||||
&cache,
|
||||
crate::mode::Mode::Simple,
|
||||
);
|
||||
let offers_table = matches!(
|
||||
&hint,
|
||||
Some(AmbientHint::Candidates { items, .. })
|
||||
if items.iter().any(|c| c.text == "Customers"),
|
||||
);
|
||||
assert!(
|
||||
!offers_table,
|
||||
"simple mode must not surface SQL FROM candidates: {hint:?}",
|
||||
);
|
||||
}
|
||||
|
||||
// ---- Phase D typed-slot hints (end-to-end) -------
|
||||
|
||||
fn schema_with_columns(
|
||||
|
||||
Reference in New Issue
Block a user