複数行を貼り付けると勝手に走る、を直す
Slack や ChatGPT から複数行コマンドを貼ると、Enter のたびに実行されて焦った経験ありませんか。あれを止める小さな規格の話。
こういう複数行をドキュメントからコピーして、ターミナルに貼りつけたい時があります:
cd ~/projectsgit pullcargo build --release素朴なターミナルに貼ると、Enter のたびに順番に実行されます:
$ cd ~/projects$ git pullAlready up to date.$ cargo build --release Compiling ...rm -rf とか drop table とか混じってたら、一発で事故。
これ、ターミナル自身が昔から用意している対策があります。
ターミナルには「ペースト」という概念がない
まず驚くかもしれませんが、ターミナルに「ペースト」なんていうイベントは元々ありません。
OS がブラウザやエディタから貼り付けられた文字を、1 文字ずつキー入力としてターミナルに流し込んでいるだけ。人間が超高速でタイピングしているのと区別がつかないので、改行文字はそのまま Enter キーとして解釈されて、コマンドが走ります。
Bracketed Paste: 「これ貼り付けだよ」印を付ける
Bracketed Paste Mode という仕組みがあって、これを有効にするとターミナルは貼り付けの前後に目印を付けてシェルに渡します:
\x1b[200~ cd ~/projectsgit pullcargo build --release \x1b[201~\x1b[200~→ ペースト開始\x1b[201~→ ペースト終了
シェル(bash / zsh)はこの目印を見たら、「間の改行は Enter じゃなくてただの文字」と解釈します。結果、貼り付けた全部がコマンドラインに並んで、ユーザーが自分で Enter を押すまで実行されない。
ccmux での使い方
ccmux はターミナルの上にさらにペインを載せているので、2 段階の仕事があります。
1. ccmux 自身がペースト検知を有効化:
execute!(stdout, EnableBracketedPaste)?;これで crossterm(端末操作ライブラリ)が、貼り付けを 1 つの Event::Paste(text) イベントにまとめてくれます。
2. 内側の PTY に、印付きで転送:
pub fn forward_paste_to_pty(&mut self, text: &str) -> Result<()> { let mut data = Vec::new(); data.extend_from_slice(b"\x1b[200~"); data.extend_from_slice(text.as_bytes()); data.extend_from_slice(b"\x1b[201~"); self.focused_pane().write_input(&data)?; Ok(())}中の bash は「これ貼り付けだな」と気付いてくれる。
古い環境への保険: 6 バイト閾値
EnableBracketedPaste が効かない古い環境だと、crossterm から普通の Key イベントが超高速で連続して飛んできます。
ccmux はメインループで「1ms 以内に貯まったキー入力の量」を見て、6 バイトより多ければ貼り付けと判定:
if buffer.len() > 6 { // 貼り付け判定 → ブラケット印で包んで送る} else { // 通常のタイピング → そのまま送る}人間のタイピングは速くても 50ms ごとに 1 文字。1ms に 6 文字以上は人間にはできない、という経験則。
気を付けること
- ネストした多重化(ccmux の中で tmux)を動かすとブラケットが二重になって壊れる → だから ccmux はネスト起動を拒否している
- 古い bash(< 4.0)/ 古い zsh(< 5.0)ではサポート外 → いまどきの環境ならまず大丈夫
「ペーストで事故る」の答えは、意外にもターミナル規格の側にもう存在していたというオチ。規格を読んで素直に使うと、大抵の UX 問題は既に解決されています。次は描画ループの話を書きます。