feat: ADR-0035 4i(e) — colour DSL vs SQL completions when mixed

Building on the 4i(d) merge: tag each completion Candidate with a
ModeClass (Both/Advanced/Simple) and, in the hint UI, colour the
continuations by mode ONLY when a candidate list actually mixes modes
(a shared entry word offering both SQL and DSL forms) — Advanced →
theme.mode_advanced, Simple → theme.mode_simple, Both → the token-kind
colour. A single-mode list (the common case, e.g. deep inside a SQL
statement) keeps the token-kind colours, so the tint appears only where
it distinguishes DSL from SQL. With (d)'s Both → Advanced → Simple
block-ordering, each colour reads as one contiguous block.

Candidate gains a `mode` field (typing_surface snapshots regenerated —
uniformly `mode: Both`, no semantic change). Tests: render_candidate_line
mixed-mode colours + the single-mode-keeps-kind-colour rule. Full suite
1913 passing / 0 failing / 1 ignored; clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-26 12:11:12 +00:00
parent 1afcf4ed29
commit f85261032d
132 changed files with 699 additions and 30 deletions
+23 -3
View File
@@ -146,6 +146,11 @@ fn expected_at(leading: &str, mode: Mode) -> Vec<Expectation> {
pub struct Candidate {
pub text: String,
pub kind: CandidateKind,
/// Source-mode classification (ADR-0035 §4i e). `Both` (neutral)
/// except for the merged continuations of a shared entry word, where
/// the hint UI colours `Advanced`/`Simple` differently — but only
/// when the candidate list actually mixes modes.
pub mode: ModeClass,
}
/// Re-ranker for a freshly-computed candidate list (ADR-0024
@@ -712,26 +717,37 @@ pub fn candidates_at_cursor_with_in_mode(
candidates.extend(identifiers.into_iter().map(|text| Candidate {
text,
kind: CandidateKind::Identifier,
mode: ModeClass::Both,
}));
candidates.extend(keywords.into_iter().map(|text| Candidate {
text,
kind: CandidateKind::Keyword,
// Keywords carry their merged mode-class (Both unless a shared entry
// word mixed simple + advanced continuations — ADR-0035 §4i e).
candidates.extend(keywords.into_iter().map(|text| {
let mode = kw_mode(text.as_str());
Candidate {
text,
kind: CandidateKind::Keyword,
mode,
}
}));
candidates.extend(type_names.into_iter().map(|text| Candidate {
text,
kind: CandidateKind::Keyword,
mode: ModeClass::Both,
}));
candidates.extend(composites.into_iter().map(|text| Candidate {
text,
kind: CandidateKind::Keyword,
mode: ModeClass::Both,
}));
candidates.extend(punct_candidates.into_iter().map(|text| Candidate {
text,
kind: CandidateKind::Punct,
mode: ModeClass::Both,
}));
candidates.extend(flags.into_iter().map(|text| Candidate {
text,
kind: CandidateKind::Flag,
mode: ModeClass::Both,
}));
if candidates.is_empty() {
@@ -2219,6 +2235,7 @@ mod tests {
Candidate {
text: text.to_string(),
kind: CandidateKind::Keyword,
mode: ModeClass::Both,
}
}
@@ -2260,14 +2277,17 @@ mod tests {
Candidate {
text: "b".to_string(),
kind: CandidateKind::Keyword,
mode: ModeClass::Both,
},
Candidate {
text: "a".to_string(),
kind: CandidateKind::Keyword,
mode: ModeClass::Both,
},
Candidate {
text: "c".to_string(),
kind: CandidateKind::Identifier,
mode: ModeClass::Both,
},
];
let out = identity_ranker(input.clone());