feat: copy the output panel to the system clipboard (#11)
New app-level `copy` / `copy all` / `copy last` command (ADR-0041). Delivery is OSC 52 *and* a best-effort native write (arboard), always both — OSC 52 acceptance is undetectable, so a true fallback can't be built. Payload is the panel's plain text exactly as rendered (tags, ✓/✗, box-drawing), drift-locked to render_output_line. arboard added --no-default-features (X11-only; OSC 52 covers Wayland). Amends ADR-0003's command registry; requirements V6.
This commit is contained in:
@@ -1376,6 +1376,81 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_text_matches_rendered_line_content() {
|
||||
// ADR-0041 drift-lock: `OutputLine::plain_text()` (the `copy`
|
||||
// payload) must equal the visible content `render_output_line`
|
||||
// produces — the concatenation of its span texts — for every
|
||||
// line shape. If the renderer changes how a line reads, this
|
||||
// fails until `plain_text` is brought back in step, so the
|
||||
// clipboard can never silently diverge from the screen.
|
||||
let theme = Theme::dark();
|
||||
let label = crate::echo::TEACHING_ECHO_LABEL;
|
||||
|
||||
let mut pending = OutputLine::echo("create table T", Mode::Simple);
|
||||
pending.status = Some(EchoStatus::Pending);
|
||||
let mut ok = OutputLine::echo("create table T", Mode::Simple);
|
||||
ok.status = Some(EchoStatus::Ok);
|
||||
let mut err = OutputLine::echo("insert into T values (1)", Mode::Advanced);
|
||||
err.status = Some(EchoStatus::Err);
|
||||
|
||||
let lines = vec![
|
||||
pending,
|
||||
ok,
|
||||
err,
|
||||
OutputLine {
|
||||
text: " T".to_string(),
|
||||
kind: OutputKind::System,
|
||||
mode_at_submission: Mode::Simple,
|
||||
styled_runs: None,
|
||||
status: None,
|
||||
},
|
||||
OutputLine {
|
||||
text: "no such table".to_string(),
|
||||
kind: OutputKind::Error,
|
||||
mode_at_submission: Mode::Simple,
|
||||
styled_runs: None,
|
||||
status: None,
|
||||
},
|
||||
OutputLine {
|
||||
text: format!("{label}CREATE TABLE T (id serial)"),
|
||||
kind: OutputKind::TeachingEcho,
|
||||
mode_at_submission: Mode::Advanced,
|
||||
styled_runs: None,
|
||||
status: None,
|
||||
},
|
||||
OutputLine::styled(
|
||||
"SCAN Customers".to_string(),
|
||||
OutputKind::System,
|
||||
Mode::Simple,
|
||||
vec![
|
||||
OutputSpan {
|
||||
byte_range: (0, 4),
|
||||
class: OutputStyleClass::Expensive,
|
||||
},
|
||||
OutputSpan {
|
||||
byte_range: (4, 14),
|
||||
class: OutputStyleClass::Neutral,
|
||||
},
|
||||
],
|
||||
),
|
||||
];
|
||||
|
||||
for line in &lines {
|
||||
let rendered: String = render_output_line(line, &theme)
|
||||
.spans
|
||||
.iter()
|
||||
.map(|s| s.content.as_ref())
|
||||
.collect();
|
||||
assert_eq!(
|
||||
line.plain_text(),
|
||||
rendered,
|
||||
"plain_text drifted from render for a {:?} line",
|
||||
line.kind,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn category_three_prose_line_renders_all_dim() {
|
||||
// ADR-0038 §6: the existing illuminating client_side notes and
|
||||
|
||||
Reference in New Issue
Block a user