Software Design連載記事を掲載します。

株式会社技術評論社の許可を得て掲載しています。
草稿なので細かい部分は実際の記事とは異なることがあります。

他の記事は左下にある「■雑誌連載中(全文公開)」から見られます。

Emacsのセーフガードシステム

Emacsが歴史のあるソフトウェアであることは今更言うまでもありません。
歴史があるということは、それだけ多くの人に使われていて、長年にわたるノウハウが蓄積されていることを意味します。
昔から多くの人が不満に思っている点はたいてい解決されています。

今回はEmacsで使える多くのセーフガードシステムを標準・外部パッケージ問わず紹介していきます。
人間は操作ミスをする生き物なので、Emacsではその被害をなくしたり最小限に抑えるための方法が多くあります。
昔からよく「保存し忘れたからフリーズしたときにデータが飛んだ」とかの悲鳴を聞きますが、Emacsならばそんなことは太古の昔に解決されているのです。
Emacsの状態を元に戻す機能も大切なセーフガードです。
けれども、せっかくセーフガードシステムが用意されていても知らなかったのでは意味がありません。
ここでは以下のテーマを採り上げます。

  • 間違った編集を元に戻す
  • 迷子になったカーソルを戻す
  • ウィンドウ構成を戻す
  • 自動保存でデータ消失をなくす

編集を元に戻す

標準のundo

通常、Emacsのバッファは作成時からの編集履歴を記憶しています。
間違った編集をしてバッファの内容がめちゃくちゃになったとしても、あわてずにC-/ (undo)を押してください。
直前の編集状態に戻ります。
もっと前に戻したい場合は引き続きC-/を押します。
もし、戻しすぎてしまった場合はC-fなど編集しないコマンドを実行後、C-/を押します。

undoをカイゼンするredo+パッケージ

標準のC-/は戻しすぎてしまった場合の挙動が使いやすくありません。
というのは、過去方向だけでなく未来方向に進むことがあるからです。
redo+パッケージはundoを過去方向のみ遡れるように再定義し、未来方向に進むM-x redoを定義します。
MELPAに登録されているのでM-x package-install redo+でインストールして設定を加えてください。
この設定を加えた場合、C-/を押しすぎてしまった場合にC-M-/で戻せるようになります。

(require 'redo+)
(global-set-key (kbd "C-M-/") 'redo)

編集履歴を永続化するundohistパッケージ

通常は編集履歴はバッファ作成時に初期化されます。
つまり、バッファを削除したり、ファイルを開き直したり、Emacsを終了したときに失われてしまいます。
undohistパッケージはバッファを削除する際に編集履歴をディスクに保存し、再びファイルが開かれるときにそれを復元します。
これもMELPAに登録されているのでM-x package-install undohistでインストールしてください。

(require 'undohist)
(undohist-initialize)
;;; 永続化を無視するファイル名の正規表現
(setq undohist-ignored-files
      '("/tmp/" "COMMIT_EDITMSG"))

編集履歴をツリー状に可視化するundo-tree

C-/で編集を戻して新しく編集しなおすと、編集履歴は分岐することになります。
つまり、戻った時点から見て間違った古い履歴と、次の編集で作られる新しい履歴ができます。
新しい履歴が作られたとき、通常のC-/では古い履歴にアクセスできません。
せっかく編集履歴のデータには古い履歴も保持してるのに、もったいないですね。

そこでundo-treeパッケージを使って編集履歴の木構造を「見える化」します。
元々undoはC-x uとC-/に割り当てられていますが、undo-treeを導入すると両者に別の役割が与えられます。
C-/にはundo-tree版undoであるM-x undo-tree-undoが、C-x uには編集履歴の木構造にアクセスするM-x undo-tree-visualizeが割り当てられます。
木構造には直観的に操作でき、C-p/C-nで履歴を時系列順にたどり(上下)、C-b/C-fで分岐を選択(左右)します。
なお、M-x redoをキーに割り当てている場合は、そのキーにM-x undo-tree-redoが割り当てられます。

(setq undo-tree-mode-lighter "")
(global-undo-tree-mode 1)

20150712233729.png
Fig1: C-x uで編集履歴にアクセス

カーソル位置を戻す

C-u C-SPCでマークを戻す

Emacsには様々なカーソル移動コマンドがありますが、長距離を移動するコマンドを実行した場合にすぐに元の位置に復帰できる仕組みになっています。
つまり、行きはよくても帰り方がわからない…つまりカーソルが迷子になる…ことがないようなセーフガードが設けられています。
本節の内容は以前の連載でも触れましたが、セーフガードの観点から再び採り上げることにします。

長距離移動とは、バッファ先頭・末尾への移動(M-<, M->)やインクリメンタルサーチ(C-r, C-s)や関数単位の移動(C-M-a, C-M-e)などが該当します。
これらのコマンドを実行するとき、予め「暗黙のマーク」によって元のカーソル位置を記憶します。
そして、C-u C-SPCを押せば元の位置に戻れます。
たとえばバッファ先頭を見てからすぐ戻る場合、M-<の後にC-u C-SPCを押せばいいだけです。
C-SPC M-<と明示的にマークする必要はありません。

暗黙のマークの存在を知っていればとても楽になります。
たとえばソースコードにrequire(必要なライブラリの宣言)を書き加える場合、requireまでインクリメンタルサーチで移動し、書き加え、C-u C-SPCで戻れるのです。

何かしらの理由でカーソル位置が思いもよらない場所に移動してしまった場合、暗黙のマークがしてあればC-u C-SPCを押せば元の位置に戻れるのです。

マークは複数個記憶しているので、C-u C-SPCを繰り返せばどんどん過去のマークへ移動できます。
何度もC-uを付けるのが面倒であれば以下の設定を加えるとよいです。
これによりC-u C-SPC C-SPC...とC-SPCを連打して遡れます。

(setq set-mark-command-repeat-pop t)

カーソル位置を戻すpoint-undoパッケージ

C-u C-SPCはカーソル迷子の万能薬ではありません。
C-v/M-vなどの画面スクロールは暗黙のマークを設定しません。
また、外部パッケージによるコマンドも必ずしも暗黙のマークを設定するとは限りません。
MELPAにある拙作point-undoパッケージは、すべてのカーソル移動を記憶することで、暗黙のマークに関係なくカーソル位置を戻します。
見た目上違和感のないようにカーソル位置だけでなくウィンドウの表示位置も復元します。

以下の設定ではf7でカーソル位置を過去方向に戻し、M-f7で戻しすぎたカーソル位置を未来方向へ進めます。
お使いの環境によってはf7がEmacsで使えないこともあるので、そのときは他のキーに割り当ててください。
そのときはf7とM-f7のように対になるキーバインドをおすすめします。

(require 'point-undo)
(global-set-key [f7] 'point-undo)
(global-set-key [M-f7] 'point-redo)

編集履歴からカーソル位置をたどるgoto-chgパッケージ

前節で示したように、Emacsは編集履歴を記憶しています。
編集履歴とは変更された場所とその内容の集まりです。
MELPAにあるgoto-chgパッケージは、編集履歴の中の変更された場所にアクセスすることで、変更された位置をさかのぼります。

これは慣れるまで動作がイメージしづらいかもしれませんが、バッファに変更を加えた時点で自動的に編集履歴に記録されることを理解すればよいです。
手動のC-SPCに対して、自動のgoto-chgです。

goto-chgは別な場所を編集してから元の場所を編集するときに効果を発揮します。
たとえば文章を書いていて、ふと誤字脱字が目に付いて、そこを修正した後、再び元の位置に戻って文章の続きを書くといったケースです。

以下の設定ではf8で編集履歴に記憶された場所を過去方向にたどり、M-f8で未来方向です。

(require 'goto-chg)
(global-set-key [f8] 'goto-last-change)
(global-set-key [M-f8] 'goto-last-change-reverse)

ウィンドウ構成を戻す

以前のウィンドウ構成に戻す標準機能winner

Emacsを使っているとウィンドウ構成がめまぐるしく変わります。
ウィンドウ構成とは、画面の分割状態とバッファの配置のことです。
ウィンドウを分割したり、大きさを変更したり、別なバッファを表示させたときにウィンドウ構成が変更されます。

たとえばf1 f(関数の説明表示)などのヘルプを表示した場合、ヘルプバッファの内容を読んだ後にしたいことは、元のウィンドウ構成に戻すことです。
ヘルプバッファはGUIにたとえればメッセージダイアログボックスがポップアップすることに相当します。
ダイアログボックスは元の画面に重なるように表示され、「閉じる」を押せば消滅します。

M-x winner-undoは「閉じる」ボタンに相当するコマンドで、直前のウィンドウ構成に戻します。
他の「戻す」系パッケージ同様に逆方向のM-x winner-redoもあります。
マイナーモードwinner-modeが有効のときのみ動作するので、設定を記述する必要があります。

(winner-mode 1)
(global-set-key (kbd "C-z") 'winner-undo)
(global-set-key (kbd "C-M-z") 'winner-redo)
;;; 後述のESC ESC ESCで使う場合
(setq buffer-quit-function 'winner-undo)

Emacsにポップアップウィンドウという概念を導入するpopwinパッケージが人気ですが、筆者はwinnerの方が応用範囲が広いと感じています。
popwinではpopwinで表示されたウィンドウのみを閉じますが、winnerはすべてのウィンドウが対象です。
popwinではC-gでポップアップウィンドウを閉じられるようになっていますが、それを実現するためにはかなり複雑な仕組みになっています。
ウィンドウを閉じることくらい、標準機能でも間に合います。

特殊状態を「取り止め」るにはESC(C-[)を3回叩け!

あなたは、ESC ESC ESCに割り当てられたコマンドを知っていますか?
Emacs的に意外なキーに割り当てられたkeyboard-escape-quitというコマンドはマルチな機能を発揮します。
コマンド名からしてC-g (keyboard-quit)に似ていることは想像できますが、共通点と相異点があります。

C-g とESC ESC ESCの共通点は以下の通りです。
これらの用途では素直にC-g で間に合います。

  • regionをキャンセルする
  • ミニバッファから抜ける
  • 前置引数をキャンセルする

ESC ESC ESCの独自機能は以下の通りです。

  • 再帰編集から抜ける
  • バッファを閉じる(閉じ方は設定可能)
  • ウィンドウが分割されているときは分割を解除する

そのうち「バッファを閉じる」アクションにwinner-undoを指定すれば、ESC ESC ESCでウィンドウ構成を戻せるようになります。
バッファを閉じる関数はbuffer-quit-functionに指定します。
デフォルトではnilになっていて、そのときはウィンドウ分割を解除、または隠しバッファ(バッファ名がスペースから始まる)を閉じるようになります。

再帰編集とは、コマンド実行中にバッファを編集できるようにする機能です。
代表的な再帰編集は置換で起こります。
M-%やC-M-%の途中でC-rを押せば置換は中断されて再帰編集に入り、ESC ESC ESCで抜けるまで任意のEmacsの操作ができます。
再帰編集を抜けると、実行中のコマンドが再開されます。
再帰編集は、モードラインのモード名が[]で囲まれているかどうかで判別できます。

前項でbuffer-quit-functionをwinner-undoに設定しているので、ESC ESC ESCは再帰編集から抜けるか、ウィンドウ構成を戻すコマンドになります。
単に前のバッファに戻りたいのであれば、buffer-quit-functionにprevious-bufferを設定してください。

ESC ESC ESCを定義はとてもシンプルなもので、cond式一発で構成されています。
condは他言語でいうif〜elseif〜elseif〜elseに相当する構文で、条件式を次々にチェックしていき、最初に一致した条件式にマッチする挙動を行います。
elispが読める方はM-x find-function-on-key ESC ESC ESCでコードを読んだ方が確実に理解できます。
なお、標準添付のlispソースコードがインストールされていない環境ではエラーになるので、Emacsについて深く学びたい方はインストールしてください。
Debian系列ではemacs24-elパッケージが必要です。

ウィンドウ構成を記憶させるいろいろな方法

Emacsであらゆるタスクを同時進行させている人ならば、ウィンドウ構成を戻すよりも、記憶させたくなると思います。
ウィンドウ構成を記憶させる方法はいろいろあり、そのためのパッケージもたくさん存在します。
本稿はあくまでもセーフガードを主題としているので軽く触れておくにとどめます。

標準の方法はレジスタにウィンドウ構成を記憶させることです。
C-x r wで記憶し、C-x r jで復元します。

ウィンドウ構成を記憶する無難なパッケージはelscreenです。
GNU Screenを模倣して作られていて、ウィンドウ構成をscreenに見立てて、切替えられるようになっています。
helmにもelscreenに関するアクションが存在します。
elscreen-persistパッケージでEmacsを終了してもscreenの状態を復元できます。
elscreen-separate-buffer-listパッケージでscreenごとに独立したバッファリストを持てるようになります。
elscreen + elscreen-persist + elscreen-separate-buffer-list と似たパッケージとしてpersp-modeパッケージがあります。

大昔からあるwindows.el(未パッケージ化)は20年以上にわたっていまだにメンテナンスされています。
ウィンドウ構成の情報や開いたバッファも永続化されます。

ウィンドウ構成に名前をつけるだけならばspacesパッケージやwindataパッケージがあります。

ぜひとも好みに応じてパッケージを選んでみてください。
パッケージを自由に選べるのもEmacsの魅力です。

自動保存によりデータ消失を防ぐ

標準のauto-save-mode

最後のテーマはデータ消失を防ぐ方法についてです。
まさに「セーフガード」にぴったりな話題です。

Emacsでは標準でauto-save-modeという自動保存機能が有効になっています。
この自動保存は編集中のファイルに直接保存するのではなく、別のファイルに保存されます。

一般に自動保存のファイル名は元のファイル名を「#」で囲んだものになります。
たとえば、foo.cならば#foo.c#となります。
diredを使っていると、もしかしたらこのようなファイルを見掛けたことがあるかもしれません。
普通のファイルならばカレントディレクトリに作られますが、Trampによるリモートファイル(sshやftpやsudo等)は/tmpに置かれます。

自動保存ファイルはC-x C-sなどで明示的に保存されたときに消去されます。
保存せずにバッファを削除しようとしたり、Emacsを終了しようとしたときは本当に削除・終了するかわざわざ訊いてくるので、保存忘れによる最低限のセーフガードはできています。
もし保存せずにバッファを削除した場合、自動保存ファイルが残っているので、そこからある程度の内容を復元できます。
そのため、保存忘れに対しては二重のセーフガードになっています。

そして何より嬉しいのがEmacsやOSがフリーズしたときに自動保存に助けられます。
自動保存ファイルが残っているファイルを開いたとき、

ファイル名 has auto save data; consider M-x recover-this-file

と教えてくれます。
内容が古い場合はM-x recover-this-fileを実行してみましょう。

また、一度に複数のファイルを復元したい場合はM-x recover-sessionを実行します。
自動保存ファイル名を記録したファイルがdiredで列挙されるので、C-c C-cを押せば復元できます。

自動保存の間隔が頻繁であればあるほど、データ消失を防げます。
今では以下のように事実上即自動保存しても問題ないです。

;;; 4ストロークごとに自動保存(デフォルト300)
(setq auto-save-interval 4)
;;; 1秒のアイドルで自動保存(デフォルト30)
(setq auto-save-timeout 1)

バックアップファイル

標準に備わっているもうひとつのデータ消失対策セーフガードは自動バックアップファイルの作成です。
バッファが作成され、最初に保存したときのみにバックアップファイルが作成されます。
バックアップファイル名はファイル名の後に「~」が付きます。
デフォルトでは、バージョン管理システム管理下のファイルや一時ファイルのディレクトリ(/tmp等)は対象外となります。

バックアップファイルはしばしば過保護で余計なファイルが混入されるから嫌がられます。
それならば、ファイルをバージョン管理システム管理下に置くのが一番です。
きちんとコミットされている限り、いつでも全てのリビジョンを取り出せるからです。
バージョン管理システムと比べれば、バックアップファイルは原始的な仕組みでしかありません。

バックアップファイルを作成しないのであれば、以下の設定を加えればいいです。
しかし、その分セーフガードがゆるんでいるので自己責任でお願いします。

(setq make-backup-files nil)

ファイルに直接自動保存!

標準のauto-saveは別のファイルに保存しますが、より過激なアプローチとしてファイルに直接自動保存をさせるMELPAパッケージもあります。
自動保存の間隔をコンマ数秒〜数秒に設定すれば、手動でC-x C-sする必要がなくなります。
つまり「ファイルを保存する」という概念を消し飛ばしてしまいます。
手動で保存するのが面倒と考えているならば導入の価値はあります。
「きりのいいところで保存するのではなく、きりのいいところでバージョン管理システムにコミットする」という考えを持っているなら、受け入れやすいです。

ただ、先月お話したように「利便性には代償が伴う」ことに注意してください。
意図せずにファイルを変更してしまった場合、それに気付きにくくなることです。
バッファが変更されたならばモードラインに「**」という修正された印が表示されますが、自動保存であれファイルが保存されたのならば常に無修正とみなされます。
undohistやバージョン管理システムとの連携させればよいです。

MELPAに登録されているauto-save-buffers-enhancedパッケージが古くから使われています。
*scratch*バッファも自動保存・復元します。
初回起動時はscratchファイルが存在しないのでエラーになりますが、*scratch*バッファに何か書き込むか~/.emacs.d/scratchファイルを予め作成すれば問題ありません。

(require 'auto-save-buffers-enhanced)
;;; 自動保存の対象外となるファイル名正規表現のリスト
(setq auto-save-buffers-enhanced-exclude-regexps
      '("Org Src"))
;;; *scratch*バッファも保存対象にする
(setq auto-save-buffers-enhanced-save-scratch-buffer-to-file-p t)
;;; *scratch*バッファ保存時のファイル名
(setq auto-save-buffers-enhanced-file-related-with-scratch-buffer
      (locate-user-emacs-file "scratch"))
;;; 自動保存時にWroteというメッセージを出さないようにする
(setq auto-save-buffers-enhanced-quiet-save-p t)
;;; 3秒後に自動保存
(setq auto-save-buffers-enhanced-interval 3.0)
;;; 有効にする!
(auto-save-buffers-enhanced t)

シンプルで新しい実装としてreal-auto-saveパッケージも存在します。
こちらの方はマイナーモードとして実装されているので、有効無効を切替えたり、特定のモードのみで有効にしたりできます。

保存時に自動スナップショット!

標準のバックアップファイルは最初の保存時に作られるものですが、MELPAにあるbackup-each-saveパッケージはより積極的なアプローチです。
標準とは異なり、別ディレクトリに日付を含むファイル名で保存時に毎回バックアップファイルとして書き出します。
たとえば /tmp/test.txt ならば /backup/backup-each-save/tmp/test.txt-150719_025939 のようなファイル名になります。
履歴を見たり復元したければ C-x d /backup/backup-each-save/tmp/test.txt-* のようにdiredを開けばいいです。

前項のauto-save-buffers-enhancedと組み合わせると度が過ぎるほど冗長になりますが、現在のストレージ容量を考えれば高々Emacsで編集する程度のテキストファイルがいくつあっても問題ありません。
データ消失に対して、まさに鉄璧の守りになります!

(require 'backup-each-save)
;;; バックアップ先
(setq backup-each-save-mirror-location "/backup/backup-each-save")
;;; 日付の形式を指定
(setq backup-each-save-time-format "%y%m%d_%H%M%S")
;;; 元のメジャーモードで開くように設定する
(add-to-list 'auto-mode-alist '("-[0-9]\\{6\\}_[0-9]\\{6\\}$" nil t))

終わりに

 今回はセーフガード特集ということで、元に戻すことやデータ保護について触れました。
「備えあれば憂いなし」というように、いざというときに役立つ知識をまとめました。

 筆者は「日刊Emacs」以外にもEmacs病院兼メルマガのサービスを運営しています。Emacsに関すること関しないこと、わかる範囲でなんでも御答えします。「こんなパッケージ知らない?」「挙動がおかしいからなんとかしてよ!」はもちろんのこと、自作elispプログラムの添削もします。集中力を上げるなどのライフハック・マインド系も得意としています。登録はこちら→http://rubikitch.com/juku/

本日もお読みいただき、ありがとうございました。参考になれば嬉しいです。