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:
@@ -26,3 +26,31 @@ pub use command::{
|
|||||||
pub use parser::{ParseError, parse_command};
|
pub use parser::{ParseError, parse_command};
|
||||||
pub use types::Type;
|
pub use types::Type;
|
||||||
pub use value::Value;
|
pub use value::Value;
|
||||||
|
|
||||||
|
/// Prefix every echoed DSL command carries in the output
|
||||||
|
/// panel — i.e. `[simple] running: <input>` reads as
|
||||||
|
/// `[simple]` (tag) + `running: ` (this constant) + the
|
||||||
|
/// user's input.
|
||||||
|
///
|
||||||
|
/// The catalog template `dsl.running` is contracted to equal
|
||||||
|
/// `format!("{ECHO_PREFIX}{{input}}")`. The constant lives
|
||||||
|
/// in code because the echo-line renderer (ADR-0022 §5)
|
||||||
|
/// peels this prefix off and re-tokenises the rest for
|
||||||
|
/// highlighting; a unit test (`echo_prefix_matches_catalog_template`)
|
||||||
|
/// pins the binding.
|
||||||
|
pub const ECHO_PREFIX: &str = "running: ";
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::ECHO_PREFIX;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn echo_prefix_matches_catalog_template() {
|
||||||
|
// The catalog template `dsl.running` must produce
|
||||||
|
// `<ECHO_PREFIX><input>` so the echo-line renderer can
|
||||||
|
// peel the prefix and re-tokenise the rest. A
|
||||||
|
// translator changing the prefix breaks this test.
|
||||||
|
let rendered = crate::t!("dsl.running", input = "");
|
||||||
|
assert_eq!(rendered, ECHO_PREFIX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
+10
-1
@@ -55,11 +55,20 @@ impl StyledRun {
|
|||||||
/// the cursor at `cursor_byte` (clamped to `input.len()`).
|
/// the cursor at `cursor_byte` (clamped to `input.len()`).
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn render_input_runs(input: &str, cursor_byte: usize, theme: &Theme) -> Vec<StyledRun> {
|
pub fn render_input_runs(input: &str, cursor_byte: usize, theme: &Theme) -> Vec<StyledRun> {
|
||||||
let mut runs = base_runs(input, theme);
|
let mut runs = lex_to_runs(input, theme);
|
||||||
inject_cursor(&mut runs, input, cursor_byte, theme);
|
inject_cursor(&mut runs, input, cursor_byte, theme);
|
||||||
runs
|
runs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Cursor-less variant: tokenises `input` into styled runs
|
||||||
|
/// covering the full byte range, with no inverted cursor.
|
||||||
|
/// Used by the echo-line renderer (ADR-0022 §5) where there's
|
||||||
|
/// no cursor to show.
|
||||||
|
#[must_use]
|
||||||
|
pub fn lex_to_runs(input: &str, theme: &Theme) -> Vec<StyledRun> {
|
||||||
|
base_runs(input, theme)
|
||||||
|
}
|
||||||
|
|
||||||
fn base_runs(input: &str, theme: &Theme) -> Vec<StyledRun> {
|
fn base_runs(input: &str, theme: &Theme) -> Vec<StyledRun> {
|
||||||
let tokens = lex(input);
|
let tokens = lex(input);
|
||||||
let mut runs = Vec::with_capacity(tokens.len() * 2);
|
let mut runs = Vec::with_capacity(tokens.len() * 2);
|
||||||
|
|||||||
@@ -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::Simple => Style::default().fg(theme.mode_simple),
|
||||||
Mode::Advanced => Style::default().fg(theme.mode_advanced),
|
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 {
|
let tag = match line.kind {
|
||||||
OutputKind::Echo => format!("[{}] ", line.mode_at_submission.label().to_lowercase()),
|
OutputKind::Echo => format!("[{}] ", line.mode_at_submission.label().to_lowercase()),
|
||||||
OutputKind::System => "[system] ".to_string(),
|
OutputKind::System => "[system] ".to_string(),
|
||||||
OutputKind::Error => "[error] ".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![
|
Line::from(vec![
|
||||||
Span::styled(tag, tag_style),
|
Span::styled(tag, tag_style),
|
||||||
Span::styled(line.text.as_str(), body_style),
|
Span::styled(line.text.as_str(), body_style),
|
||||||
|
|||||||
Reference in New Issue
Block a user