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:
@@ -725,6 +725,23 @@ fn runs_to_spans<'a>(
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Strip a leading one-shot `:` sigil (and the whitespace after
|
||||
/// it) from `input`, returning the advanced command slice and the
|
||||
/// cursor remapped into it. Mirrors `App::submit`'s `:` handling
|
||||
/// so the hint panel hints at the command, not the sigil
|
||||
/// (ADR-0022 Amendment 1). Used only when the effective mode is
|
||||
/// `AdvancedOneShot`, where `input` is guaranteed to start (after
|
||||
/// any leading whitespace) with `:`.
|
||||
fn strip_one_shot_prefix(input: &str, cursor: usize) -> (&str, usize) {
|
||||
let lead_ws = input.len() - input.trim_start().len();
|
||||
let after_colon = lead_ws + 1; // skip the `:`
|
||||
let ws_after = input[after_colon..].len() - input[after_colon..].trim_start().len();
|
||||
let prefix_len = (after_colon + ws_after).min(input.len());
|
||||
let effective = &input[prefix_len..];
|
||||
let effective_cursor = cursor.saturating_sub(prefix_len).min(effective.len());
|
||||
(effective, effective_cursor)
|
||||
}
|
||||
|
||||
fn render_hint_panel(app: &App, theme: &Theme, frame: &mut Frame<'_>, area: Rect) {
|
||||
let block = Block::default()
|
||||
.borders(Borders::ALL)
|
||||
@@ -740,21 +757,33 @@ fn render_hint_panel(app: &App, theme: &Theme, frame: &mut Frame<'_>, area: Rect
|
||||
|
||||
// Resolution order for the hint panel body:
|
||||
// 1. An explicit app-set hint (e.g. modal contexts) wins.
|
||||
// 2. Otherwise, in simple mode with non-empty input,
|
||||
// the ambient typing-assistance hint (ADR-0022 §6).
|
||||
// 2. Otherwise, with non-empty input, the ambient
|
||||
// typing-assistance hint (ADR-0022 §6) computed in the
|
||||
// effective mode.
|
||||
// 3. Otherwise, the existing empty-state placeholder.
|
||||
// Advanced mode skips ambient hinting (ADR-0022 §12) —
|
||||
// the DSL lexer/parser don't speak SQL.
|
||||
// ADR-0022 Amendment 1: advanced mode no longer skips ambient
|
||||
// hinting. The original §12 carve-out predated the unified
|
||||
// mode-aware walker (ADR-0030/0031/0032); the walker now
|
||||
// speaks SQL, so `ambient_hint_in_mode` surfaces SQL slot
|
||||
// hints + completion candidates in advanced mode too.
|
||||
let empty_hint = crate::t!("panel.hint_empty");
|
||||
let ambient = match app.effective_mode() {
|
||||
EffectiveMode::Simple => crate::input_render::ambient_hint(
|
||||
&app.input,
|
||||
app.input_cursor,
|
||||
app.last_completion.as_ref(),
|
||||
&app.schema_cache,
|
||||
),
|
||||
EffectiveMode::AdvancedPersistent | EffectiveMode::AdvancedOneShot => None,
|
||||
// In one-shot advanced mode (`:` prefix in simple mode) the
|
||||
// raw input carries the `:` sigil, which is not part of the
|
||||
// grammar. Strip it for the ambient computation so the hint
|
||||
// reflects the advanced command — mirroring `App::submit`.
|
||||
let (hint_input, hint_cursor) = match app.effective_mode() {
|
||||
EffectiveMode::AdvancedOneShot => {
|
||||
strip_one_shot_prefix(&app.input, app.input_cursor)
|
||||
}
|
||||
_ => (app.input.as_str(), app.input_cursor),
|
||||
};
|
||||
let ambient = crate::input_render::ambient_hint_in_mode(
|
||||
hint_input,
|
||||
hint_cursor,
|
||||
app.last_completion.as_ref(),
|
||||
&app.schema_cache,
|
||||
app.effective_mode().as_mode(),
|
||||
);
|
||||
let muted = Style::default().fg(theme.muted);
|
||||
let line = match (app.hint.as_deref(), ambient) {
|
||||
(Some(set), _) => Line::from(Span::styled(set.to_string(), muted)),
|
||||
@@ -1025,6 +1054,29 @@ mod tests {
|
||||
insta::assert_snapshot!("default_advanced_dark", snapshot);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn advanced_mode_hint_panel_surfaces_sql_candidates() {
|
||||
// Regression reproduction (ADR-0022 Amendment 1): in
|
||||
// advanced mode the hint panel must surface ambient
|
||||
// assistance for SQL — here the FROM-slot table candidate
|
||||
// `Customers` — not the empty placeholder. Before the fix
|
||||
// `render_hint_panel` returned `None` for advanced mode and
|
||||
// the hint resolver/completion ran in simple mode, so a SQL
|
||||
// statement got the "this is SQL" gate and no candidates.
|
||||
let mut app = App::new();
|
||||
app.mode = Mode::Advanced;
|
||||
app.schema_cache.tables.push("Customers".to_string());
|
||||
app.input.push_str("select * from ");
|
||||
app.input_cursor = app.input.len();
|
||||
let theme = Theme::dark();
|
||||
let rendered = render_to_string(&mut app, &theme, 80, 24);
|
||||
assert!(
|
||||
rendered.contains("Customers"),
|
||||
"advanced-mode hint panel should surface the FROM-slot \
|
||||
candidate `Customers`; got:\n{rendered}",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn highlighted_input_all_token_classes_snapshot() {
|
||||
// ADR-0022 stage 2: representative input that exercises
|
||||
@@ -1050,8 +1102,12 @@ mod tests {
|
||||
// Typing `:sel` in simple mode should flip the input panel
|
||||
// label to `Advanced:` while the persistent mode stays simple.
|
||||
// The visible input includes the auto-inserted space after `:`.
|
||||
// With the cursor after `sel` (ADR-0022 Amendment 1), the hint
|
||||
// panel now offers the advanced `select` completion — the `:`
|
||||
// sigil is stripped before the ambient walk.
|
||||
let mut app = App::new();
|
||||
app.input.push_str(": sel");
|
||||
app.input_cursor = app.input.len();
|
||||
let theme = Theme::dark();
|
||||
let snapshot = render_to_string(&mut app, &theme, 80, 24);
|
||||
insta::assert_snapshot!("one_shot_advanced_dark", snapshot);
|
||||
|
||||
Reference in New Issue
Block a user