第6回関西Emacs勉強会で, Emacs上で最強のターミナル(端末)環境を実現する話をしてきました. 以下がそのとき用いたスライドです.
このスライドだけでは, とりあえず使ってみるのではなく常用したい場合にどうしたらよいかわかりにくいと思うので, その辺りを補足しながら, きちんとしたドキュメントを書くまでの暫定の使い方を書いておこうと思います.
更新履歴
- 2012-11-07
- 端末バッファを別ウィンドウに開くコマンドの仕様変更(term-plus-mux-el@ed343fb)に追従
- 2012-10-24
- リポジトリ構成の変更に伴い配布場所とインストール方法の説明を変更
term+mux-new
のセッションを訊く条件が変更されたのを反映
これは何?
Emacs上の端末エミュレータです. もともとterm.elというものがEmacsに標準添付されていてM-x term
で利用できますが, かなり古いものということもあり, 機能面で不満がありました. また, GNU screenなどのマルチプレクサを用いている場合は, その機能も持った端末でなければ乗り換え対象として挙げづらいという事情もあります.
これらの問題を解消して, Emacs上に開いた端末を積極利用できるように, term.elを大幅に強化するのがterm+.elです.
サポート情報
Emacs 22に対応する予定はありません. 開発はEmacs 24.1.50で行なったため, おそらくEmacs 24.2が一番安定して動作します. Emacs 23は対応が甘くバグが残っているかもしれません.
何かバグを見つけたり意見・要望がある場合はissue trackerにお願いします. 既に自分でいくつかissueを英語で登録していますが, 海外ユーザがすごく増えたりしない間は英語が苦手なら日本語で書いてもらっても構わないと思います.
ソースリポジトリの構成と依存パッケージ
配布場所 | ファイル | 外部依存 | 内容 |
---|---|---|---|
github:tarao/term-plus-el | term+.el | なし | 必須ファイル. Emacsに標準で入っているパッケージのみに依存しています. |
term+vars.el | |||
term+input.el | |||
term+edit.el | |||
term+file-transfer.el | |||
term+logging.el | |||
term+shell-history.el | |||
xterm-256color.el | 256色対応のためのファイル. これをrequire しなくても他の機能は使えます. | ||
github:tarao/term-plus-ki-el | term+key-intercept.el | key-intercept.el | ESC やC-c を端末に送る/Emacsで解釈するという2通りをうまく使いわけたい場合に必要です. |
github:tarao/term-plus-mode-el | term+mode.el | multi-mode-util.el | 編集モードの入力フィールドの部分だけterm-mode 以外のメジャーモード(たとえばsh-mode )にしたい場合に必要です. |
multi-mode.el | |||
github:tarao/term-plus-mux-el | term+mux.el | tab-group.el | マルチプレクサとしての機能を使いたい場合は必要です. |
github:tarao/term-plus-evil-el | term+evil.el | Evil | Evilユーザのためのパッチです. |
github:tarao/term-plus-ash-el | term+anything-shell-history.el | anything-complete.el | term+shell-hisotry.elによるシェルの履歴検索をanythingでやるようにするためのファイルです. |
とりあえず試してみる
term+.elの設定以外は何も読み込まない状態で試すには以下のようにします. ただし, 外部依存ファイルもすべてダウンロードするため時間がかかります.
$ git clone git://github.com/tarao/term-plus-all.git $ cd term-plus-all $ git submodule update --init $ make emacs # ウィンドウを開く場合 $ make term # emacs -nwで開く場合 $ make EMACS=emacs-snapshot term # emacs-snapshot -nwで開く場合
他の個人設定(.emacsやinit.el)といっしょに使う場合は, term+.el本体と外部依存ファイルを適切に配置し, term+.elのロード設定をする必要があります.
インストール
まず, 上記の配布元から必要なファイルと外部依存ファイルをダウンロードします.
必須ファイルのみを使う場合は, 必要なファイルをロードパスのどこかに置いた上で次のようにします.
(require 'term+)
加えて, 256色対応が必要な場合はxterm-256color.elもロードパスのどこかに置いた上で次のようにします.
(require 'xterm-256color)
term+key-intercept.elを利用する場合は, 依存ファイルを含めてロードパスのどこかに置いた上で次のようにします.
(require 'term+key-intercept)
term+mode.elを利用する場合は, 依存ファイルを含めてロードパスのどこかに置いた上で次のようにします.
(require 'term+mode)
term+mux.elを利用する場合は, 依存ファイルを含めてロードパスのどこかに置いた上で次のようにします.
(require 'term+mux)
term+evil.elを利用する場合は, 依存ファイルを含めてロードパスのどこかに置いた上で次のようにします.
(eval-after-load 'evil '(progn (require 'term+evil) (when (featurep 'term+mode) (require 'multi-mode+evil))))
term+anything-shell-history.elを利用する場合は, 依存ファイルを含めてロードパスのどこかに置いた上で次のようにします.
(require 'term+anything-shell-history)
端末の起動
単一の端末
従来通りM-x term
あるいはM-x ansi-term
すると, term+.elが有効になった端末バッファを開くことができます. 端末内のプロセス(シェルなど)が終了すると, デフォルトではバッファも閉じられます. この挙動を変更するにはterm+kill-buffer-at-exit
変数の値をnil
にします.
端末をタブで開く
term+mux.elが必要です. 以下のコマンドで, マルチプレクサで管理された端末のタブを開くことができます.
M-x term+mux-new
- 新しいタブに端末を開きます. 現在セッションが選択されていればそのセッションのタブを, 選択されていなければ, セッションが複数ある場合はどのセッションのタブを開くか訊いてから, セッションが1つしかない場合はそのセッションのタブを開きます.
M-x term+mux-other-window
- 既に現在のセッションあるいはデフォルトセッションに端末バッファがあれば, それを別ウィンドウに開きます. 端末バッファがない場合は新しい端末バッファを別ウィンドウに開きます.
M-x term+mux-new-other-window
term+mux-new
と同じですが, 別のウィンドウに開きます.M-x term+mux-noselect
term+mux-new
と同じですが, 新しい端末バッファを選択しません.M-x term+mux-new-command
term+mux-new
と同じですが, 端末内で実行するコマンド(デフォルトはシェル)を指定します.M-x term+mux-new-session
- 新しいセッションを作成します. そのセッションにタブが1つもなければ
term+mux-new
を使って1つ開きます. M-x term+mux-remote-session
term+mux-new-session
と同じですが, ユーザ名, ホスト名, セッション名を指定してセッションを作成します.
ターミナルモード
最初に端末を開いたときのモードです. このモードではほとんどのキーはそのまま端末内アプリに送られます. Emacs側で解釈されるキーとその意味は次の通りです.
C-c
- プレフィックスキー (term.elにもともと用意されているコマンドを利用できます. term+key-intercept.elを利用している場合は, 後続のキーを即座に打たなかった場合には
C-c
を端末に送ります.) C-c C-e
ESC
を端末に送る (term+key-intercept.elを利用している場合は単にESC
キーを打てば同様のことができます.)C-c C-c
C-c
を端末に送るC-c h
- 端末画面のハードコピーを取る
C-c l
- 端末の出力テキストログのファイルへの保存を開始/終了
C-c r
- 端末を録画開始/終了
C-q
- 後続のキーをそのまま端末に送る
C-y
- killリング(クリップボード)の中身を端末にyank(ペースト)
C-x
- Emacsの通常の
C-x
キーとして動作 M-x
- Emacsの通常の
M-x
キーとして動作 M-:
- Emacsの通常の
M-:
キーとして動作 M-RET
- 編集モードに移行
ターミナルモードでEmacs側で解釈されるキーを増やすには, term+char-map
に対してdefine-key
します. たとえば, C-z
で他のバッファを前面に出すには以下のように設定します.
(define-key term+char-map (kbd "C-z") #'bury-buffer)
編集モード
ターミナルモードからM-RET
で編集モードに入ると, 端末内のカーソル位置以外の領域は読み取り専用になり, カーソル位置に入力フィールドが設定されます. 入力フィールド内はEmacsの通常のバッファと同様に編集でき, RET
で入力フィールドの内容が端末に送られます. 入力フィールド内で改行するにはC-j
を使います.
入力フィールドでのキーバインドはterm+line-map
で設定できます. デフォルトでは次のようになっています.
RET
- 入力フィールドの内容を端末に送信
C-a
- 行頭へ移動し, 入力フィールドの先頭で一度止まる
C-e
- 行末へ移動し, 入力フィールドの末尾で一度止まる
C-k
- カーソル位置から行末までを削除し, 入力フィールドの末尾以降は残す
C-c C-u
- 入力フィールドの内容を削除
C-c C-w
- 入力フィールドの内容を1単語だけ削除
M-p
- 1つ前の入力内容を表示
M-n
- 1つ後の入力内容を表示
M-RET
- ターミナルモードに戻る
term+mode.elを利用すると入力フィールドの中だけ別のメジャーモード(たとえばsh-mode
)にすることができます. 詳しくはシェル連携についての説明を見て下さい.
入力フィールドの外ではスペースキーで範囲選択を開始できます. もう一度スペースキーを押すと選択された範囲をコピーして編集モードを終了します. RET
やESC
でも編集モードを終了できます. 入力フィールドの外で有効になるキーバインドはterm+input-readonly-map
で設定できます.
ログ機能
ターミナルモードでは標準で3種類のログ機能を利用できます.
ハードコピー
GNU screenにおける:hardcopy
と同等の機能です.
C-c h
またはM-x term+hardcopy
で, 端末の1画面に表示されている内容をテキストファイルに保存できます. term+hardcopy-visible-contents
をnil
に設定すると, 1画面ではなく端末バッファ内のすべての内容を保存します. term+hardcopy-append
をt
に設定すると, 保存先のファイルが存在する場合は末尾に追記するようになります. この際, 追記された内容ともともとのファイルの内容との間にはterm+hardcopy-separator
に設定された区切りが挿入されます. term+hardcopy-separator
の詳細はM-x describe-variable
, term+hardcopy-separator
を参照して下さい.
出力テキストログ
1画面ではなく, いままでに表示されたバッファ内容をテキストファイルに保存することもできます. C-c l
またはM-x term+start-buffer-log
でファイルを指定すると, そのファイルにバッファ内容を記録していきます. 記録をやめるにはもう一度C-c l
するか, またはM-x term+stop-buffer-log
します. ファイルへの書き込みはEmacsがアイドル状態の時または記録の終了時に行なわれます. ファイルに書き込むまでの待ち時間はterm+buffer-log-interval
で秒単位(小数可)で指定できます.
端末バッファでは本来term-buffer-maximum-size
で指定した行数を超えた分は古い行から削除されますが, この機能で出力されたファイルには古すぎて削除された分のバッファ内容も記録されています(ただしM-x term+start-buffer-log
したときに既に消えていた分に関してはこの限りではありません).
この機能は内部でtruncate(1)
コマンドを使用します. もしtruncate(1)
がインストールされていない場合は, 端末バッファの内容を(2048行を超えた分も含めて)別のバッファに保持するようになっています. メモリ効率が気になる場合はtruncate(1)
コマンドをインストールするようにして下さい.
録画
C-c r
またはM-x term+start-record
で, 指定したファイルへの端末の録画を開始します. 録画中は右上に「●REC」という録画マークが表示されます. 録画を終了するにはもう一度C-c r
するか, もしくはM-x term+stop-record
するか, あるいは録画マークをクリックします.
録画される内容は, 後述の特殊制御シーケンスを除いたあらゆる(ふつうの文字列も含む)制御シーケンスと, そのシーケンスが出力された時刻で, ttyrec
コマンドと互換性のあるデータ形式で保存されるので, ttyplay
コマンドで再生できます.
セッション管理とタブ操作
この機能にはterm+mux.elおよびその依存パッケージtab-group.elが必要です.
セッション
セッションは複数の端末バッファのタブを1つのグループにまとめたものです. ふつうはユーザ名とホスト名が共通する複数の端末バッファを1つにまとめるために使います. あるセッションに属する端末バッファを選択中にterm+mux-new
で新たな端末バッファを開くとき, セッションに関連づいたユーザ名とホスト名が使用されます.
セッションがローカルホストのroot
のものの場合, デフォルトではsudo
した端末を開きます. またセッションがリモートホストのものの場合, デフォルトではssh
した端末を開きます. sudo
やssh
への引数はterm+mux-sudo-options
およびterm+mux-ssh-options
で設定できます. ssh
の場合, ForwardX11
やControlMaster
などの設定も適宜行なわれます. これらの挙動を変えたい場合はterm+mux-ssh-*
カスタム変数を設定して下さい.
デフォルトでは同じセッション内のバッファはバッファ一覧に1つだけしか表示されません. この設定を変更するにはterm+mux-session-buffer
変数の値をnil
にします.
タブバー
タブは, ターミナルモードではモードラインに, 編集モードでは通常のモードラインの末尾に表示されます. term+mux-mode-line-tabbar
変数の値をnil
にすると, タブは常にヘッダラインに表示されます.
タブはドラッグ&ドロップで別の位置に移動できます. これはウィンドウをまたいでいても構いません. ウィンドウをまたぐ移動の場合, ある端末バッファを別のセッションのタブバーに, あるいは端末バッファではないタブバー(tab-group.elの機能で作られた通常のタブグループ)に移動することもできます.
タブ操作
タブに開いた端末バッファでは次のキーでタブを操作できます. ただし編集モードでは先頭にC-x
が必要です. プレフィックスキーはterm+mux-char-prefix
およびterm+mux-line-prefix
を設定することで変更可能です.
C-t N
- 新しい端末バッファを開く
C-t o
- 端末バッファを別ウィンドウに開く
C-t O
- 新しい端末バッファを別ウィンドウに開く
C-t c
- 新しい端末バッファを開く
C-t C
- 新しいコマンドを指定して端末バッファを開く
C-t S
- 新しいセッションを作る
C-t R
- 新しいリモートセッションを作る
C-t r
- 現在のタブの名前を変更する
C-t t
- 現在のタブの名前を変更する
C-t u
- 現在のタブの名前を元に戻す
C-t スペース
- 次のタブに移動
C-t n
- 次のタブに移動
C-t p
- 前のタブに移動
C-t s
- タブを選択 (選択中はタブ番号が表示され, タブ番号またはタブ名の一部をスペース区切りで入力することで絞り込んで選択します.)
C-t g
- タブを表示するグループをスイッチ (これは1つのタブが複数のタブグループに属している場合に, 別のグループのタブバーを表示するために使います.)
C-t N
- 現在のバッファを指定したタブグループのタブとして追加
C-t P
- 現在のバッファの(現在のグループの)タブをタブバーから取り除く
C-t l
- 現在のグループに属するタブ一覧を表示
C-t ←
- タブバーを左にスクロール
C-t →
- タブバーを右にスクロール
C-t home
- タブバーを左いっぱいにスクロール
C-t end
- タブバーを右いっぱいにスクロール
C-t <
- タブを左に移動
C-t >
- タブを右に移動
C-t [
- タブを左端に移動
C-t ]
- タブを右端に移動
シェル連携
term+.elではシェル連携のための機能を提供しています. シェル側の設定例(zsh用)はhttps://github.com/tarao/dotfiles/blob/master/.zsh/eterm.zshにあります.
実際にはシェルだけでなく端末内で動くすべてのアプリケーションがこの機能を利用できます. シェル連携機能を利用するには, 端末内のアプリケーションは端末(/dev/tty
)に対して特殊な制御シーケンスを送ります. 特殊な制御シーケンスに関連づけられたコマンドは端末内アプリケーションからの要求に従った動作をして, 場合によっては端末内アプリケーションへ応答を返します.
定義済みの特殊制御シーケンスについて調べる
M-x describe-function
, term-emulate-terminal
に詳しい説明があります. またM-x term+control-command-list
で現在定義されている特殊制御シーケンスの一覧が表示されます.
編集モード
編集モードに関する特殊制御シーケンスには以下のものがあります.
- "
\e]51;mode;mode-name\e\\
" - 次に編集モードに入ったときに入力フィールド内で有効にするメジャーモードをmode-nameに指定
- "
\e]52;i;text\e\\
" - textが入力フィールド内に入った状態で編集モードに移行
- "
\e]52;n;text\e\\
" - textが入力フィールド内に入った状態で
evil-normal-state
の編集モードに移行 (Evil利用時のみ可, term+evil.elが必要)
たとえばM-i
でsh-mode
が有効になった編集モードに入るようにするzshの設定は次のようになります.
function switch-to-line-mode-insert () { local buf="$BUFFER" zle kill-buffer zle -R echo -ne "\e]51;mode;sh-mode\e\\" > /dev/tty echo -ne "\e]52;i;$buf\e\\" > /dev/tty } zle -N switch-to-line-mode-insert bindkey '^[i' switch-to-line-mode-insert
右プロンプトにも対応した詳細なバージョンは設定例を参照して下さい.
ユーザ名, ホスト名, カレンドディレクトリ
ユーザ名, ホスト名およびカレントディレクトリを端末へ通知すると, 端末バッファのdefault-directory
の値を端末内のアプリケーションのものに設定できます. こうしておくと, TRAMPでリモートファイルを開く場合や, 他のシェル連携機能を利用する場合に便利です.
以下の制御シーケンスでこれらの情報を通知できます.
- "
\e]51;host;host-name\e\\
" - ホスト名をhost-nameに設定
- "
\e]51;user;user-name\e\\
" - ユーザ名をuser-nameに設定
- "
\e]51;cd;path\e\\
" - カレントディレクトリをpathに設定
zshでの設定例は以下のようになります.
host=`hostname` echo -ne "\e]51;host;$host\e\\" > /dev/tty user=`id -run` echo -ne "\e]51;user;$user\e\\" > /dev/tty function precmd_eterm_cwd () { local dir; dir=`pwd` echo -ne "\e]51;cd;$dir\e\\" > /dev/tty } typeset -Uga precmd_functions # これはどこかで一度だけしておく precmd_functions+=precmd_eterm_cwd
ファイル転送
指定したファイルをEmacsで開く, あるいはコピーするために, 以下の制御シーケンスが利用できます.
- "
\e]51;open;files\e\\
" - filesをEmacsで開く
- "
\e]51;view;files\e\\
" - filesをEmacsで
view-mode
で開く - "
\e]51;get;files\e\\
" - filesをミニバッファで指定した場所にコピー
- "
\e]51;put;\e\\
" - ミニバッファで指定したファイルを
default-directory
にコピー - "
\e]51;put;m\e\\
" dired
で選択した複数のファイルをdefault-directory
にコピー
filesは;
で区切られたファイル名です. ユーザ名, ホスト名およびカレントディレクトリが正しく通知されていれば(default-directory
が正しく更新されるため)リモートホストのファイルであっても転送できます.
open
やview
で別ウィンドウで開くようにするにはterm+open-in-other-window
変数の値をt
に設定します.
view-mode
で開いた場合, 通常のview-mode
と違い, q
で(正確にはView-quit
コマンドで)バッファを閉じます.
get
/put
の向きの覚え方は, FTPの場合と同じです.
シェルで利用する方法は設定例を参照して下さい.
履歴選択
Emacsのインタフェースでシェルのコマンド履歴を参照してコマンドを選択・端末に送信することができます. ただし, あらかじめシェルの履歴ファイルを指定しておく必要があります.
- "
\e]51;histfile;path\e\\
" - 履歴ファイルをpathに設定
- "
\e]52;h;text\e\\
" - 絞り込みのための初期値をtextに設定した上で履歴選択を開始
リモートホストの端末で正しく動作させるためには, ユーザ名, ホスト名が正しく設定されている必要があります. histfile
で指定されたファイルはEmacsのバッファとして開くため, リモートホストの場合には動作速度に問題がある場合もあります.
anything-complete.elとterm+anything-shell-history.elを利用している場合は履歴の選択がzshの履歴検索にanything.elを使う(ターミナル版)に似た, anythingを用いたインタフェースになります.
たとえばC-r
で履歴選択できるようにするためのzshでの設定例は以下のようになります.
echo -ne "\e]51;histfile;$HISTFILE\e\\" > /dev/tty function history-search-eterm () { local buf="$BUFFER" zle kill-buffer echo -ne "\e]52;h;$buf\\e\\" > /dev/tty } zle -N history-search-eterm bindkey '^R' history-search-eterm
マルチプレクサ
マルチプレクサに関する特殊制御シーケンスとしては以下のものが利用可能です(term+mux.elが必要).
- "
\ekstr\e\\
" - タブタイトルをstrに変更 (
screen
形式) - "
\e[2;str\e\\
" - タブタイトルをstrに変更 (
tmux
形式) - "
\e]51;cdd;param\e\\
" - 他のタブのカレントディレクトリを問い合わせる
最初の2つはタブのタイトルを変更するためのもので, それぞれscreen
, tmux
のものと同じ制御シーケンスです. たとえばscreen
を用いている場合に, カレントディレクトリもしくは現在実行中のコマンドに応じてタブのタイトルを自動設定するスクリプト例https://github.com/tarao/dotfiles/blob/master/.zsh/screen-title.zshに加えて, 以下の設定をしておくと, term+mux.elでも同様にタブ名を自動設定できます.
typeset -a eterm_options eterm_options=(${(s:,:)INSIDE_EMACS}) function eterm_has () { [[ -n "$eterm_options[(r)$1]" ]] && return return 1 } eterm_has mux && { whence precmd_screen_window_title >/dev/null && { precmd_functions+=precmd_screen_window_title } whence preexec_screen_window_title >/dev/null && { preexec_functions+=preexec_screen_window_title } SCREEN_TITLE=auto }
3つ目は, 他のタブのカレントディレクトリに移動するためのGNU screen用コマンドcdd
(id:secondlifeさん作)のterm+mux.el版を実装するために必要な制御シーケンスです. 実際の実装は設定例を参照して下さい. term+mux.el版のcdd
を引数なしで実行した場合, Emacsのタブ選択UIで他のタブを選択できます.
特殊制御シーケンスを追加する
特殊制御シーケンスは, 開始符号と終了符号の間にはさまれた文字列を, 関連づけられたコマンドの第1引数に渡して実行します. 制御シーケンスとコマンドを関連づけるにはterm+new-control-command
関数に開始符号, 終了符号およびコマンド(のシンボル)を渡します.
たとえばファイルを開くための特殊制御シーケンスは次のように定義されています.
(defun term+open (files &optional find-file) ...) (term+new-control-command "\033]51;open;" "\033\\" 'term+open)