ADR-0022 stage 3/8: simple-mode echo lines highlighted

Lift `dsl::ECHO_PREFIX = "running: "` as a public const,
with a unit test asserting `t!("dsl.running", input = "")`
matches it. The catalog template is now contracted to equal
`format!("{ECHO_PREFIX}{input}")` — a translator changing
the prefix breaks the test.

Add `input_render::lex_to_runs(input, theme)` — a
cursor-less variant of `render_input_runs` for use cases
(echo lines, future hint panel) that need token-class
colouring without an inverted cursor.

ui::render_output_line: when the line is an Echo submitted
in Simple mode, peel the prefix and re-tokenise the rest
through lex_to_runs, rendering each token at its class
colour. Advanced-mode echoes and any echo whose body
unexpectedly lacks the prefix fall through to the plain
rendering.

Tests: 683 passing, 0 failing, 1 ignored (682 baseline →
+1 echo_prefix_matches_catalog_template). Clippy clean
(uses let-chain to keep the if condition flat).

Stage 4 adds render-time parse + error overlay so the
failing token in mid-typed input lights up in the error
colour.
This commit is contained in:
claude@clouddev1
2026-05-10 17:32:11 +00:00
parent cafc455c8a
commit 39da399add
3 changed files with 70 additions and 6 deletions
+32 -5
View File
@@ -530,16 +530,43 @@ fn render_output_line<'a>(line: &'a OutputLine, theme: &Theme) -> Line<'a> {
Mode::Simple => Style::default().fg(theme.mode_simple),
Mode::Advanced => Style::default().fg(theme.mode_advanced),
};
let body_style = match line.kind {
OutputKind::Echo => Style::default().fg(theme.fg),
OutputKind::System => Style::default().fg(theme.system),
OutputKind::Error => Style::default().fg(theme.error),
};
let tag = match line.kind {
OutputKind::Echo => format!("[{}] ", line.mode_at_submission.label().to_lowercase()),
OutputKind::System => "[system] ".to_string(),
OutputKind::Error => "[error] ".to_string(),
};
// Simple-mode echo lines get token-class highlighting on
// their input portion (ADR-0022 §5). Echo body shape is
// contracted to `<ECHO_PREFIX><input>`; the prefix is
// pinned to the catalog template by
// `dsl::tests::echo_prefix_matches_catalog_template`.
if line.kind == OutputKind::Echo
&& line.mode_at_submission == Mode::Simple
&& let Some(rest) = line.text.strip_prefix(crate::dsl::ECHO_PREFIX)
{
let mut spans: Vec<Span<'a>> = Vec::with_capacity(2 + rest.len() / 4);
spans.push(Span::styled(tag, tag_style));
spans.push(Span::styled(
crate::dsl::ECHO_PREFIX,
Style::default().fg(theme.fg),
));
for run in crate::input_render::lex_to_runs(rest, theme) {
spans.push(Span::styled(
&rest[run.byte_range.0..run.byte_range.1],
run.style,
));
}
return Line::from(spans);
}
// Echo body without the expected prefix, or any non-echo
// line, falls through to the plain rendering below.
let body_style = match line.kind {
OutputKind::Echo => Style::default().fg(theme.fg),
OutputKind::System => Style::default().fg(theme.system),
OutputKind::Error => Style::default().fg(theme.error),
};
Line::from(vec![
Span::styled(tag, tag_style),
Span::styled(line.text.as_str(), body_style),