ここしばらく更新が止まってしまってから久しいですが、ブログを移転します。
wordpress.comにはお世話になりましたが、下書きの確認がやや面倒だったこともあってGitHub Pagesに移転することにしました。
移転先はこちらです。
これまで読んでくれた方、ありがとうございました。
コメントをもらえたときはとても嬉しかったです。
記事を読み返してみると、昔の自分のほうが楽しそうな文章を書いていますね。
タイトル通りel-init 0.2.0をリリースしました。
- HKey/el-init · GitHub
https://github.com/HKey/el-init
このバージョンではエラーハンドリングについての変更が行われました。
変更内容
このバージョンからrequireラッパーも debug-on-error の値を尊重するようになり、 特別な設定をせずとも emacs --debug-init でEmacsを起動した場合 (正確には debug-on-error がnon-nilな場合)、el-initによるロード中に発生した エラーに対してもデバッガが立ち上がるようになりました。
el-init-viewerを使って確認できる情報では不十分なときに役に立つでしょう。
スクリーンショット
- 通常時
--debug-initオプション使用時
バージョン番号について
バージョンが突然0.2.xに上がっていますが、これは最近になって セマンティックバージョンニングを知ったためで、 特別大きな変更があったというわけではないので安心してください。
el-initとエラーハンドリングについて
el-initではinit-loaderに倣って、設定ファイル群をロードする際に 発生したエラーを補足して、出来る限りロードを継続するように作られています。 そのため、 el-init 0.2.0以前から el-init-load 関数では condition-case の代わりに condition-case-unless-debug を使用していて、 requireラッパーをエラーを補足しないものに限った場合にのみ --debug-init オプション付きでEmacs起動すれば、デバッガを立ち上げることができるように していました。
しかしながら、デフォルトではrequireラッパーに el-init-require/record-error が指定されているので、いざトラブルが発生した際に簡単に利用できるかというと そうではありませんでした。 一応これには理由があって、 el-init-load がel-initの核となる部分なのに対し requireラッパーはあくまで選択可能な拡張という位置づけなので、 エラー補足を目的としたrequireラッパーが debug-on-error に対して どういう立場を取るのか、悩んでいました。
結局のところ、Emacsでデバッガを立ち上げる必要があるようなトラブルの中で 設定ファイルを修正しなければならない、というのは大変だろうということで requireラッパーでも debug-on-error を尊重する形を取ることに決めました。
これはel-initの同梱するrequireラッパーに対する方針として、 今後追加される(かもしれない)requireラッパーに対しても適応していきます。
余談
実装するにあたって、 ignore-errors や condition-case を condition-case-unless-debug に変えるだけだから簡単だろうと思っていたら 地味にハマってしまいました。 結論だけ言うと、テストを通す際にertによって debug-on-error が t になってしまうことを考慮したり、undercoverで使われているedebugの関係で edebug-on-error も nil にする必要があって、気づくまでに時間がかかりました…
- HKey/el-init · GitHub
https://github.com/HKey/el-init
0.1.5での変更点は古いシンボルの削除と el-init-require/lazy の動作改善です。
古いシンボルについては0.0.9の頃のコードが動くように残しておいたもので、 Issues (#4, #6) で予告されていたとおり2月末に削除しました。
el-init-require/lazy については、 eval-after-loadの呼び出し順について で書いたように eval-after-load の file 引数をフィーチャー名(シンボル)で 指定した場合とファイル名(文字列)で指定した場合とで読み込みのタイミングに 違いが出てしまう、ということを受けて、どちらを使って設定ファイルの読み込みを するか、ユーザーが選択できるようにしました。
引数の指定方法を変更する場合は el-init-lazy-feature-type を変更します。 el-init-lazy-feature-type を symbol にするとフィーチャー名を、 string にするとファイル名を使用して el-init-require/lazy が eval-after-load を呼び出すようになります。 また、今まではファイル名で指定していましたが、このバージョンからは フィーチャー名がデフォルトで使われるようになったので、遅延ロードを 積極的に活用している人は注意してください。
前回に引き続いて、今度は eval-after-load と after-load-functions の 呼び出し順についてです。
結論
試してみたところ次のようになりました。
eval-after-load(文字列指定)after-load-functionseval-after-load(シンボル指定)
呼び出し位置によっては eval-after-load が多用されている状況での ちょっとした順番の入れ替えに使えるかなと思ったのですが、 ちょっと意外な結果でした。
eval-after-load を多用した場面での不具合調査の時に、 この情報が役に立つ…かな?
検証用のコード
(defun my-test-after-load-function (&rest _args)) (defun my-test-eval-after-load-a ()) (defun my-test-eval-after-load-b ()) (trace-function #'my-test-after-load-function) (trace-function #'my-test-eval-after-load-a) (trace-function #'my-test-eval-after-load-b) (add-hook 'after-load-functions #'my-test-after-load-function) (with-eval-after-load 'foo (my-test-eval-after-load-a)) (with-eval-after-load "foo" (my-test-eval-after-load-b)) (add-to-list 'load-path "/tmp") (require 'foo)
/tmp/foo.el(provide 'foo)
結果
====================================================================== 1 -> (my-test-eval-after-load-b) 1 <- my-test-eval-after-load-b: nil ====================================================================== 1 -> (my-test-after-load-function "/tmp/foo.el") 1 <- my-test-after-load-function: nil ====================================================================== 1 -> (my-test-eval-after-load-a) 1 <- my-test-eval-after-load-a: nil
同じファイルに対して eval-after-load を複数呼び出した場合、 先に登録されたものから評価されると思っていたのですが少し違うようですね。
結論としては、 eval-after-load の file 引数を文字列で指定したものが 登録した順に一通り評価され、その後でシンボルで file を指定したものが 登録した順に評価されるようです。
これに気付かずしばらくハマってしまいました。
検証用のコード
(defun my-test-a ()) (defun my-test-b ()) (defun my-test-c ()) (defun my-test-d ()) (with-eval-after-load 'foo (my-test-a)) (with-eval-after-load "foo" (my-test-b)) (with-eval-after-load 'foo (my-test-c)) (with-eval-after-load "foo" (my-test-d)) (trace-function #'my-test-a) (trace-function #'my-test-b) (trace-function #'my-test-c) (trace-function #'my-test-d) (add-to-list 'load-path "/tmp") (require 'foo)
/tmp/foo.el(provide 'foo)
結果
====================================================================== 1 -> (my-test-b) 1 <- my-test-b: nil ====================================================================== 1 -> (my-test-d) 1 <- my-test-d: nil ====================================================================== 1 -> (my-test-a) 1 <- my-test-a: nil ====================================================================== 1 -> (my-test-c) 1 <- my-test-c: nil
my-test-a が後に、 my-test-d が先に呼び出されているのが分かると思います。
jaword をEvilで使いたかったので、 taraoさんの記事
- Evil: EmacsをVimのごとく使う – 拡張編 – 貳佰伍拾陸夜日記
http://d.hatena.ne.jp/tarao/20130305/evil_ext#tutorial2
を参考にして、というかほとんどそのままですがEvilで使えるようにしてみました。
(require 'cl-lib) (require 'jaword) (require 'evil) (defun my-evil-jaword-forward-word (&optional count) (let ((dir (if (natnump count) +1 -1)) (count (abs count))) (while (and (plusp count) (jaword-forward dir)) (cl-decf count)) count)) (evil-define-motion my-evil-jaword-forward-word-begin (count) :type exclusive (evil-move-beginning count #'my-evil-jaword-forward-word)) (evil-define-motion my-evil-jaword-backward-word-begin (count) :type exclusive (evil-move-beginning (- (or count 1)) #'my-evil-jaword-forward-word)) (evil-define-motion my-evil-jaword-forward-word-end (count) :type inclusive (evil-move-end count #'my-evil-jaword-forward-word nil t)) (evil-define-motion my-evil-jaword-backward-word-end (count) :type inclusive (evil-move-end (- (or count 1)) #'my-evil-jaword-forward-word nil t)) (define-key jaword-mode-map [remap evil-forward-word-begin] #'my-evil-jaword-forward-word-begin) (define-key jaword-mode-map [remap evil-forward-word-end] #'my-evil-jaword-forward-word-end) (define-key jaword-mode-map [remap evil-backward-word-begin] #'my-evil-jaword-backward-word-begin) (define-key jaword-mode-map [remap evil-backward-word-end] #'my-evil-jaword-backward-word-end)
ce で単語を書きなおしたりできるので個人的には満足していますが、 細かい部分は問題がありそうですね(行をまたぐかどうかとか)。
decompjp と ac-mozc を連携させて再変換を行う オペレータを定義して、例えばそれを gc にバインドして誤変換してしまった単語の 先頭に移動してから gce とすれば再変換ができる、 とか面白そうだなぁと思ったものの、出番が少なそうだったので アイデアでとどめておくことにしました。
- HKey/el-init-viewer · GitHub
https://github.com/HKey/el-init-viewer
el-init-viewerは el-init-record を可視化するためのものです。 el-init-record は各 require ラッパーが色々な情報を格納する為の変数で、 ロードにかかった時間や発生したエラーの情報などが記録されます。
これを可視化するのがel-init-viewerです。 ctable のおかげで一覧を表示するだけでなくソートすることもできるので、 読み込みに時間のかかった設定ファイルを見つけるといったこともできます。
インストール
MELPAに登録してあるので、
M-x package-install el-init-viewer
でインストールすることができます。
(MELPAの設定については省略します m(_ _)m)
使い方
使い方は簡単で、
M-x el-init-viewer
とすると下記のようなバッファが出現します。
このバッファはctableによるものなのでctableのキー操作を使用することができます。 また、 eval-after-load で発生したエラーのカラムをクリックすると 専用のバッファを開きます。 ( M-x el-init-viewer-eval-after-load でも可能です)
デフォルトでは el-init-record に対応するデータのないカラムは 表示されないようになっているので、el-initで el-init-record に 情報を記録する require ラッパーを使っていない場合、 何も表示されないかもしれません。 el-init-viewer-hide-no-data-column を nil にすることで カラム表示の省略を切ることができます。
情報を記録するタイプのラッパーには、ロード時間を記録する el-init-require/benchmark やエラーを記録する el-init-require/record-error などがあるので必要に応じて el-init-load の wrappers 引数に追加してみてください。
タイトルの通り、el-init 0.1.4をリリースしました。
- HKey/el-init · GitHub
https://github.com/HKey/el-init
前回の記事の後に0.1.3、0.1.4がリリースされたので、 その両方の変更点について紹介したいと思います。
0.1.3
変数 el-init-overridden-require-p の追加
新しい変数 el-init-overridden-require-p が追加されました。 この変数は require ラッパーが require 関数をオーバーライドしているか どうかを調べるためのもので、オーバーライドされた require 関数の 呼び出し中だけ t に束縛されます。
これは例えば lib- から始まる設定ファイルを el-init-load からの 読み込みを弾きつつ、他の設定ファイルからの読み込みは許したいような ラッパーを作る時に使います。
(defun my-require/filter (feature &optional filename noerror) (when (or el-init-overridden-require-p (not (string-match-p "\\`lib-" (symbol-name feature)))) (el-init-next feature filename noerror)))
el-init-require/lazy の動作改善
el-init-overridden-require-p の実装に伴って、 el-init-require/lazy の対象の設定ファイルを他の設定ファイルから require しても問題ないようになりました。
el-init-require/benchmark の計算方法の変更
今までは init-foo.el から init-bar.el を読み込んだ時、 init-bar.el の読み込みにかかった時間も init-foo.el の読み込み時間に カウントしていましたが、0.1.3からは init-bar.el の時間を含めないように なりました。
el-init-get-record の引数 default の追加
el-init-get-record に新しくオプショナル引数として default が追加されました。 これは cl-getf と同じように動作します。 (実際に内部では cl-getf を使用しています)
(let ((el-init-record nil) (not-found (cl-gensym))) (eq (el-init-get-record 'foo 'bar not-found) not-found)) ;; => t
0.1.4
0.1.4では el-init-require/lazy の不具合修正のみがなされています。
終わりに
0.1.4で el-init-require/lazy が実用的なレベルになり、自分がel-initで 作りたかったものは大体揃ったかなぁという印象です。 el-init-record ビューアがまだですけどね。
el-init-require/lazy は設定ファイルのロードを遅延するもので、 これは el-init-lazy-init-regexp で指定された設定ファイルのロードを eval-after-load の呼び出しに置き換えます。
例えば init-lazy-foo.el なら
(eval-after-load "foo" '(require 'init-lazy-foo))
と等価な形に処理をしてくれるので起動時に読み込まれる設定ファイルを 減らすことができます。 とは言え今はuse-packageがあるのでここまでする必要はあまりなさそうですね。 eval-after-load でコードをラップする必要がなくなることで 素直なコンパイラ警告が得られるので、自分では気に入っています。
もし良かったら試してみてください。
リポジトリはこちらです。
- HKey/el-init · GitHub
https://github.com/HKey/el-init
el-initは init-loader のように、分割された設定ファイルを 読み込むためのローダです。 init-loaderでは load を使って設定ファイルを読み込むのに対し、 el-initでは require を使用します。 そのためにいくつかのinit-loaderには無い制約が存在します。
インストール
基本的な使い方
例としてel-initで読み込みたい設定ファイルが ~/.emacs.e/init にあるとして、 それらをel-initで読み込むには次のようにします。
(require 'el-init) (el-init-load "~/.emacs.d/init" :subdirectories '("."))
こうすることによって、 ~/.emacs.d/init にある設定ファイルが require を使って読み込まれます。
ただし、ロードに ruquire を使っているので、設定ファイルに適切な provide 式を記述しておく必要があることに注意してください。 そうしないとエラーが発生します。
provide の記述は少々面倒なので、el-initには el-init-provide という ユーティリティ関数が用意されています。 これはファイル名を元に provide を呼び出します。 これを各設定ファイルの最後に書き加えてください。
(require 'el-init) (el-init-provide)
(require 'el-init) はなくても動作しますが、書いておくことをおすすめします。
ロード順の決定方法
el-initとinit-loaderの大きな違いはロード順の決定方法にあります。 init-loaderではファイル名の番号付けによってロード順が決定するのに対し、 el-initでは基本的に require の仕組みを利用します。
従って、ある設定ファイルが他の設定ファイルに依存している時、 依存先のファイルを require することによって期待するロード順に なるようにします。
例えば init-bar.el が init-foo.el に依存しているなら
;; init-bar.el (require 'init-foo)
とします。
el-initには require を使う方法以外にもう1つ、 ロード順を決める方法があります。 それはディレクトリを分ける方法です
これは、パッケージマネージャの初期化やロードパスの整備などの環境設定ファイルと 各パッケージ向けの設定ファイルのような、直接依存関係があるわけでは無いが ロード順の関係は存在する場合に便利です。
例えばパッケージマネージャの設定が ~/.emacs.d/init/pm に、 各パッケージ向けの設定が ~/.emacs.d/init/config にある場合、 el-init-load の subdirectories 引数を次のようにします。
(el-init-load "~/.emacs.d/init" :subdirectories '("pm" "config"))
こうすることで最初に ~/.emacs.d/init/pm にあるファイルが読み込まれ、 次に ~/.emacs.d/init/config にあるファイルが読み込まれます。
require を使用することによる制約について
1つは上で述べたように provide を呼び出す必要があることで、 もう1つは設定ファイル名を他のemacs lispと被らないようにする必要がある、 ということです。 ファイル名が被ってしまうと、他のemacs lispをシャドウすることになるので 注意してください。
require ラッパーについて
el-initには require ラッパーという仕組みがあります。 これは設定ファイルを読み込む際に使用する require を ラップできるようにするもので、el-initでは 設定ファイル内で発生したエラーの記録やロード時間の計測に この仕組みが使われています。
el-initにはすでにいくつかのラッパーが定義してあります。 使用するラッパーを指定するには el-init-load の wrappers 引数を使用します。
例えばエラーを記録する el-init-require/record-error と ロード時間を測る el-init-require/benchmark を使いたいなら、 次のようになります。
(el-init-load "~/.emacs.d/init" :subdirectories '(".") :wrappers '(el-init-require/record-error el-init-require/benchmark))
ちなみにデフォルトでは el-init-require/record-error のみが使用されます。
また、ラッパーは自作することも可能です。 ラッパーは require と同じ引数を持つ関数として定義し、 その中で require の代わりに el-init-next を呼び出します。
例として el-init-require/ignore-errors は次のように定義されています。
(defun el-init-require/ignore-errors (feature &optional filename noerror) (ignore-errors (el-init-next feature filename noerror)))
wrappers で指定されたラッパーは、リストの先頭のラッパーが呼びだされ、 そのラッパーが el-init-next を呼ぶことでリストの次のラッパーを呼び出し、 そしてまたその次へと呼び出しが行われ、リストの最後のラッパーで el-init-next が呼び出されることで require が呼び出されます。
また、 el-init-next を呼び出さないようにすれば、特定のファイルだけ 読み込みをスキップすることができます。 el-init-require/system-case ではこの仕組みを利用しています。 ただ、オーバーライドとの関係で設定ファイル内からの require でも 同様に動作するため、el-initの読み込み対象には含めたくないけど設定ファイルから require されたら読み込まれてほしいというような場合にはディレクトリを 分けておくの方が無難でしょう。1
el-init-load の引数について
el-init-load の引数は次のようになっています。
(el-init-load directory &key subdirectories wrappers override override-only-init-files)
directory
これはロードの基準となるディレクトリを指定します。
subdirectories
これは実際にロードの対象にするディレクトリを指定するリストです。 directory からの相対パスのリストで、このリストの先頭のディレクトリから 順にロードが行われます。 もし、 directory 自身も含めたい場合には、”.”も含める必要があることに 注意してください。
また、ディレクトリと中にあるディレクトリを再帰的に対象に含めたい場合には、 2番目の要素が t のリストを指定します。
次のようなディレクトリ構成の時に
~/.emacs.d/init
├── config
│ ├── ext
│ │ └── init-helm.el
│ └── lang
│ └── init-javascript.el
└── pm
└── init-package.el
config 内を再帰的に対象にしたいなら、次のようにします。
(el-init-load "~/.emacs.d/init" :subdirectories '("pm" ("config" t)))
デフォルト値は
(".")
です。
wrappers
ロードする際に使用するラッパーを指定します。 デフォルト値は
(el-init-require/record-error)
です。
override
これは設定ファイル内で呼ばれた require に対してもラッパーを 有効にするかどうかのオプションです。 デフォルトでは t で、有効にしています。
override-only-init-files
これは設定ファイル内で呼ばれた require に対してラッパーを 有効にする場合、設定ファイルに対してのみラッパーを作用させるための オプションです。 この引数がnon-nilなら、ラッパーは設定ファイルにのみ作用し、 ライブラリのロードには作用しません。 デフォルトでは t です。 設定ファイルかどうかは el-init-load の対象かどうかで決まります。
狙い
el-initの主な狙いは2つあります。
1つは分割した設定ファイルそれぞれがemacs lispファイルとして 適切に記述できるようにすることです。 つまり auto-async-byte-compile.el のような形でバイトコンパイルをしても 警告が出ない、もしくは正しい警告が得られるようにすることです。 そのために設定ファイル自身に依存関係を明示できるよう require を使用しています。
そして、もう1つはユーザーが自由にロード処理を 選択、拡張できるようにすることです。 このために require ラッパー機能があります。
以前のel-initとの違いについて
現在のel-initは、以前紹介した時に比べて色々と変更がなされているので 注意してください。 レキシカルスコープが有効になっていたり、接頭辞が el-init: から el-init- に変更されています。 詳しくは ChangeLog.org を確認してみてください。
- el-init/ChangeLog.org at master · HKey/el-init · GitHub
https://github.com/HKey/el-init/blob/master/ChangeLog.org
また、当時のコードはバージョン0.0.9としてタグを打ってあるので、 githubのリリースからアーカイブをダウンロードすることができます。 どうしても古いバージョンが必要な人はそちらを使ってください。
開発について
travis ciを使うにあたって下記のページを参考にしました。
- Emacs Lispのテスト、依存性管理、CI – 備忘録
http://www.kaichan.info/blog/2014-02-23-emacs-cask.html
テスト用のemacsは evm を使って用意しています。 テストしているバージョンは24.1から24.4までです。 23系列が無いのはcaskが動かないからです。
バイトコンパイル時の警告もテスト対象にしていますが、 組み込みでない cl-lib を使うと警告が出てしまうので、 emacs 24.1と24.2は警告を許すようにしました。
また、カバレッジ計測に undercover.el を使っています。 undercover.elについては下記のページを参考にしました。
- undercover.el で Emacs Lisp のテストも Coveralls.io に登録するぞ! – Thanks Driven Life
http://gongo.hatenablog.com/entry/2014/10/07/213249
edebugをもとにしているということで、計測対象をバイトコンパイルすると 計測できないことに気付かずしばらくハマりました。 また、現状では cl-defun もうまく計測できないようです。
今後について
el-initについてはあまり大きな変更は予定していません。 それと今回紹介しなかった機能に el-init-record というものがあって、 それのビューアを今作っています。 それでエラー内容やロード時間が確認できるようになる予定です。
また、el-initでは機能廃止の際に削除されるまでの間、 なるべく警告が出るようにしているので、 アップデートした際には注意してみてください。
Footnotes:
オーバーライドされた require からの呼び出しかどうかを判定する APIを用意するか検討中です。
windmoveはウィンドウを十字移動できるようにしてくれる拡張機能で、 ウィンドウ移動がわかりやすくなるので使っています。 普段、ウィンドウは分割しても2画面にするくらいだったので、 windmoveで移動するときに移動先のウィンドウがなかった場合に 自動的にウィンドウを分割するようにしました。
(defun my-windmove-split-as-needed (original dir &optional arg window) (let ((result (funcall original dir arg window))) (or (unless (window-minibuffer-p result) result) (split-window window nil (cond ((eq dir 'up) 'above) ((eq dir 'down) 'below) (dir)))))) (advice-add 'windmove-find-other-window :around #'my-windmove-split-as-needed)
アドバイスの定義には、るびきちさんのところで紹介されていた nadviceを使ってみました。
- 【関数再定義革命】Emacs 24.4ではdefadviceは時代遅れ!nadvice.elによる洗練された新しいアドバイス定義方法!! | るびきち「日刊Emacs」
http://rubikitch.com/2014/10/30/nadvice/
advice-add は以前のアドバイス定義に比べてわかりやすく、内容を関数として 用意できるので、再定義やデバッグが楽になっていいですね。