completion+hint: F1/F2 advanced-mode completion fixes

F1: the hint panel is the completion UI, so a premature "no such table/
column" ERROR on the token the user is still typing must not shadow its
completion. ambient_hint now suppresses an under-cursor error diagnostic
when a completion exists for the (non-empty) partial it overlaps, and
falls through to the candidates. Genuinely-unknown names (no prefix match)
still show the error; WARNINGs are unaffected. Both modes.

F2: projection-before-FROM ("select <cursor> from T" after deleting *)
offered the global column list instead of T's columns, because the §10.6
look-ahead's full-input walk can't reach FROM through an empty projection.
When the look-ahead finds no scope, retry with a neutral placeholder
inserted at the cursor so the trailing FROM/CTE scope is recovered for
narrowing. Only the repaired walk's from_scope/cte_bindings are used.

Test-first: 3 F1 tests (mid-typed completes, unknown still errors, simple-
mode DSL) + 1 F2 multi-table narrowing test. 1469 baseline green.
This commit is contained in:
claude@clouddev1
2026-05-21 20:25:16 +00:00
parent ed40445828
commit 1c8cbc1983
2 changed files with 157 additions and 9 deletions
+27 -1
View File
@@ -279,7 +279,33 @@ pub fn candidates_at_cursor_with_in_mode(
&& probe.cte_bindings.is_empty()
&& input.len() > leading.len()
{
Some(crate::dsl::walker::completion_probe_in_mode(input, cache, mode))
let direct = crate::dsl::walker::completion_probe_in_mode(input, cache, mode);
if direct.from_scope.is_empty() && direct.cte_bindings.is_empty() {
// The slot at the cursor is empty/incomplete — e.g. the
// projection list of `select <cursor> from T` after the
// user deleted `*` — so the full-input walk never
// reached FROM and recovered no scope. Repair by
// inserting a neutral expression placeholder at the
// cursor and re-walking, so the trailing FROM/CTE scope
// is recovered for column narrowing (ADR-0032 §10.6).
// Only the repaired walk's `from_scope` / `cte_bindings`
// are consumed (table + columns), so the inserted token
// doesn't perturb the expected set, which comes from the
// leading probe.
let mut repaired = String::with_capacity(input.len() + 2);
repaired.push_str(&input[..start]);
repaired.push_str("1 ");
repaired.push_str(&input[start..]);
let repaired_probe =
crate::dsl::walker::completion_probe_in_mode(&repaired, cache, mode);
if repaired_probe.from_scope.is_empty() && repaired_probe.cte_bindings.is_empty() {
Some(direct)
} else {
Some(repaired_probe)
}
} else {
Some(direct)
}
} else {
None
};