hel: pb multi-sink copy, enc command-consumer, proper help

pb: built-in fan-out to every clipboard on PATH (pbcopy/wl-copy/xclip
-selection clipboard/xsel -ib/tmux in a session); no HEL_PB script needed.
Override via 'set hel_pb' or $HEL_PB still honored; no sink -> data on stdout.

enc: takes a sub-command like pb. 'enc ls|ld <regex>' encodes the last match
(newest for ld); literal name/id wins first. ls/ld emit bare names when
captured (LKEval.capture), so 'pb ld <re>' copies names. Producers limited to
ls/ld (enc never runs a mutating command). 'set hel_enc_strict 1' errors on
>1 match.

help: 'help [topic]' — grouped overview + per-topic detail (every command, the
entry descriptor, all modes with examples, parent/master chaining, config keys,
files/env, Notion store/load subcommands). Old inline help string removed.

Tests added for all three; native + wasm32 builds green.
This commit is contained in:
2026-06-14 17:58:05 +02:00
parent a31ae9669f
commit 4cb3464e86
5 changed files with 565 additions and 67 deletions
+31 -2
View File
@@ -91,7 +91,7 @@ peg::parser! {
rule mode() -> Mode = m:(umode() / rmode()) !['0'..='9' | 'a'..='z' | 'A'..='Z'] { m }
rule noop_cmd() -> Command<'input> = ("#" [' '..='~']*)? { Command::Noop }
rule help_cmd() -> Command<'input> = "help" { Command::Help }
rule help_cmd() -> Command<'input> = "help" t:(_ w:word() { w })? { Command::Help(t) }
rule quit_cmd() -> Command<'input> = "quit" { Command::Quit }
rule pb_cmd() -> Command<'input> = "pb" _ e:$(([' '..='~'])+) { Command::PasteBuffer(e.to_string()) }
rule save_cmd() -> Command<'input> = "save" _ s:$(([' '..='~'])+) { Command::Dump(Some(s.to_string())) }
@@ -114,7 +114,9 @@ peg::parser! {
rule correct_cmd() -> Command<'input> = "correct" _ name:word() { Command::Correct(name) }
rule uncorrect_cmd() -> Command<'input> = "uncorrect" _ name:word() { Command::Uncorrect(name) }
rule unpass_cmd() -> Command<'input> = "unpass" name:(_ w:word() { w })? { Command::UnPass(name) }
rule enc_cmd() -> Command<'input> = "enc" _ name:word() { Command::Enc(name) }
// `enc` takes the rest of the line (like `pb`): a bare name/id, or a
// sub-command whose output names the entry to encode (e.g. `enc ld re`).
rule enc_cmd() -> Command<'input> = "enc" _ e:$(([' '..='~'])+) { Command::Enc(e.to_string()) }
rule rm_cmd() -> Command<'input> = "rm" _ name:word() { Command::Rm(name) }
rule comment_cmd() -> Command<'input> = "comment" _ name:word() c:comment()? { Command::Comment(name, c) }
}
@@ -251,6 +253,33 @@ add t3 C 99 2022-12-14
);
}
#[test]
fn parse_enc_arg_test() {
// `enc` now captures the rest of the line (like `pb`): a bare name/id,
// or a sub-command (`ld <regex>`) resolved at eval time.
assert_eq!(command_parser::cmd("enc t3"), Ok(Command::Enc("t3".to_string())));
assert_eq!(command_parser::cmd("enc 3"), Ok(Command::Enc("3".to_string())));
assert_eq!(
command_parser::cmd("enc ld micro.*exa"),
Ok(Command::Enc("ld micro.*exa".to_string()))
);
assert_eq!(
command_parser::cmd("enc ls foo"),
Ok(Command::Enc("ls foo".to_string()))
);
// Display round-trips.
assert_eq!(Command::Enc("ld micro.*exa".to_string()).to_string(), "enc ld micro.*exa");
}
#[test]
fn parse_help_test() {
assert_eq!(command_parser::cmd("help"), Ok(Command::Help(None)));
assert_eq!(command_parser::cmd("help enc"), Ok(Command::Help(Some("enc".to_string()))));
assert_eq!(command_parser::cmd("help modes"), Ok(Command::Help(Some("modes".to_string()))));
assert_eq!(Command::Help(Some("enc".to_string())).to_string(), "help enc");
assert_eq!(Command::Help(None).to_string(), "help");
}
#[test]
fn parse_short_parent_test() {
// `name ^parent` (no mode/date): name is the name, ^parent stays in the comment