feat: DSL→SQL teaching echo — §4 styled-runs polish (ADR-0038)
Lands the last open item on ADR-0038: the de-emphasised styled-runs rendering treatment for the echo + every category-3 prose line. The echoed SQL now reads as code — the dimmed `Executing SQL:` label plus the SQL portion lexed and coloured the same way the input echo treats user-typed input (ADR-0028 §5 styled-runs over input_render::lex_to_runs in advanced mode). Category-3 prose lines (the DontConvert caveat and the existing illuminating `client_side.*` notes — shortid auto-fill, type-conversion transforms) all render dimmed too, per §6's "de-emphasised prose line" wording, so every cat-3 line is visually consistent. * New `OutputKind::TeachingEcho` variant + a custom branch in `ui::render_output_line` mirroring the OutputKind::Echo input-echo path: strip the canonical `Executing SQL:` prefix, render it with `theme.muted`, then lex the rest in `Mode::Advanced` and emit one span per token. Tag stays `[system]` for visual consistency with other system output. * New `OutputStyleClass::Hint` styled-runs class, resolved to `theme.muted` in `output_span_style`. Used for the cat-3 prose lines (dont_convert caveat + the existing client_side notes). * New const `crate::echo::TEACHING_ECHO_LABEL = "Executing SQL: "` — the byte boundary the ui.rs branch needs is fixed (an i18n template can't provide that), so the label moves from i18n to a constant. The `echo.executing_sql` i18n key is retired (en-US.yaml + keys.rs); a comment in en-US.yaml points future locales at re-introducing it if needed. * App-side helpers: `push_teaching_echo(sql)` builds the TeachingEcho line; `push_category_three_prose(text)` builds a System line with a whole-text Hint span. `note_ok_summary` and `handle_dsl_change_column_success` / `handle_dsl_add_column_success` use these instead of plain `note_system` for the echo, the caveat, and the illuminating notes. Existing tests pass unchanged — text content is the same; only styling changes. New tests pin the polish: * `ui::tests::teaching_echo_line_renders_dim_prefix_and_lexed_sql` asserts the TeachingEcho rendering produces a dim prefix span + keyword-coloured SQL spans (confirming the lexer ran in advanced mode). * `ui::tests::category_three_prose_line_renders_all_dim` pins the whole-text Hint coverage. * `ui::tests::hint_class_resolves_to_muted_foreground` pins the theme resolution across both light and dark. * `app::tests::polished_echo_carries_teaching_echo_kind_and_caveat_a_hint_span` pins the App-side wiring (kind + styled_runs shape). Tests: 2019 passed / 0 failed / 1 ignored (pre-existing); clippy clean (`--all-targets -D warnings`, nursery). ADR-0038 is now feature-complete — every catalogue row implemented, round-tripped, AND polished per §4.
This commit is contained in:
@@ -606,7 +606,7 @@ fn approximate_wrapped_rows_from_output(
|
||||
Mode::Simple => "[simple] ".len(),
|
||||
Mode::Advanced => "[advanced] ".len(),
|
||||
},
|
||||
OutputKind::System => "[system] ".len(),
|
||||
OutputKind::System | OutputKind::TeachingEcho => "[system] ".len(),
|
||||
OutputKind::Error => "[error] ".len(),
|
||||
};
|
||||
let total = tag_len + line.text.chars().count();
|
||||
@@ -625,6 +625,11 @@ const fn output_span_style(class: OutputStyleClass, theme: &Theme) -> Style {
|
||||
OutputStyleClass::AutomaticIndex => Style::new()
|
||||
.fg(theme.warning)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
// ADR-0038 §4 / §6: de-emphasised text — the `Executing SQL:`
|
||||
// prefix and every category-3 prose line (caveat + the
|
||||
// existing `client_side.*` notes). `theme.muted` is the
|
||||
// established dim foreground.
|
||||
OutputStyleClass::Hint => Style::new().fg(theme.muted),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -635,7 +640,7 @@ fn render_output_line<'a>(line: &'a OutputLine, theme: &Theme) -> Line<'a> {
|
||||
};
|
||||
let tag = match line.kind {
|
||||
OutputKind::Echo => format!("[{}] ", line.mode_at_submission.label().to_lowercase()),
|
||||
OutputKind::System => "[system] ".to_string(),
|
||||
OutputKind::System | OutputKind::TeachingEcho => "[system] ".to_string(),
|
||||
OutputKind::Error => "[error] ".to_string(),
|
||||
};
|
||||
|
||||
@@ -665,6 +670,34 @@ fn render_output_line<'a>(line: &'a OutputLine, theme: &Theme) -> Line<'a> {
|
||||
// Echo body without the expected prefix, or any non-echo
|
||||
// line, falls through to the plain rendering below.
|
||||
|
||||
// ADR-0038 §4 styled-runs polish — the DSL → SQL teaching echo
|
||||
// gets the same syntactic treatment as the input echo, but with
|
||||
// a dimmed `Executing SQL: ` prefix instead of the mode label,
|
||||
// and the SQL portion lexed in `Mode::Advanced` (the echoed SQL
|
||||
// is always advanced syntax, regardless of submission mode).
|
||||
if line.kind == OutputKind::TeachingEcho
|
||||
&& let Some(rest) = line.text.strip_prefix(crate::echo::TEACHING_ECHO_LABEL)
|
||||
{
|
||||
let prefix_len = crate::echo::TEACHING_ECHO_LABEL.len();
|
||||
let mut spans: Vec<Span<'a>> = Vec::with_capacity(2 + rest.len() / 4);
|
||||
spans.push(Span::styled(tag, tag_style));
|
||||
spans.push(Span::styled(
|
||||
&line.text[..prefix_len],
|
||||
Style::default().fg(theme.muted),
|
||||
));
|
||||
for run in
|
||||
crate::input_render::lex_to_runs_in_mode(rest, theme, Mode::Advanced)
|
||||
{
|
||||
spans.push(Span::styled(
|
||||
&rest[run.byte_range.0..run.byte_range.1],
|
||||
run.style,
|
||||
));
|
||||
}
|
||||
return Line::from(spans);
|
||||
}
|
||||
// A TeachingEcho line missing the canonical prefix is a builder
|
||||
// bug; it falls through to the plain rendering below.
|
||||
|
||||
// ADR-0028 §5: a line carrying a styled-runs payload is
|
||||
// rendered span-by-span, each run's semantic class resolved
|
||||
// to a colour from the active theme. The tag keeps its
|
||||
@@ -685,7 +718,11 @@ fn render_output_line<'a>(line: &'a OutputLine, theme: &Theme) -> Line<'a> {
|
||||
|
||||
let body_style = match line.kind {
|
||||
OutputKind::Echo => Style::default().fg(theme.fg),
|
||||
OutputKind::System => Style::default().fg(theme.system),
|
||||
// TeachingEcho without a prefix falls back to `system` styling
|
||||
// — the kind itself only signals "render with the dim+lex
|
||||
// custom path above"; reaching here means the builder produced
|
||||
// a malformed line, so degrade gracefully rather than crash.
|
||||
OutputKind::System | OutputKind::TeachingEcho => Style::default().fg(theme.system),
|
||||
OutputKind::Error => Style::default().fg(theme.error),
|
||||
};
|
||||
Line::from(vec![
|
||||
@@ -1094,6 +1131,95 @@ mod tests {
|
||||
assert_eq!(rendered.spans[2].style.fg, Some(theme.fg));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hint_class_resolves_to_muted_foreground() {
|
||||
// ADR-0038 §4 / §6: the dim style class used for the
|
||||
// `Executing SQL:` prefix, the DontConvert caveat, and every
|
||||
// category-3 prose note. Pins the `theme.muted` resolution
|
||||
// across both palettes.
|
||||
for theme in [Theme::dark(), Theme::light()] {
|
||||
let style = output_span_style(OutputStyleClass::Hint, &theme);
|
||||
assert_eq!(
|
||||
style.fg,
|
||||
Some(theme.muted),
|
||||
"Hint must resolve to theme.muted on {:?} background",
|
||||
theme.background,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn teaching_echo_line_renders_dim_prefix_and_lexed_sql() {
|
||||
// ADR-0038 §4 styled-runs polish: a TeachingEcho line is laid
|
||||
// out as [tag][dim prefix][lexed SQL spans]. The tag stays the
|
||||
// mode tint; the `Executing SQL: ` prefix is `theme.muted`;
|
||||
// the SQL portion is re-lexed in advanced mode so it picks up
|
||||
// keyword / identifier / literal colours.
|
||||
let theme = Theme::dark();
|
||||
let line = OutputLine {
|
||||
text: format!(
|
||||
"{}{}",
|
||||
crate::echo::TEACHING_ECHO_LABEL,
|
||||
"CREATE TABLE T (id serial PRIMARY KEY)"
|
||||
),
|
||||
kind: OutputKind::TeachingEcho,
|
||||
mode_at_submission: Mode::Advanced,
|
||||
styled_runs: None,
|
||||
};
|
||||
let rendered = render_output_line(&line, &theme);
|
||||
// [system] tag, then the dim prefix, then ≥1 SQL spans.
|
||||
assert!(rendered.spans.len() >= 3, "tag + prefix + sql: {:?}", rendered.spans);
|
||||
assert_eq!(rendered.spans[0].content.as_ref(), "[system] ");
|
||||
assert_eq!(rendered.spans[1].content.as_ref(), crate::echo::TEACHING_ECHO_LABEL);
|
||||
assert_eq!(
|
||||
rendered.spans[1].style.fg,
|
||||
Some(theme.muted),
|
||||
"prefix is dimmed (theme.muted)",
|
||||
);
|
||||
// At least one SQL span carries a keyword colour — `CREATE` is
|
||||
// the leading keyword and gets `tok_keyword`. Pinning this
|
||||
// also confirms the lexer ran in advanced mode (the bare
|
||||
// `CREATE` keyword is only highlighted past the entry word in
|
||||
// advanced — ADR-0030 §8).
|
||||
let has_keyword_span = rendered
|
||||
.spans
|
||||
.iter()
|
||||
.any(|s| s.style.fg == Some(theme.tok_keyword));
|
||||
assert!(
|
||||
has_keyword_span,
|
||||
"expected at least one keyword-coloured SQL span: {:?}",
|
||||
rendered.spans
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn category_three_prose_line_renders_all_dim() {
|
||||
// ADR-0038 §6: the existing illuminating client_side notes and
|
||||
// the new --dont-convert caveat are de-emphasised prose. A
|
||||
// styled-runs payload with a single Hint span over the whole
|
||||
// text yields one dim body span (plus the [system] tag).
|
||||
let theme = Theme::dark();
|
||||
let text = "[client-side] 5 row(s) were transformed".to_string();
|
||||
let line = OutputLine::styled(
|
||||
text.clone(),
|
||||
OutputKind::System,
|
||||
Mode::Advanced,
|
||||
vec![OutputSpan {
|
||||
byte_range: (0, text.len()),
|
||||
class: OutputStyleClass::Hint,
|
||||
}],
|
||||
);
|
||||
let rendered = render_output_line(&line, &theme);
|
||||
assert_eq!(rendered.spans.len(), 2, "tag + one Hint span");
|
||||
assert_eq!(rendered.spans[0].content.as_ref(), "[system] ");
|
||||
assert_eq!(rendered.spans[1].content.as_ref(), text.as_str());
|
||||
assert_eq!(
|
||||
rendered.spans[1].style.fg,
|
||||
Some(theme.muted),
|
||||
"the whole prose line is dim",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn candidate_line_colours_mixed_mode_continuations() {
|
||||
// ADR-0035 §4i (e): when a shared entry word's completions mix
|
||||
|
||||
Reference in New Issue
Block a user