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:
+50
-1
@@ -7,7 +7,7 @@
|
||||
//! builder, help / usage references. The ast_builders match
|
||||
//! against the `MatchedPath` items in declaration order.
|
||||
|
||||
use crate::dsl::command::{AppCommand, Command, MessagesValue, ModeValue};
|
||||
use crate::dsl::command::{AppCommand, Command, CopyScope, MessagesValue, ModeValue};
|
||||
use crate::dsl::grammar::{
|
||||
CommandNode, HintMode, IdentSource, IdentValidator, Node, ValidationError,
|
||||
Word,
|
||||
@@ -37,8 +37,16 @@ fn validate_unknown_messages(value: &str) -> Result<(), ValidationError> {
|
||||
})
|
||||
}
|
||||
|
||||
fn validate_unknown_copy(value: &str) -> Result<(), ValidationError> {
|
||||
Err(ValidationError {
|
||||
message_key: "copy.unknown",
|
||||
args: vec![("value", value.to_string())],
|
||||
})
|
||||
}
|
||||
|
||||
const UNKNOWN_MODE_VALIDATOR: IdentValidator = validate_unknown_mode;
|
||||
const UNKNOWN_MESSAGES_VALIDATOR: IdentValidator = validate_unknown_messages;
|
||||
const UNKNOWN_COPY_VALIDATOR: IdentValidator = validate_unknown_copy;
|
||||
|
||||
// --- Shapes (constants are referenced by Optional/Choice slices) --
|
||||
|
||||
@@ -114,6 +122,29 @@ const MESSAGES_CHOICES: &[Node] = &[
|
||||
const MESSAGES_VALUE: Node = Node::Choice(MESSAGES_CHOICES);
|
||||
const MESSAGES_VALUE_OPT: Node = Node::Optional(&MESSAGES_VALUE);
|
||||
|
||||
// `copy [all|last]`: same shape as `messages` — known scope words are
|
||||
// `Word` siblings (so they reach completion + the expected set); the
|
||||
// trailing catch-all `Ident` funnels any other word into the friendly
|
||||
// `copy.unknown` validator. Bare `copy` (no value) means `all`.
|
||||
const COPY_CHOICES: &[Node] = &[
|
||||
Node::Word(Word::keyword("all")),
|
||||
Node::Word(Word::keyword("last")),
|
||||
Node::Ident {
|
||||
source: IdentSource::Free,
|
||||
role: "copy_value",
|
||||
validator: Some(UNKNOWN_COPY_VALIDATOR),
|
||||
highlight_override: None,
|
||||
writes_table: false,
|
||||
writes_column: false,
|
||||
writes_user_listed_column: false,
|
||||
writes_table_alias: false,
|
||||
writes_cte_name: false,
|
||||
writes_projection_alias: false,
|
||||
},
|
||||
];
|
||||
const COPY_VALUE: Node = Node::Choice(COPY_CHOICES);
|
||||
const COPY_VALUE_OPT: Node = Node::Optional(©_VALUE);
|
||||
|
||||
const EMPTY_SEQ: Node = Node::Seq(&[]);
|
||||
const SAVE_AS_OPT: Node = Node::Optional(&SAVE_AS_WORD);
|
||||
|
||||
@@ -202,6 +233,17 @@ fn build_messages(path: &MatchedPath, _source: &str) -> Result<Command, Validati
|
||||
Ok(Command::App(AppCommand::Messages { value }))
|
||||
}
|
||||
|
||||
fn build_copy(path: &MatchedPath, _source: &str) -> Result<Command, ValidationError> {
|
||||
// The unknown-value branch's validator always errors, so reaching
|
||||
// here means either a known scope word or a bare `copy` (= all).
|
||||
let scope = if path.contains_word("last") {
|
||||
CopyScope::Last
|
||||
} else {
|
||||
CopyScope::All
|
||||
};
|
||||
Ok(Command::App(AppCommand::Copy { scope }))
|
||||
}
|
||||
|
||||
// --- Command nodes -------------------------------------------------
|
||||
|
||||
pub static QUIT: CommandNode = CommandNode {
|
||||
@@ -287,3 +329,10 @@ pub static REDO: CommandNode = CommandNode {
|
||||
ast_builder: build_redo,
|
||||
help_id: Some("app.redo"),
|
||||
usage_ids: &["parse.usage.redo"],};
|
||||
|
||||
pub static COPY: CommandNode = CommandNode {
|
||||
entry: Word::keyword("copy"),
|
||||
shape: COPY_VALUE_OPT,
|
||||
ast_builder: build_copy,
|
||||
help_id: Some("app.copy"),
|
||||
usage_ids: &["parse.usage.copy"],};
|
||||
|
||||
@@ -613,6 +613,7 @@ pub static REGISTRY: &[(&CommandNode, CommandCategory)] = &[
|
||||
(&app::MESSAGES, CommandCategory::Simple),
|
||||
(&app::UNDO, CommandCategory::Simple),
|
||||
(&app::REDO, CommandCategory::Simple),
|
||||
(&app::COPY, CommandCategory::Simple),
|
||||
(&ddl::DROP, CommandCategory::Simple),
|
||||
(&ddl::ADD, CommandCategory::Simple),
|
||||
(&ddl::RENAME, CommandCategory::Simple),
|
||||
|
||||
Reference in New Issue
Block a user