導入

Emacs-27.1で、 tab-bar-modetab-line-mode が追加された。

tab-line-mode では、Figure 1にあるように、 ウィンドウ1 上部(ヘッダラインという)にバッファ一覧が タブとして表示される。バッファ切り替えを行うときにそれをクリックすることで バッファを切り替えられる。 実装を見ると、どうやら window-next-bufferswindow-prev-buffers を用いて 表示するバッファを決めているようなので、さしあたりでは C-x b (switch-bufer) の一部代替、及び next/previous-buffer の代替が目的だろう。 バッファリストが表示されるという点では C-x C-b (list-buffer)や ibuffer の 代替とも捉えられるが、横幅に限りがあることと、そもそも window-next/prev-buffer では全バッファを網羅できないことを踏まえると、完全に置き換えるには貧弱だろう。 tab-line-mode に関しては、バッファ切り替えを文字以外で行うのに 慣れることができそうもないので、ここまでにしておく。

Figure 1: global-tab-line-mode をオンにしたEmacs。各ウィンドウにタブ欄がある。

Figure 1: global-tab-line-mode をオンにしたEmacs。各ウィンドウにタブ欄がある。

私が今回説明したいのは、 tab-bar-mode である。こちらは先程とは違い、 1つのフレーム1につき1つ 、一番上にタブ欄が表示される。 このタブが切り替えるのはウィンドウ構成、つまりどの位置になんのバッファを表示した ウィンドウがあるかの情報、である。これはとても有用だ。 単純に表示領域が足りないからというのもあるが、やはり大きいのは、 突然別のことをやりたくなったときに今の状態を保持してさっと移り、 終わったら戻ってこれることだ。だれしも一度は経験した、「さっきなに見てたっけ」 「さっきまでなにがしたかったんだっけ」というのが格段に減ることだろう。 現実世界にもはやく実装されてほしい。今まで机の上を保存したいと何度思ったことか。

Figure 2: tab-bar-mode をオンにしたEmacs。ウィンドウは複数あるがタブ欄は1つだけだ。

Figure 2: tab-bar-mode をオンにしたEmacs。ウィンドウは複数あるがタブ欄は1つだけだ。

elscreen との比較

同じことをしてくれるパッケージには、 elscreen がある。こちらも同様にウィンドウ構成を切り替えられ、今まで利用してきたが、 いくつか欠点がある。

一つは、タブの表示が貧弱なことだ。 基本的にヘッダラインに表示しているので、(同じ内容なのに)全てのウィンドウで表示されたり、 左右に分割されているウィンドウでは半分しか見えなかったりといった感じで、 痒いところに手が届かない。やはりタイトルバーのようにフレーム一杯に広がっている領域が1つだけ欲しい。 そういうわけで過去にnavbar.elというものの制作(があるので正確には改修)に少しだけ携わった ことがある(今もメンテナではあります)が、いかんせん古いコードだったので、かなりバギーで、 動作が怪しい。それに比べると、 tab-bar-mode はかなり優秀だと思う。 先程述べたとおり、 tab-bar-mode では1フレームにつき一つ、タブ一覧がフレーム一杯に広がっている。 それでいて、GNU Emacsに取り込まれていることからある程度の動作は保証されているからだ。

もう一つは、前段落とやや被るが、 elscreen は外部パッケージであることだ。 あくまでもGNU Emacsの外の存在である。ウィンドウ構成周りは元々複雑でバグが入りやすいため、 ウィンドウ構成の管理が外部パッケージだと、どうしても怪しい動作が目立つ。 その点 tab-bar-mode はGNU Emacsに取り込まれているので、安心できる。 もちろんバグが絶対にないわけではないが、本家に入っているなら安定性は重視されているだろう。

ただし、もちろん利点もある。 elscreen はかなりの古参なので、拡張がたくさんある。 elscreen-persist なんかは(MELPAからは消えているとはいえ)その筆頭だろう。 tab-bar-mode にそのような類のものはまだないので、 永続化したい人は乗り換えを待ったほうがいいかもしれない。

基本的な使い方とキーバインド

まず、 M-x tab-bar-modetab-bar-mode をオンにすることで上部にタブが出てくる 2。 気に入ったら init.el に以下のように書けばよい。

(tab-bar-mode +1)

見た目については、アクティブなタブは tab-bar-tab 、 非アクティブなタブは tab-bar-tab-inactive のfaceを変更すれば変えられる。 といってもどちらも tab-bar にinheritしているので、 tab-bar のほうのfaceを変えるのがお手軽かもしれない。

と、ここまではただの見た目だ。これはとりあえず見た目を他と合わせたい自分用。 とりあえず、先にキーバインドを示しておく。prefixは C-x ttab-prefix-map に登録されているので、もしこれを変えたければ適宜 define-key すればよい。

Table 1: tab-bar 系のキーバインド(C-x t は省略)
key function summary
2 tab-new タブの新規作成
1 tab-close-other 現在のタブ以外全て削除
0 tab-close 現在のタブの削除
o tab-next 次のタブへ移動
m tab-move 現在のタブの位置を移動
r tab-rename タブに名前を付ける
RET tab-bar-select-tab-by-name 名前でタブを選択
b switch-to-buffer-other-tab バッファを新しいタブで開く
C-f, f find-file-other-tab ファイルを新しいタブで開く

各コマンドの詳細はこれから書くが、基本は C-x t 2 でさくっとタブを作り、 おわったら C-x t 0 で消す運用だろう。名前はお好みで C-x t r を使って付ける。 一斉に消したければ C-x t 1 。これくらい覚えていれば使えると思う。

各キーの説明

2 (tab-new)、 1 (tab-close-other)、 0 (tab-close)、 o (tab-next)に関しては、 バッファやウィンドウを操作する C-x 系の split-window-below (C-x 2)、 delete-other-windows (C-x 1)、 delete-window (C-x 0)、 other-window (C-x o)に倣っているようだ。 たしかに、間に t を挟むだけだし、手に馴染んでいるのでわかりやすい。 3ストロークはちと多い気もするが、連発するものでもないのでそこまで問題なさそうだ。

m (tab-move)はすこしわかりづらいかもしれないが、要は表示されたタブの並び換えに使う ものだ。現在のタブを一つ右に移動させる。並べ替えにどれほどの需要があるかは 不明だが、複数の関連するウィンドウ構成をまとめておきたいときには有用だろう。

r (tab-rename)はタブに名前を付けるものだ。renameの名が冠されているが、 デフォルトではタブには名前がなく、カレントバッファの名前が動的に表示される (こちらで変更可能)ので、 名前を最初につけるときにも使うことになる。フランクに使うなら わざわざ名前はつけなくてもいいかもしれない。

RET (tab-bar-select-tab-by-name)は名前を入力してタブを選択する形だ。 名前がついてない場合はカレントバッファが候補の名前となる。 completing-read を直で呼んでいるので、 idoivy を使いたければ 他と同様に completing-read-functionido-completing-readivy-completing-read とすればよいだろう。

b (switch-to-buffer-other-tab)、 fC-f (find-file-other-tab)も C-x 系に倣っている。 それぞれ選択したバッファ、ファイルを新しいタブで開くもので、丁度 C-x b (switch-to-buffer)と C-x C-f (find-file)に対応している。

tab-bar-history-mode

また、 tab-bar-history-mode というものもある。これは各タブでウィンドウ構成の履歴を 辿れるようにするものだ。よほど重たいのでなければ、オンにしない手はない。

(tab-bar-history-mode +1)

利用できる関数は2つだけ。 tab-bar-history-backtab-bar-history-forward だ。 それぞれがウィンドウ構成履歴を戻る、進む役割をもつ。 また、タブ一覧の一番左側にでてくる2つの矢印にも同じものが割り当てられている。

Custom variables

この節ではカスタマイズ変数の説明をする。括弧内は初期値。 あまり重要でなさそうなのは載せてないので、各自で調べてね☆

tab-bar-show (t)

先述のようなタブ操作系のキーバインドを使ったときに、自動で tab-bar-mode を オンにして、タブを表示するかどうか。 t なら常にそうする。 nil なら常にそうしない。 1 なら2つ以上のタブがあるときだけ表示する。

tab-bar-new-tab-choice (t)

新規タブがどの状態で始まるかどうか。 t ならカレントバッファ1つ。 nil なら現在のタブを複製。 文字列ならその名前のバッファ、なければファイルやディレクトリを探して開く。 関数ならその返り値のバッファを開く。個人的には動的に決められるようになっているのが とても好み。よき。

tab-bar-new-button-show (t)、 tab-bar-close-button-show (t)

新規タブボタン、タブ削除ボタンを表示するかどうか。~t~ なら表示、 nil なら 非表示。 tab-bar-close-button-show の場合、 さらに selected (カレントタブでのみ表示)と non-selected (カレントタブ以外で表示)の二つも可能。

tab-bar-tab-hints (nil)

タブに連番を表示するかどうか。

tab-bar-tab-name-function (tab-bar-tab-name-current)

名前がないとき、名前をどう決めるか。デフォルトではカレントバッファ名 を返す tab-bar-tab-name-current (Figure 3)となっているが、 任意の関数に変更可能。 windowの数を数えて付け加えてくれる tab-bar-tab-name-current-with-count (Figure 4)、 長さが tab-bar-tab-name-truncated-max を超えると省略してくれる tab-bar-tab-name-truncated (Figure 5)、 表示されている全バッファの名前をカンマで繋げてくれる tab-bar-tab-name-all (Figure 6)が用意されている。 自身で作った関数でももちろんOK。

Figure 3: tab-bar-tab-name-current のとき(デフォルト)

Figure 3: tab-bar-tab-name-current のとき(デフォルト)

Figure 4: tab-bar-tab-name-current-with-count のとき

Figure 4: tab-bar-tab-name-current-with-count のとき

Figure 5: tab-bar-tab-name-truncated のとき

Figure 5: tab-bar-tab-name-truncated のとき

Figure 6: tab-bar-tab-name-all のとき

Figure 6: tab-bar-tab-name-all のとき

tab-bar-new-tab-to (right)

新しいタブをどこに作るか。 leftright なら現在のタブの左右に、 leftmostrightmost なら一番左や一番右に作る。

tab-bar-close-tab-select (recent)

現在のタブを閉じたとき、どのタブに移動するか。 recent なら最近利用したタブに、 leftright なら元々あったタブの左右の タブに移動する。

tab-bar-close-last-tab-choice (nil)

最後の1つのタブを閉じたときにどうするか。 nil ならなにもしない。 delete-frame なら現在のフレームを削除。 tab-bar-mode-disable なら tab-bar-mode をオフにする。 また、関数を渡すと閉じたときに関数を実行してくれる。

改造

せっかく1フレーム1つの領域を手に入れたので、自由に表示したい。 日付や時計、場合によってはbranchなんかも、1フレーム1つで十分だろう。

というわけで、しくみをざっくりと見て、真似してみようではないか。 と思って見てみると、なんだかおもしろい記述が。

(global-set-key [tab-bar]
                `(menu-item ,(purecopy "tab bar") ignore
                            :filter tab-bar-make-keymap))
Code 1: tab-bar-mode.el L197-199

どうやら [tab-bar] にメニューアイテムを割り当てると画面上部に表示できるらしい。 おもしろい試みだ。 navbar.el ではバッファを表示させていたのに対し、 専用の領域を用意したわけか。(試しに [tab-bar2] に同じものを割り当ててみたが、 なにも表示されなかったので、おそらく新しく専用の領域を作ったのだろう。)

恥ずかしながら menu-item 形式の global-set-key は初めて見たので、 define-key のヘルプをみてみると、InfoファイルのExtended Menu Itemsの項に 説明があるらしい。(日本語版は(24.5のものだが)ここにある。ほとんどかわっていないので 問題ない。)これを読むと、どうやら :filter に渡された関数 tab-bar-make-keymap は、 3番目の ignore というシンボルを受けとり(tab-bar では使ってないので適当なシンボル をおいているっぽい)、キーマップを返すものらしい。本来は ignore の位置に キーマップを直接置くが、動的に生成したいときは :filter を使う、という感じのようだ。

tab-bar-make-keymap は内部的には tab-bar-make-keymap-1 を呼んでいるだけだが、 この関数は通常我々が使うようなキーマップとは毛色が異なり、 cdr の各要素のほとんどは (symbol menu-item string function) となっている。 symbol は識別子で、 string が表示される文字列だ。クリックすることで function が呼び出される。もし単に文字を表示したければ、 tab-bar の セパレータの表示に倣って functionignore にすればよいだろう。

さあこれで準備は整った。要は tab-bar-make-keymap-1 をハックして、 さっきの形式で文字列をいれてやればいいのだろう。

というわけで tab-bar-display というパッケージを作った。

使いかたは簡単で、 tab-bar-display-beforetab-bar-display-afterformat-mode-line 形式で表示したいものを書いて、 tab-bar-display-modetab-bar-mode と共にオンにしておけば、タブ一覧の前後にそれが表示される。

Figure 7: tab-bar-display-mode を用いた例

Figure 7: tab-bar-display-mode を用いた例

まとめ

tab-bar-mode はとても便利というはなしでした。 かなり便利そうなので elcreen を捨てて乗り換えようかしら。


  1. Emacsにおいては、一般に言うウィンドウをフレームと言い、フレームを分割したものをウィンドウと言う。 ↩︎ ↩︎

  2. なお、この操作は必須ではない。名前とは矛盾するが、 tab-bar-mode がオフでも tab 系の関数を使うことができる。 ↩︎