広くなった画面を有効利用できる、Emacs内Window管理ツール e2wm.el を作ってみた。(旧名称 ewm.el)

世間が iPad で盛り上がっている中、空気を読まずにEmacsです。

(2010/05/31 追記:バグがあって動かない状態でしたので、修正いたしました。試してみて動かなかった皆さんご迷惑をおかけしました。peccuさんご指摘ありがとうございました。)

(2010/06/07 名称変更:名称を変更ましたので、混乱しないように内容をすこし修正しました。既に導入いただいた皆さんすみません。)

概要

Window分割をあらかじめ決めておいて一発で切り替えたり、ポップアップの出現位置を固定したり、ちょっと便利になるプラグインを追加できるようにする、e2wm.elというEmacsの拡張を作ってみました。Emacs以外の人に分かりやすい表現で言うと、Eclipseの「パースペクティブ」のようなものを実現するものです。


画面概観

近年モニターの解像度が増加してきたことにより、世の中のEmacsの使い方が若干変わってきたように感じます。自分はX201s(1440x900)を使い始めてから、広い画面を有効に使うために頻繁にウインドウ分割を行って多くの情報を表示したいと考えるようになってきました。Emacsは自動でウインドウの分割を制御してくれますが、すこし複雑な分割を行っていると思った通りにバッファが表示されません。そこで、ウインドウやバッファ表示の制御をユーザーが調整できるようにして、あらかじめユーザーの望んだとおりにバッファが表示されるようにできると便利そうだと考えました。

e2wm.elはEmacsのウインドウ管理を乗っ取り、ポップアップウインドウの出現場所や、ウインドウの分割などをあらかじめ設定しておいた方法で表示されるように管理します。また、ウインドウの管理のついでに、他のIDEを参考にしていくつか作業上便利な付加機能をつけられるようにしてみました。

ちょっと見るとEclipseのような画面になりますが、Emacs初心者向けのIDEを実現するのではなく、主にEmacsのヘビーなユーザーが自分好みのWindow配置を実現することを目的としています。もちろん、Emacs初心者が使いやすくなるような改善は行っていきたいと思います。

この拡張のように、EmacsのWindow管理について改善を試みている人は他にも見つけることができます。おそらくEmacsのWindow管理や高解像度モニタへの対応の方向は、今後重要になっていくのではないかと思っています。もしよろしければご意見をいただけるとうれしいです。

ウインドウ管理とパースペクティブ

Emacsをよく使っている人は、Window分割をそれなりに使っていると思いますが、大抵は決まった分割にすることが多いと思います。そこで、作業シーンによって分割表示方法を設定しておき、一発で切り替えることができるようにしてみました。この分割表示方法をEclipseにならってパースペクティブと呼んでいます。現在は以下のものを用意しています。

  • code: 中央に1つのコードを表示してがっつり読み書きする
  • two: 左右2分割してコードの比較や参照をする
    • htwo twoの上下2分割版
  • doc: 長いコードやドキュメントをfollow-modeで読む
  • dashboard: たまに見たい、使いたいバッファを集めて置いておく
  • array: 開いているバッファを全部表示して全体を眺めたり、視覚的に探したりする

パースペクティブはユーザー側で自由に増やしたりレイアウトを変更したりできます。自分の好みにカスタマイズすることで、自分でウインドウを分割してバッファを表示し直すような作業がほとんど必要無くなります。

バッファ履歴と表示管理

e2wm.elではバッファを主に3種類に分けています。

  • 編集対象 → エディタの目的。履歴管理をする。
  • ドキュメント → ちょっと見たり、並べて見たり、がっつり読みたい。
  • その他 → 作業のじゃまにならないように表示したい。

主にこれらの分類に従って、パースペクティブごとに、どこのウインドウにどのバッファを表示するかを細かく制御できるようになっています。

また、編集対象のバッファについては専用に表示の履歴を管理していますので、行ったり来たりする編集が楽になると思います。

プラグインによる拡張

Eclipseの「ビュー」のような、機能を持ったウインドウを増やすことができます。
e2wm.elではプラグインと呼んでいます。現在のところ以下のようなプラグインを用意しています。

  • 編集中バッファのディレクトリ内のファイル一覧を表示
  • バッファ履歴一覧を表示
  • Imenuでアウトラインの表示・移動、現在地の表示
  • 時計やtopの表示

プラグインも自由に増やしたり設定することができます。

インストール

必要なものは Emacs22 以上です。開発はLinux上のEmacs23.1を主に使っていますが、Mac(CarbonEmacs), Linux(Emacs23.1, 23.2), Windows(NTEmacs22,23.1) 上の各Emacsで簡単ながら動作確認しています。まじめに試してはいませんが、 -nw でもほぼ大丈夫です。一部、Emacs23以上の機能を使ったり、いくつかのプラグインでtopコマンドやwget,ImageMagickなどを使っています。

取ってくるもの

まず、e2wm.el, window-layout.el を load-path に置きます。

auto-install.elを持っている人は、以下の式を評価することで取って来れます。
(2010/05/31 追記:window-layout.elを先に取ってきてください。)

(auto-install-from-url "http://github.com/kiwanami/emacs-window-layout/raw/master/window-layout.el")
(auto-install-from-url "http://github.com/kiwanami/emacs-window-manager/raw/master/e2wm.el")

手動で入れる場合は、以下のリンク先をload-pathに保存してください。

設定

あとは、以下のような呼び出しの内容を .emacs などに書きます。(設定のひな形は後ほどカスタマイズのところで紹介します。)

;最小の e2wm 設定例
(require 'e2wm)
(global-set-key (kbd "M-+") 'e2wm:start-management)

この例では、Altキーを押しながら「+」を押すとウインドウの管理を開始します。終了する場合は「C-c ; Q」です。

初期画面

e2wm.elによってウインドウの管理を開始すると以下のような画面になります。既に開いているバッファがあれば履歴一覧に追加されます。

パースペクティブプラグインについて簡単に機能を紹介します。

※注意点

バッファ切り替えやウインドウ周りの関数をほとんど乗っ取っていますので、お使いの環境と相性が悪いことがあるかもしれません。本気バッファで使う前に、作業に支障が出ないかどうかご確認をお願いします。

パースペクティブの紹介

code

メインになる画面です。ひとつのファイルを読んだり編集することを目的にしています。

メインのウインドウに表示されているコードに対して、filesプラグインとimenuプラグインが連動します。

また、現在開いているバッファの履歴が左下に表示されていますので、戻ったり進んだりすると何が表示されるのかがすぐにわかるようになっています。

anythingや補完、helpなどのポップアップは、固定で下の方に出てくるようになっています。常に決まったところに出るため、縦長いウインドウやメインのウインドウに出てきてイラッとすることが無くなり、作業に集中できます。

画面が狭かったり、コードを広く表示したい場合は、「C-c ; M」でメインウインドウの最大化をトグルできます。imenuやsubもトグルできますので、よく使う場合は使いやすいキーに割り当てておくと便利だと思います。

細かい機能ですが、「C-c ; C」で時計と履歴を切り替えられるようになっています。

two (htwo)

コードを並べて見たり編集することを目的にしている画面です。同一ファイルの2カ所(例えば定義と実装)を表示させたり、リファクタリングでコードを整理するとき、また定義・タグジャンプなどでコードを追っていくときに便利です。

左のウインドウがメインのウインドウで、履歴のトップを表示します。隣のウインドウは、ひとつ前にメインに表示していたバッファを表示します。この動きにより、twoパースペクティブを切り替えた時点で、比較したいバッファが既に並んでいる場合が多いです。さらに必要があれば履歴やfind-fileで連続で2つのバッファを開くと左右に並ぶため、いちいち並べたいウインドウに移動してバッファを表示する動作が必要がありません。同一バッファを並べたい場合は、「C-c ; d」でメインのバッファが並びます。

また、ドキュメント系(Info, Help, w3m, WoMan)のバッファを表示(switch-to-buffer, pop-to-bufferなど)すると、右側のウインドウに優先的に表示します。「codeからtwoに移動して、anythingでinfoを検索して、ドキュメントを参照しながらコードを書く」といった動きもスムーズにできます。

基本的にcodeとtwoの往復がコーディング作業の中心になると思います。メインのバッファはパースペクティブを切り替えても保持されるため、codeパースペクティブから少ないアクションでtwoに移動して別のコードを参照し、またすぐにcodeに戻って1つに集中するといった、良くある動きをスムーズに行うことができるようになります。

左右分割ではなくて上下分割を行う htwo というパースペクティブがあります。画面横幅が狭い場合はこちらの方が便利かもしれません。

doc

長いバッファ(特にドキュメント)をがっつり表示することを目的としたパースペクティブです。左右2分割ですが、表示するバッファを自動的に follow-mode (2段組のように左のウインドウと右のウインドウが続いている)にしてしまうので、広い画面を有効に活用しながら本のようにじっくり読むことができます。

左右に内容が流れるのはちょっと不思議な気分ですが、慣れると広い範囲を一度に見渡せるため非常に便利に使えます。

このパースペクティブはドキュメント系のバッファを特別に扱い、ほかのパースペクティブに移動しても、以前表示していたバッファを保持するようになっています。この動きにより、codeとdocを往復してドキュメントを読みながらコード編集を行うという動作がスムーズにできます。

もちろん、ドキュメントだけでなく長いコードを見渡すときにも便利です。

array

開いているバッファを一度に見渡すパースペクティブです。MacのExposeのようなイメージで、全体のバッファを見渡しながら、全体の内容を眺めたり、バッファのサマリーを確認したりします。

パースペクティブと言うよりは、バッファ選択のデモンストレーション的なものになっています。実用的ではあまりありませんが、おそらくEmacs以外のユーザーを威嚇するのに有効ではないかと思います。

なお、このパースペクティブはバッファ選択以外の操作を止めるためにglobal-mapを一時的に取り替えています。

dashboard

編集中のコードとは関係ないけども、ちょっと見たり使ったりしたいバッファを置いておくパースペクティブです。

画面下にはEmacsのメモリ情報(garbage-collectionの値など)が表示されます。今まで気にしたことがなかったのですが、メモリやシンボルの使用状況が気になる人にとっては便利かもしれません。

デフォルトではeshellとdoctorなどを起動していますが、プラグインであれば何でも置けますので、topや時計などのほかに、シェルやtwitterクライアントやircクライアントなどを表示するなどの応用ができます。シンプルにメモ用のバッファやスクラッチバッファを置くだけというのも便利だと思います。

このパースペクティブは単純にプラグインを列挙して並べます。レイアウトなどを細かく調整したい場合は、新しくパースペクティブを作ってレイアウトを作る方がいいと思います。

プラグインの紹介

Eclipseの「ビュー」のように、適当な位置に表示できる小さなプログラムです。

files (dired)

ディレクトリ内のファイルの一覧を表示します。表示するディレクトリの場所は、メインのコード(履歴の先頭)が保存されているディレクトリです。今のところ、Diredに比べてごく基本的な機能しかありませんが、ソートの仕方によってちょっと見た目が変わります。

↓時間でソートしたところ

  • 主なキーバインド
    • j,k : 上下移動
    • D : diredで開く
    • + : ディレクトリ追加
    • ^ : 上の階層へ
    • g : 表示更新
    • d : ファイル削除
    • r : ファイルリネーム
    • t : 時間でソート
    • s : 名前でソート
    • z : サイズでソート
    • space, enter : ファイルを開く

もちろん、jk以外にも普通のEmacsの移動のキーが使えます。また、他のプラグインでも、基本的に「スペース」での選択はウインドウのフォーカスが変わらず、「エンター」での選択はメインのウインドウにフォーカスを移動します。スペースで次々にチラ見していって、エンターで目的の場所に移動という感じで使う感じです。

Diredで表示する dired プラグインもあります。こちらは完全に Dired ですので使い慣れている人にとってはこちらの方が便利かもしれません。

history-list

バッファの一覧や現在表示中のバッファの位置を表示します。バッファを行ったり来たりするときに、これがあると何回キーを押せばいいのかすぐにわかって便利です。


  • 主なキーバインド
    • j,k : 上下移動
    • d : バッファ閉じる(kill-buffer)
    • space, enter : バッファの表示
history-nth main-prev

履歴の指定した場所を表示します。 history-nth は、メインで表示している履歴のひとつ前、 main-prev はさっきまでメインに表示していたバッファを表示します。twoパースペクティブの右側は main-prev です。

history-nthとmain-prevの違い

なお、バッファの履歴一覧は、「戻る」「進む」では一覧の順番を変更しません。履歴一覧から選んだり、バッファを手動で切り替えたりすると一覧を最近表示した時間順で並び替えます。単純にバッファを行ったり来たりするだけではバッファの一覧が変わらないため、「戻る」でどこのバッファに戻るのかがわかりにくくならないのではないかと思います。実際にいろいろ試してみてこの方法に落ち着いたのですが、もしもっといいアイデアがあれば教えてください。

imenu

メインバッファのアウトラインを、imenuの仕組みで一覧を取得して表示します。また可能であればメインのウインドウと連動して、which-funcのように現在地を表示します。


  • 主なキーバインド
    • j,k : 上下移動
    • u,e : 上にページ単位で移動
    • d,v : 下にページ単位で移動
    • space, enter : バッファの表示

アウトラインはEclipseの時はそれほど重宝していませんでしたが、他人のコードの大まかな構造や規模(関数や変数の数など)を把握したりするのにちょっと便利なことに気がつきました。

また、キーボードで使いやすく設定しておけば、ちょっと離れた場所に移動するときに便利になります。

imenuは簡単ながら非常に多くのモードが対応していますので、プログラム以外でも便利に使えます。以下の画面はWebDBの記事を書く用の inao-mode での画面です。見出しやコードの目印を頼りに、全体の流れを確認したり、素早く目的の場所に移動したりできます。

しかしながら、デフォルトのImenuではやっぱり解析が難しいコードも数多くあります。semantic-imenuやnavi.elなど、他にも便利なアウトラインツールがありますので、今後対応していきたいと思っています。

top

topコマンドの出力をそのまま表示します。dashboard向きのプラグインです。
タイマーで自律的に内容を更新するプラグインのサンプルのような意味合いもあります。

clock

時計を表示します。wgetImageMagickがあれば、ネットワークから指定のURLで画像を取ってきてリサイズして表示することもできますので、美人や美男な時計を表示することもできます。これで女子にもEmacsを安心してお勧めできますね。


open

特定のバッファがあるかどうかを調べて、無ければ指定のコマンドを実行してバッファを表示するプラグインです。twitterircdashboardなどで開いておくときに使います。

その他機能

メニューとモードライン

やっぱりコマンドだけだと忘れてしまうこともあるので、簡単なメニューを用意しました。

現在表示されているパースペクティブと、切り替え可能なパースペクティブが表示されます。また、選択されているウインドウのプラグインを調べたり、一時的に切り替えたりすることもできます。

モードラインでは、現在のパースペクティブ名を表示します。

「E2wm」はグローバルなマイナーモードとして動いています。詳細については設計や実装で説明する予定です。

パースペクティブセット

パースペクティブは言語ごとにカスタマイズして増えていくような仕組みになっています。大量に増えていくと切り替えが大変ですので、簡単ながらパースペクティブのセットの仕組みを入れてみました。

e2wm:pstset-next-pst-command, e2wm:pstset-prev-pst-command で現在のセットのなかで、パースペクティブを切り替えることができます。

今はこれだけですが、もう少しパースペクティブが増えてきてからまじめに考えたいと思います。

簡単なカスタマイズ

ここでは簡単なカスタマイズ方法について書きます。パースペクティブプラグインの作成方法については、後ほど書く予定です。

ウインドウレイアウト調整

デフォルトの配置は自分の好みになっていますので、もし何か気になったところがあれば各パースペクティブのレイアウトやウインドウの内容について調整した方がいいと思います。デフォルトは作者の 1440x900 であわせていますので、WXGAの場合は両サイドの幅を少し狭めた方がいいかもしれません。また、XGAの場合には両サイドはデフォルトで隠していた方がいいかもしれません。

簡単なレイアウトの修正には、直接以下の変数を変更すると早いです。

  • code
    • e2wm:c-code-recipe
    • e2wm:c-code-winfo
  • two
    • e2wm:c-two-recipe
    • e2wm:c-two-winfo
    • (e2wm:c-htwo-winfo)
  • doc
    • e2wm:c-doc-recipe
    • e2wm:c-doc-winfo

xxx-recipeとxxx-winfoがセットになっています。arrayやdashboardは内部で自動的に計算してレイアウトを決定していますので、レイアウトのカスタマイズはあまりできません。

xxx-recipeは、レイアウトを定義しています。

codeでは以下のような内容になっています。

  '(| (:left-max-size 35)
      (- (:upper-size-ratio 0.7)
         files history)
      (- (:upper-size-ratio 0.7)
         (| (:right-max-size 30)
            main imenu)
         sub)))

ちょっと込み入っていますが、

(-もしくは| (分割幅) (左・上の定義) (右・下の定義) )

という風になっていて、この記述を入れ子にしていくことで全体のレイアウトを指定します。
「|」だと縦に分割、「-」だと横に分割します。:upper-size-rario, :right-size などで割合や行数・桁数でサイズを指定できます。この記述方法の詳細は window-layout.elのコメント に詳しく書いてあります(書いたつもり)。

files,historyなどはウインドウの名前で、xxx-winfoの方で何を表示するかを決めるときに使います。

例えば、codeの場合は以下のような構造になっています。

xxx-winfoは、分割した各ウインドウの中に何を表示するかを定義しています。

codeでは以下のような内容になっています。

  '((:name main)
    (:name files :plugin files)
    (:name history :plugin history-list)
    (:name sub :buffer "*info*" :default-hide t)
    (:name imenu :plugin imenu :default-hide nil))

各ウインドウがひとつのプロパティリストになっていて、プロパティ名に続いて値が並んでいます。

  • 「:name」 はウインドウ名を定義しています。上でレイアウトの時に入れた名前と対応しています。
  • 「:buffer」 はもしそのバッファがあればそのバッファを表示しますが、パースペクティブで動的にバッファを表示しますので、e2wm.elではあまり意味はありません。
  • 「:plugin」 はプラグインを表示します。 :plugin-args があればプラグイン表示時に引数として使われます。
  • 「:default-hide」 が t だと初期表示では表示されません。必要な時だけ表示したいウインドウはこれで隠しておくことができます。

必須なのは :name だけです。あとはパースペクティブの定義の中で、どのバッファをどのように処理するかが記述してありますので、そこで動的に内容が決まっていきます。

これらの変数を設定することで、ウインドウのレイアウトや、プラグインや設定などを変更するカスタマイズを行うことができます。

パースペクティブの定義についてはまた後ほど書く予定です。

キーバインド

また、よく使うキーバインドについても使いやすく設定した方がいいと思います。

プレフィックスキーは e2wm:prefix で定義されていますので、プレフィックスキーを変更する場合は、この変数を (require 'e2wm) する前に変更しておいてください。

以下の変数で全体や各パースペクティブキーバインドを設定します。

  • パースペクティブ全体 / e2wm:pst-minor-mode-keymap
  • code / e2wm:dp-code-minor-mode-map
  • two,htow / e2wm:dp-two-minor-mode-map
  • doc / e2wm:dp-doc-minor-mode-map

例えば、自分のところでは以下のようにキーバインドを設定しています。

(e2wm:add-keymap 
 e2wm:pst-minor-mode-keymap
 '(("<M-left>" . e2wm:dp-code ) ; codeへ変更
   ("<M-right>"  . e2wm:dp-two) ; twoへ変更
   ("<M-up>"    . e2wm:dp-doc)  ; docへ変更
   ("<M-down>"  . e2wm:dp-dashboard) ; dashboardへ変更
   ("C-."       . e2wm:pst-history-forward-command) ; 履歴進む
   ("C-,"       . e2wm:pst-history-back-command) ; 履歴戻る
   ("C-M-s"     . e2wm:my-toggle-sub) ; subの表示をトグルする
   ("prefix L"  . ielm) ; ielm を起動する(subで起動する)
   ("M-m"       . e2wm:pst-window-select-main-command) ; メインウインドウを選択する
   ) e2wm:prefix-key)

(e2wm:add-keymap 
 e2wm:dp-doc-minor-mode-map 
 '(("prefix I" . info)) ; infoを起動する
 e2wm:prefix-key)

(defun e2wm:my-toggle-sub () ; Subをトグルする関数
  (interactive)
  (e2wm:pst-window-toggle 'sub t 'main))

e2wm:add-keymap はキーバインド追加のヘルパー関数です。kbdマクロの文字列かベクトルでキーを設定し、対応するコマンドを指定します。文字列の中に「prefix」が入っていると、プレフィックスキーに置換されます。

これらの設定のひな形を以下のところにおいておいてみました。設定の時の参考にしてみてください。

http://github.com/kiwanami/emacs-window-manager/raw/master/e2wm-config.el

その他いろいろ

elscreenなどのウインドウ管理をするものとはちょっと相性が悪いです。自分もelscreenを使っていますので、そのうち解決策を考えてみます。

結構いろいろがんばってウインドウ表示系を乗っ取っていますので、たまにウインドウがおかしくなったり、エラーが出てEmacsが動かなくなったりします。そんなときは、「C-c ; Q」で一旦終了してもう一度e2wmを開始するか、Emacsを再起動してみてください。

Emacsのすべての作業をe2wm.elで管理するのも無理があるかもしれません。自分はコードやドキュメントを書くときには使っていますが、Wanderlusthowmでは別プロセスのEmacsでelscreenを使っていて、e2wm.elは使っていません。ただ、howmと組み合わせるとおもしろいかもとは思っていますので、今後発展があるかもしれません。

ewm.elという名前のelがつい最近 gnu-emacs-sources@gnu.org にPOSTされました。しかも結構考えていることが似ています。しかしながら、そのPOSTよりちょっと前から同じ名前で github に登録していて、しかもググればトップに出てくる状態だったので、しばらく名前は変えない方向で粘ります。

e2wm は、equilibrium emacs window manager の略です。自分のスタイルにカスタマイズして近づけていくと、やがて平衡に達するというイメージです。自分の中ではある程度平衡に達しつつあるのですが、もうちょっといろいろ他の人の意見も聞いてみながら発展させていければいいなと思っています。

今後

今回は簡単に使い方の紹介を書いてみました。
e2wm.elの考え方や仕組みの説明も今後何回かに分けて書く予定です。内容は以下のようです。

  1. 開発環境、エディタについての考察からe2wm.elの要件定義
  2. e2wm.el の設計と実装
  3. e2wm.el の拡張、カスタマイズ、今後

予定ですので、途中で力尽きてしまうことがあります。