73 lines
3.7 KiB
Rust
73 lines
3.7 KiB
Rust
//! 把 UTF-8 文本读写适配到当前平台的系统剪贴板命令。
|
|
//! 外部命令失败作为普通错误交给调用方处理;这里不解析或执行剪贴板中的程序文本。
|
|
//!
|
|
//! Clipboard text is always UTF-8, including Finder/LaunchServices launches without LANG.
|
|
use anyhow::{Result, bail};
|
|
#[cfg(target_os = "macos")]
|
|
/// 构造固定 UTF-8 环境的系统命令;本函数不启动进程,退出状态由 read/write 检查。
|
|
// 仅 macOS 的 read/write 使用,构造独立 Command 并覆盖编码环境,不修改宿主进程全局环境变量。
|
|
fn command(program: &str) -> std::process::Command {
|
|
let mut cmd = std::process::Command::new(program);
|
|
// 从 Finder 等无 LANG 的环境启动时也显式指定 UTF-8,防止系统工具按其他编码转换中文。
|
|
cmd.env("LC_ALL", "en_US.UTF-8")
|
|
.env("LC_CTYPE", "en_US.UTF-8")
|
|
.env("LANG", "en_US.UTF-8");
|
|
cmd
|
|
}
|
|
/// 取得系统剪贴板的 UTF-8 文本,失败作为可显示错误返回。
|
|
// shell/app 处理开发工具粘贴快捷键时调用;失败由工具 input_error 展示,不用空字符串冒充成功。
|
|
pub fn read() -> Result<String> {
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
// 直接启动固定路径程序并传递独立参数,不经过 shell;-Prefer txt 要求文本表示。
|
|
let output = command("/usr/bin/pbpaste")
|
|
.arg("-Prefer")
|
|
.arg("txt")
|
|
.output()?;
|
|
// 进程正常退出但状态非成功仍为错误,不能把可能残缺的 stdout 当作源代码粘贴。
|
|
if !output.status.success() {
|
|
bail!("system clipboard read failed");
|
|
}
|
|
// Never silently replace invalid bytes in the user's source.
|
|
// 严格验证输出 UTF-8,非法字节报错而不是替换为 U+FFFD 后悄悄改变用户文本。
|
|
Ok(String::from_utf8(output.stdout)?)
|
|
}
|
|
// 平台分支在编译时选择,非 macOS 不尝试猜测其他系统剪贴板命令。
|
|
#[cfg(not(target_os = "macos"))]
|
|
{
|
|
bail!("system clipboard adapter is not available on this platform")
|
|
}
|
|
}
|
|
/// 将提供的文本交给系统剪贴板命令,不执行文本内容。
|
|
// shell/app 的复制与剪切路径调用;只有成功返回后剪切才会删除编辑器中的选中文本。
|
|
pub fn write(value: &str) -> Result<()> {
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
use std::io::Write;
|
|
// 正文只经子进程标准输入传递,不拼成命令参数,因此引号、换行和源码中的命令字符保持原意。
|
|
let mut child = command("/usr/bin/pbcopy")
|
|
.stdin(std::process::Stdio::piped())
|
|
.spawn()?;
|
|
// 取走子进程管道句柄,后面可显式 drop 发送 EOF,不能让 pbcopy 一直等待更多输入。
|
|
let mut stdin = child
|
|
.stdin
|
|
.take()
|
|
.ok_or_else(|| anyhow::anyhow!("clipboard stdin is unavailable"))?;
|
|
// 按 UTF-8 原字节写完整内容;写管道失败立即传播,此函数该错误路径没有另外执行 wait。
|
|
stdin.write_all(value.as_bytes())?;
|
|
// 写完后先关闭管道发送 EOF,让 pbcopy 知道输入结束;之后等待退出状态,不能仅以写管道成功作为完成。
|
|
drop(stdin);
|
|
// 等待真实系统工具退出并检查状态;写入成功和剪贴板操作完成是两个独立检查。
|
|
if !child.wait()?.success() {
|
|
bail!("system clipboard write failed");
|
|
}
|
|
Ok(())
|
|
}
|
|
#[cfg(not(target_os = "macos"))]
|
|
{
|
|
// 非 macOS 当前没有实现适配器,显式报错而不伪装成写入成功。
|
|
let _ = value;
|
|
bail!("system clipboard adapter is not available on this platform")
|
|
}
|
|
}
|