ui: styled-output-line mechanism (ADR-0028 step 1)

OutputLine gains an optional styled-runs payload — a
Vec<OutputSpan> of { byte_range, OutputStyleClass } over the
line text. render_output_line gains a branch: when the payload
is present it renders the text span-by-span, each run's
semantic class (Neutral / Efficient / Expensive /
AutomaticIndex) resolved to a theme colour at render time;
otherwise the existing whole-line kind styling. The echo path
is untouched.

Theme gains `plan_efficient` — a green deliberately distinct
from `system` so green never reads as two things (ADR-0028 §6);
`warning` is reused for expensive steps.

A general per-span output-styling capability (ADR-0016's OOS-3
realized); the query-plan renderer will be its first consumer.
No user-visible change on its own. 1133 passing, clippy clean.
This commit is contained in:
claude@clouddev1
2026-05-19 10:45:43 +00:00
parent a1e4932858
commit 03d8a09457
3 changed files with 152 additions and 3 deletions
+60
View File
@@ -30,11 +30,65 @@ pub enum OutputKind {
Error,
}
/// The semantic style class of an [`OutputSpan`] (ADR-0028 §5).
///
/// A general output-styling vocabulary, resolved to a concrete
/// theme colour at render time — never a baked-in colour. The
/// query-plan renderer (ADR-0028) is its first consumer.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputStyleClass {
/// Default foreground — connectors, names, structural text.
Neutral,
/// An efficient query-plan step — an index search, a
/// covering index, a primary-key lookup.
Efficient,
/// An expensive query-plan step — a full table scan or a
/// temp B-tree.
Expensive,
/// An automatic-index step — the engine built a temporary
/// index because none existed; the strongest "add an index
/// here" signal.
AutomaticIndex,
}
/// A styled span of an output line: a byte range over the
/// line's text and the semantic class it carries (ADR-0028 §5).
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct OutputSpan {
/// Half-open byte range `[start, end)` into the line text.
pub byte_range: (usize, usize),
pub class: OutputStyleClass,
}
#[derive(Debug, Clone)]
pub struct OutputLine {
pub text: String,
pub kind: OutputKind,
pub mode_at_submission: Mode,
/// Optional per-span styling (ADR-0028 §5). When `Some`,
/// `render_output_line` colours the text span-by-span from
/// these runs; when `None` it falls back to whole-line
/// styling by `kind`.
pub styled_runs: Option<Vec<OutputSpan>>,
}
impl OutputLine {
/// An output line carrying per-span styled runs (ADR-0028
/// §5) — the text is coloured per `runs`, not by `kind`.
#[must_use]
pub const fn styled(
text: String,
kind: OutputKind,
mode_at_submission: Mode,
runs: Vec<OutputSpan>,
) -> Self {
Self {
text,
kind,
mode_at_submission,
styled_runs: Some(runs),
}
}
}
/// What mode the next submission would be evaluated in.
@@ -948,6 +1002,7 @@ impl App {
text: effective_input,
kind: OutputKind::Echo,
mode_at_submission: effective_mode,
styled_runs: None,
});
Vec::new()
}
@@ -1052,6 +1107,7 @@ impl App {
text: crate::t!("dsl.running", input = input),
kind: OutputKind::Echo,
mode_at_submission: submission_mode,
styled_runs: None,
});
vec![Action::Replay { path }]
}
@@ -1060,6 +1116,7 @@ impl App {
text: crate::t!("dsl.running", input = input),
kind: OutputKind::Echo,
mode_at_submission: submission_mode,
styled_runs: None,
});
vec![Action::ExecuteDsl {
command: cmd,
@@ -1074,6 +1131,7 @@ impl App {
text: crate::t!("dsl.running", input = input),
kind: OutputKind::Echo,
mode_at_submission: submission_mode,
styled_runs: None,
});
// Caret pointer at the failure position, when we
// have one. Aligned to the "running: " prefix so
@@ -1754,6 +1812,7 @@ impl App {
text,
kind,
mode_at_submission: self.mode,
styled_runs: None,
});
return;
}
@@ -1762,6 +1821,7 @@ impl App {
text: line.to_string(),
kind,
mode_at_submission: self.mode,
styled_runs: None,
});
}
}