diff --git a/crates/cli/src/learn.md b/crates/cli/src/learn.md index dcb6254b..ce65339a 100644 --- a/crates/cli/src/learn.md +++ b/crates/cli/src/learn.md @@ -511,13 +511,18 @@ and `YAS_NET=0` govern it. The proxy ends with the process. ```bash yas clipboard list # list available MIME types -yas clipboard get # read clipboard (text/plain) +yas clipboard get # read clipboard as plain text yas clipboard get --mime image/png > shot.png # read specific MIME type yas clipboard set "hello" # set clipboard from argument echo "hello" | yas clipboard set # set clipboard from stdin yas clipboard set --mime image/png < shot.png # set specific MIME type ``` +`set` offers text as `text/plain;charset=utf-8`. A plain-text `get` +(`text/plain`, with or without a charset, or `UTF8_STRING`) reads whichever +plain-text variant the owner offered, so set-then-get round-trips; other MIME +types must match a listed type exactly. + ## GUI surfaces On Linux, GUI apps launched in a terminal connect to the built-in Wayland compositor automatically. diff --git a/crates/cli/src/yas_extension_manage.rs b/crates/cli/src/yas_extension_manage.rs index eda4f409..e7b49e2f 100644 --- a/crates/cli/src/yas_extension_manage.rs +++ b/crates/cli/src/yas_extension_manage.rs @@ -30,8 +30,9 @@ const MAX_MANIFEST_BYTES: u64 = 1024 * 1024; #[derive(Args, Clone, Debug)] pub(crate) struct ManageArgs { - /// Registry directory containing manifest.json and extension modules - #[arg(long, default_value = DEFAULT_REGISTRY)] + /// Registry base URL serving manifest.json and extension modules + /// (https://, or http:// on a loopback host) + #[arg(long, value_name = "URL", default_value = DEFAULT_REGISTRY)] pub from: String, } diff --git a/crates/cli/src/yas_selection.rs b/crates/cli/src/yas_selection.rs index bfa49d3c..3d7a279c 100644 --- a/crates/cli/src/yas_selection.rs +++ b/crates/cli/src/yas_selection.rs @@ -30,6 +30,7 @@ pub(crate) async fn cmd_get(on: Option<&str>, hub: &str, mime: &str) -> Result<( async fn get_with_client(client: &mut NativeClient, mime: &str) -> Result, String> { let record = clipboard_record(client).await?; + let mime = resolve_mime(mime, &record.mime_types); let result: selection::GetResult = client .request_typed( family::SELECTION, @@ -40,7 +41,7 @@ async fn get_with_client(client: &mut NativeClient, mime: &str) -> Result String { + if let Some(exact) = offered + .iter() + .find(|mime| mime.eq_ignore_ascii_case(requested)) + { + return exact.clone(); + } + let is_plain_text = |mime: &str| { + let base = mime.split(';').next().unwrap_or_default().trim(); + base.eq_ignore_ascii_case("text/plain") || mime.eq_ignore_ascii_case("UTF8_STRING") + }; + if !is_plain_text(requested) { + return requested.to_owned(); + } + let utf8 = |mime: &&String| { + let lower = mime.to_ascii_lowercase().replace(' ', ""); + lower == "text/plain;charset=utf-8" || lower == "utf8_string" + }; + offered + .iter() + .find(utf8) + .or_else(|| offered.iter().find(|mime| is_plain_text(mime))) + .cloned() + .unwrap_or_else(|| requested.to_owned()) +} + async fn clipboard_record(client: &mut NativeClient) -> Result { let records = client .snapshot(family::SELECTION) @@ -233,6 +263,31 @@ mod tests { transfer::{ByteData, Close, Descriptor, Direction, Mode, UploadStage}, }; + #[test] + fn plain_text_requests_read_any_offered_plain_text_variant() { + let offered = |types: &[&str]| types.iter().map(|t| t.to_string()).collect::>(); + let utf8 = offered(&["image/png", "text/plain;charset=utf-8"]); + assert_eq!( + resolve_mime("text/plain", &utf8), + "text/plain;charset=utf-8" + ); + let plain = offered(&["text/plain"]); + assert_eq!( + resolve_mime("text/plain;charset=utf-8", &plain), + "text/plain" + ); + let both = offered(&["text/plain", "UTF8_STRING", "text/plain;charset=utf-8"]); + assert_eq!(resolve_mime("text/plain", &both), "text/plain"); + assert_eq!(resolve_mime("TEXT/PLAIN", &both), "text/plain"); + let x11 = offered(&["UTF8_STRING", "STRING"]); + assert_eq!(resolve_mime("text/plain", &x11), "UTF8_STRING"); + assert_eq!(resolve_mime("image/png", &plain), "image/png"); + assert_eq!( + resolve_mime("text/plain", &offered(&["image/png"])), + "text/plain" + ); + } + #[test] fn inline_boundary_accounts_for_the_typed_item_envelope() { let mime = "text/plain";