Emacsを起動する

この記事は記憶が正しければ[Emacs Advent Calendar 2015](http://qiita.com/advent-calendar/2015/emacs)の何日めかのつもりでしたが、あれ、目から水が…

さて、気をとりなほして。

> 教育者, 将軍, 栄養士, 心理学者, 親はプログラムする. 軍隊, 学生, 一部の社会はプログラムされる.

*(計算機プログラムの構造と解釈 第二版 序文[^1]より引用)*

みなさんはEmacsをプログラムする。

[^1]: [計算機プログラムの構造と解釈 第二版 序文](http://sicp.iijlab.net/fulltext/xfore.html) 2000年 Gerald Jay Sussman, Harold Abelson, Julie Sussman 著 / 和田英一 訳

以前、[Emacs - 起動直後になんかする。recentfとかね](http://qiita.com/tadsan/items/3d4c5d14850cff7fbcb5)って記事を書いたときに調べた流れから、ちょこちょこピックアップして紹介します。まとまりはありません。

筆者の環境の都合上、現在の開発版(25)ではなく、安定版(24.5)のファイルを参照します。

https://github.com/emacs-mirror/emacs/blob/emacs-24.5/lisp/startup.el

## Emacs Lispのコードを読む前に

### `find-function`/`find-variable`

定義位置のソースコードを開いてジャンプすることができます。

## which-func

モードラインに現在編集中の函数名を表示します。これから読むファイルにはちょっと長めの函数もあるので、現在位置を見失ったりしないためにべんりです。

### hook

Emacsの初期化処理にはフックを設定することができます。

* [GNU Emacs Manual: Hooks](http://www.gnu.org/software/emacs/manual/html_node/emacs/Hooks.html)
* [GNU Emacs Lispリファレンスマニュアル: Hooks](http://www.geocities.co.jp/SiliconValley-Bay/9285/ELISP-JA/elisp_361.html)

## 起動したときに何がおこるのか

この処理自体はCから呼び出されるはずですが、今回はLispのコードを呼んで把握する範囲のみを対象とします。

### トップレベル

https://github.com/emacs-mirror/emacs/blob/emacs-24.5/lisp/startup.el#L494

Emacs Lispの函数定義は、こんな具合です。

```el
(defun normal-top-level ()
  "Emacs calls this function when it first starts up.
It sets `command-line-processed', processes the command-line,
reads the initialization files, etc.
It is the default value of the variable `top-level'."
```

わざわざ翻訳するまでもないですが、「Emacsは最初の起動時にこの函数を呼び出す。`command-line-processed`をセットし、コマンドラインを処理や初期化ファイルの読み込みなどをおこなふ」といったところですね。

`startup.el`は基本的に説明的なコメントが多くて助かります。Lispには説明的なコードが多く付属し、Emacsは自己説明的であると[Wikipediaにも言及があり](https://ja.wikipedia.org/wiki/Emacs#.E8.AA.AC.E6.98.8E.E6.96.87.E5.AD.97.E5.88.97)、実際JavadocやPythonのdocstringに引き継がれてます。

このあとしばらく、ファイルをいくつか読み込んだり、OSなどの環境ごとに変数をセットしたりします。

### 次の世界への扉

https://github.com/emacs-mirror/emacs/blob/emacs-24.5/lisp/startup.el#L623-L624

```el
      (unwind-protect
          (command-line)
```

ここでしれっと大物のサブルーチンを呼んでるので、うっかり読み飛ばしてると見逃します。

### コマンドライン

https://github.com/emacs-mirror/emacs/blob/emacs-24.5/lisp/startup.el#L814-L820

```el
(defun command-line ()
  "A subroutine of `normal-top-level'.
Amongst another things, it parses the command-line arguments."
  (setq before-init-time (current-time)
	after-init-time nil
        command-line-default-directory default-directory)
```

ここでは、初期化処理の中でもコマンドライン引数のパースを担当する部分ですね。

私にとって意外だったのは、最初に解釈するのが`VERSION_CONTROL`環境変数だったことです。この環境変数は[GNU Coreutils: Backup options](https://www.gnu.org/software/coreutils/manual/html_node/Backup-options.html)で説明されて居ります。

[Emacsの自動保存ファイル(#*#)やバックアップファイル(*~)の作成先変更まとめ / マスタカの ChangeLog メモ](http://masutaka.net/chalow/2014-05-11-1.html)あたりの話とも絡みそうですが、ちゃんと追ってません。あと、`vc.el`とは関係ないです。

次に[simple.el](https://github.com/emacs-mirror/emacs/blob/emacs-24.5/lisp/simple.el)が読み込まれます。謎ファイル名ですが、ここではテキストエディタとしての基本的な機能が定義されます。それがこのタイミングで読み込まれるのは環境変数をチェックしてる流れですね。

### オプション

https://github.com/emacs-mirror/emacs/blob/emacs-24.5/lisp/startup.el#L921-L943

```el
	 ((member argi '("-d" "-display"))
	  (setq display-arg (list argi (pop args))))
	 ((member argi '("-Q" "-quick"))
	  (setq init-file-user nil
		site-run-file nil
		inhibit-x-resources t))
	 ((member argi '("-D" "-basic-display"))
	  (setq no-blinking-cursor t
		emacs-basic-display t)
	  (push '(vertical-scroll-bars . nil) initial-frame-alist))
	 ((member argi '("-q" "-no-init-file"))
	  (setq init-file-user nil))
	 ((member argi '("-u" "-user"))
	  (setq init-file-user (or argval (pop args))
		argval nil))
	 ((equal argi "-no-site-file")
	  (setq site-run-file nil))
	 ((equal argi "-debug-init")
	  (setq init-file-debug t))
	 ((equal argi "-iconic")
	  (push '(visibility . icon) initial-frame-alist))
	 ((member argi '("-nbc" "-no-blinking-cursor"))
	  (setq no-blinking-cursor t))
```

このあたりを読めば、ロングオプションとショートオプションを指定したときに、どのような変数が対応するかわかりますね。

Emacsを活用する上で利用頻度が多そうなのは`-Q`ですね。`emacs -Q`で起動すると初期化ファイルを読み込まないので爆速で起動できます。

あと、`-u`は複数人で共有してるサーバーでこっそり他人の`.emacs`を動かしてみるときに便利です。

### はじめてのhook

https://github.com/emacs-mirror/emacs/blob/emacs-24.5/lisp/startup.el#L1000

```el
  (run-hooks 'before-init-hook)
```

はい、はじめてのフック起動が登場しました。とはいっても、まだ`init.el`は呼んでないので、ふつうはユーザーのカスタマイズとしてこのフックに何かを登録することはありません。

### 初期化ファイルを探せ

https://github.com/emacs-mirror/emacs/blob/emacs-24.5/lisp/startup.el#L1115-L1132

```el
		    (let ((user-init-file-1
			   (cond
			     ((eq system-type 'ms-dos)
			      (concat "~" init-file-user "/_emacs"))
			     ((not (eq system-type 'windows-nt))
			      (concat "~" init-file-user "/.emacs"))
			     ;; Else deal with the Windows situation
			     ((directory-files "~" nil "^\\.emacs\\(\\.elc?\\)?$")
			      ;; Prefer .emacs on Windows.
			      "~/.emacs")
			     ((directory-files "~" nil "^_emacs\\(\\.elc?\\)?$")
			      ;; Also support _emacs for compatibility, but warn about it.
			      (push '(initialization
				      "`_emacs' init file is deprecated, please use `.emacs'")
				    delayed-warnings-list)
			      "~/_emacs")
			     (t ;; But default to .emacs if _emacs does not exist.
			      "~/.emacs"))))
```

MS-DOSでは`.`から始まるファイルが作れないので、みたいな事情で`_emacs`ってファイルから読み込むようになってます。

よく見てみると`(concat "~" init-file-user "/.emacs")`って感じで初期化ファイル名を作ってますね。この変数にはさきほど紹介した`-u`でユーザー名を指定したときには`~tadsan/.emacs.d`のように、ユーザーのホームディレクトリが指定されてますね。

### 初期化ファイルをもいっこ探せ

https://github.com/emacs-mirror/emacs/blob/emacs-24.5/lisp/startup.el#L1138-L1153

```el
          (when (eq user-init-file t)
			;; If we did not find ~/.emacs, try
			;; ~/.emacs.d/init.el.
            (let ((otherfile
                   (expand-file-name
                "init"
                (file-name-as-directory
                 (concat "~" init-file-user "/.emacs.d")))))
              (load otherfile t t)

              ;; If we did not find the user's init file,
              ;; set user-init-file conclusively.
              ;; Don't let it be set from default.el.
              (when (eq user-init-file t)
                (setq user-init-file user-init-file-1))))
```

今度は`.emacs`または`.emacs.el`が存在しなかったときには`.emacs.d/init.el`を読み込もうとします。

実際のところ、[GNU Emacs Lisp Reference Manual: Init File](http://www.gnu.org/software/emacs/manual/html_node/elisp/Init-File.html#Init-File)などを読んでも、`.emacs` `.emacs.el` `.emacs.d/init.d`のどれが推奨・非推奨だといったことは書かれてません。が、`.emacs`は古い形式であり`.el`と`.elc`の関係に対応しない問題があります。

一つに決まってない以上は個人の好みなのですが、`.emacs.d/init.el`の方がdotfilesとして管理しやすくて良いかなー、と思ってます。閑話休題。

ともあれ、この段階であなたがカスタマイズしたEmacsの初期化ファイルが読み込まれるわけです。

### パッケージ初期化

package.elの初期化チェックをしてたりします。今回は割愛しますが、Emacs 25 (次期バージョン)では`init.el`に`(package-initialize)`って書いておくことが推奨されてます。

### hook、ふたたび

https://github.com/emacs-mirror/emacs/blob/emacs-24.5/lisp/startup.el#L1287-L1288

```el
  (setq after-init-time (current-time))
  (run-hooks 'after-init-hook)
```

ここで起動時間の記録と、初期化後のhookが呼ばれます。


### また引数のターン

https://github.com/emacs-mirror/emacs/blob/emacs-24.5/lisp/startup.el#L1309-L1310

```el
  ;; Process the remaining args.
  (command-line-1 (cdr command-line-args))
```

ここで`command-line-1`函数を呼んで、残りの引数を解釈します。

https://github.com/emacs-mirror/emacs/blob/emacs-24.5/lisp/startup.el#L2165-L2166

```el
(defun command-line-1 (args-left)
  "A subroutine of `command-line'."
```

### ファイルを開く

https://github.com/emacs-mirror/emacs/blob/emacs-24.5/lisp/startup.el#L2340-L2360

このあとはもろもろの引数が処理されるのですが、Emacsはエディタなので、特にここに着目してみます。

```el
		  ((member argi '("-find-file" "-file" "-visit"))
		   (setq inhibit-startup-screen t)
		   ;; An explicit option to specify visiting a file.
		   (setq tem (or argval (pop command-line-args-left)))
		   (unless (stringp tem)
		     (error "File name omitted from `%s' option" argi))
		   (setq file-count (1+ file-count))
		   (let ((file (expand-file-name
				(command-line-normalize-file-name tem)
				dir)))
		     (if (= file-count 1)
			 (setq first-file-buffer (find-file file))
		       (find-file-other-window file)))
		   (unless (zerop line)
		     (goto-char (point-min))
		     (forward-line (1- line)))
		   (setq line 0)
		   (unless (< column 1)
		     (move-to-column (1- column)))
		   (setq column 0))
```

`inhibit-startup-screen`はEmacsの起動時の画面のことです。筆者の画面ではこんな感じ。

![]()

で、Emacsの起動時にファイルを開く場合は邪魔なので表示しないようになってます。

`file-count`はこの函数の頭の方で`let`で作られたローカルな変数です。`emacs`コマンドには複数のファイル名を指定することができるので、最初に指定されたファイルが最初の画面になるように区別されてます。

ここで気に留めておきたいのが、コマンドラインオプションの解釈と同時に`find-file`(または`find-file-other-window`)で **逐次的に実ファイルも開いてる** ことですね。

あと、実はこのあとに同じ処理がもう一度書かれてて、DRYじゃないです。

### そしてまたhook実行

https://github.com/emacs-mirror/emacs/blob/emacs-24.5/lisp/startup.el#L2430-L2467

```el
    (if (or inhibit-startup-screen
	    initial-buffer-choice
	    noninteractive
            (daemonp)
	    inhibit-x-resources)

	;; Not displaying a startup screen.  If 3 or more files
	;; visited, and not all visible, show user what they all are.
	(and (> file-count 2)
	     (not noninteractive)
	     (not inhibit-startup-buffer-menu)
	     (or (get-buffer-window first-file-buffer)
		 (list-buffers)))

      ;; Display a startup screen, after some preparations.

      ;; If there are no switches to process, we might as well
      ;; run this hook now, and there may be some need to do it
      ;; before doing any output.
      (run-hooks 'emacs-startup-hook)
      (and term-setup-hook
	   (run-hooks 'term-setup-hook))
      (setq inhibit-startup-hooks t)

      ;; It's important to notice the user settings before we
      ;; display the startup message; otherwise, the settings
      ;; won't take effect until the user gives the first
      ;; keystroke, and that's distracting.
      (when (fboundp 'frame-notice-user-settings)
	(frame-notice-user-settings))

      ;; If there are no switches to process, we might as well
      ;; run this hook now, and there may be some need to do it
      ;; before doing any output.
      (when window-setup-hook
	(run-hooks 'window-setup-hook)
	;; Don't let the hook be run twice.
	(setq window-setup-hook nil))
```

まだ`command-line-1`の中なのですが、ここでいくつかのhookが実行されます。ここだけ見ると一定の条件下では`emacs-start-hook`とか`term-setup-hook`が実行されなさそうですが、実際は`command-line`と`command-line-1`を抜けた後で、きちんと実行されます。


### スプラッシュスクリーン

https://github.com/emacs-mirror/emacs/blob/emacs-24.5/lisp/startup.el#L2476

```el
      (display-startup-screen (> file-count 0)))))
```

上記コードの直下にあります。いちおう`file-count`を参照して、複数のファイルが開かれてたら全面に出ないようにはなってます。

### `command-line`を抜けて

ここまでくると残りは消化試合で、`load-path`のチェックをしたり、emacs-serverとして起動されてたら必要な処理をしたりします。

そして、`normal-top-level`が終了。ありがとうございました。

## 振り返る

### そのとき、何がしたかったのか

Emacsの起動時に処理を挟み込んで、「コマンドライン経由でファイルが開かれたときは何もしない」「ファイルが何も開かれてないときは`recentf`からファイルを開く」といった分岐をしたかったのです。

↑ この処理を実現したい型はMELPAから `init-open-recentf` パッケージをインストールして、`init.el` に `(init-open-recentf)` って書いてね

### 何に悩んだのか

上記の処理を`init.el`だけで完結させて書けないかと試行錯誤したのですがうまくいかず、この記事に書いたスタートアップ処理を一から辿っていって把握するはめになったわけです。

### 開いたファイルの数

`command-line-1`の中では`file-count`変数を使ってうまく処理してるのですが、残念ながらこれはローカル変数なので別のところで参照することはできません。ので、全バッファを開いてみて`buffer-file-name`をリストアップしてるのですが、もっとうまい方法はないものか…

```el
(defun init-open-recentf-buffer-files ()
  "Return list of opened file names."
  (let ((found-files '()))
    (dolist (buffer (buffer-list))
      (with-current-buffer buffer
        (when buffer-file-name
          (cl-pushnew buffer-file-name found-files))))
    found-files))
```

ちなみにEmacs組み込みの`list-buffers`の中でも似たようなことをやってます。

## まとめ

まとめることは特にないんですけど、Emacsデフォルトの機能だけでスタートアップ処理を辿っていったり、(やりたくないけど)改変すらできてしまふのがおもしろいですね。最近のElectron系のエディタではJavaScriptのコードを読めたりするのかな?