端末エミュレータに対して、リージョンをクリップボードにコピーするための制御文字列(OSC52/PASTE64)をEmacsから投げる方法。

2016/1/23 修正: (osc52-interprogram-cut-function)を間違えて(osc52-select-text-tmux) と書いていた部分を修正。後者はtmux限定のコピー用関数です。合わせてgistも修正しました。

背景

OSC52、またはPASTE64と呼ばれる制御文字列がある。これを(対応した)端末エミュレータが受け取ると、画面には何も表示しない代わりに、引き渡された文字列をOSのクリップボードに格納してくれる1 2。これを活用することは、SSH先のホストでエディタ上に表示されているものをコピーしたいときに特に有効だ。端末上で普通に範囲選択をしてコピーする場合は、「見たままで」しかコピーできない。すなわち、画面バッファを超えた範囲はコピーできないし、画面を分割していたり、エディタで行番号を表示している場合はそのままコピーされてしまう。この状況は、時々あって、割とつらい。

もしもリモートのエディタ上から直接この制御文字列を端末に投げることができれば、端末によって、つまりローカルで解釈されるため、投げられた文字列は直接OSのクリップボードにコピーされて嬉しい。そこで、いつも使っているEmacsからリージョン(選択範囲)内の文字列をこのOSC52/PASTE64制御文字列を送信することができるように設定した次第である。

なお、vimの方はこちら: Vimからクリップボードインテグレーションシーケンス(PASTE64/OSC52)を利用する - Qiita

設定

端末を設定する

端末がOSC52/PASTE64を受け入れるように設定する。

iTerm2の場合は2を、xterm、Tera Term、RLogin、mltermの場合は1が参考になるだろう。

Emacsを設定する

AlexCharltonさんがいい感じのgistを上げてくださってるので利用させてもらう。

https://gist.github.com/AlexCharlton/cc82001c407786f7c1f7

このファイルはいくつかのシンボルを提供するが、重要なのは (osc52-set-cut-function)と、(osc52-interprogram-cut-function)の関数および、カスタム変数osc52-multiplexerだ。関数について、前者は環境変数TERMを使って利用すべき適切な制御文字列を設定する。後者はは第1引数で渡された文字列をクリップボードに送る。

TERM=screenの時には、実際にはユーザはscreenではなくtmuxを使っている可能性があるがこれはプログラムからは分からないので、osc52-multiplexer実際に使っているターミナルマルチプレクサを指定する。

以上をふまえ、load-pathの通ったところにosc52e.elを置き、init.el

(require 'osc52e)
(osc52-set-cut-function)

(custom-set-variables '(osc52-multiplexer 'tmux))
;; (custom-set-variables '(osc52-multiplexer 'screen)) ;; screenを使っている場合はこっち

;; osc52e自体はリージョンを送る関数は提供していないので、自分で定義する
(defun osc52-send-region-to-clipboard (START END)
  "Copy the region to the system clipboard using the OSC 52 escape sequence."
  (interactive "r")
  (osc52-interprogram-cut-function (buffer-substring-no-properties
                           START END)))

;; 適当にバインドする
(global-set-key (kbd "C-x M-w") 'osc52-send-region-to-clipboard)

と書くことで、C-x M-wでクリップボードに文字列がコピーできるようになる。

改良版osc52e.el

せっかくなので(osc52-send-region-to-clipboard)を予め含めるようにosc52e.elを改良したので貼っておく。

https://gist.github.com/mecab/49eabc1978fe3d6dedb3ca5674a16ece

(ちなみに、el-getを使っている場合は

(el-get-bundle gist:49eabc1978fe3d6dedb3ca5674a16ece:osc52e)

でインストールできる。)

このバージョンでは、制御文字列の選択についても改良しており、より多くの環境で適切に端末を推定できる。具体的には、以下の問題を解決している。

  1. 環境によって?Emacsは内部的にTERMの値を"dumb"に変更する。この場合、(getenv "TERM")"dumb"を返すため正しくターミナルを決定できない。そこで、Emacs起動時の環境変数を取得するinitial-environmentを見るようにした。

  2. tmuxユーザの中には、TERM"screen"と申告するのではなく、潔く"tmux"にしたり、"tmux"の文字列をどこかに含める人もいる。この場合、osc52-multiplexerの値にかかわらずtmux用の制御文字列を使うようにした。逆に、元のバージョンではTERMが"screen"から始まらない場合(たとえ"tmux"であったとしても)osc52-multiplexerを無視して、ターミナルマルチプレクサを想定しない制御文字列が送信されるので注意が必要である。

終わりに

本記事は、実はskajiさんによるOSC52版pbcopyに投げて同等のことを実現するelispを自分で書いていて、その公開のための記事になる予定だった。

が、結局のところ執筆中にググっていたらうっかり上記のosc52e.elを見つけてしまい、これのほうがemacs単体で完結するしいいやん!!!!!慣れないelispと格闘して潰した1日半はなんだったんだ!!!!!!という気持ちでこの記事は執筆された。

せっかくなので供養させてください!!!!🙏🙏🙏

https://gist.github.com/mecab/2ea01bf0059c40795fae2becb681d059

予告

ところで、この方法でも問題があって、SSH先でもtmuxを立ち上げていると利用できない。なぜなら

 [ssh先のpty] 
      |
(tmux用の制御文字列)
      ↓
[ssh先のtmux]
      |
(xterm用の制御文字列)
      ↓
 [ローカルのtmux]
      |
(tmuxが制御文字列を解釈しないので消える)
      ↓
[端末エミュレータ]

という構成になるので、手元まで制御文字列が届かないためである。それに対する真に驚くべき解決策を友人とともに見つけたが、この記事はそれを書くには狭すぎる。

のでこれについてはまた後日。

style="display:block" data-ad-client="ca-pub-5314531653763426" data-ad-slot="6600226595" data-ad-format="auto">


参考文献