はじめに
この記事は「Emacs Advent Calendar 2019」の2日目の記事として書いたものです。昨日は私の「依存関係をスマートに解決しつつ「GitHub Actions」でCIを無料でぶん回す」でした。
まだ空きがあるので、ぜひ参加頂ければと思います!
leafについて
leaf.elはjwiwgleyさんのuse-packageを2.5年使った上で、私が感じていたストレスを解消するためにスクラッチから開発したパッケージです。Qiitaでもプレリリースとリリース時に記事を書きましたが、半年経つと新機能も増えてきます。
- プレリリース記事: use-packageからの移行のすゝめ - leaf.elでバージョン安全なinit.elを書く
- リリース記事: {正式リリース}leaf.elで雑然としたEmacs設定ファイル「init.el」をクリーンにする
リリースしたときの記事に追記しようかと思いましたが、別記事にすることにしました。
leafのバージョニングについて
leaf.elのissues/pullsを見てもらえれば分かりますが、 v1.0.0
を付けたときからmasterへのcommitを禁止しており、issueを立てて、そのissueに対するPRを自分でマージする形でアップデートをしています。さらに、1つのPRで 0.0.1
バージョンが上がるようにしているので、バージョンを見るだけでどれだけアップデートされたかが分かるようになっています。
リリース記事を見ると、2019/06/15に投稿されており、この記事には2019/06/14にマージした「Treat pair which car is list in :hook, :defun etc」までの情報で書かれています。
これは :hook
や :modes
, :defun
の分配指定の機能です。leaf.elとは/変数設定のためのキーワード/:hookの章で紹介されている、3つ目の例がちゃんと動くようにしたPRでした。(これはuse-packageでも実装されている機能なので、これをマージしないと記事として収まりが悪かった。)
(cort-deftest-with-macroexpand leaf/hook
'(((leaf ace-jump-mode
:hook cc-mode-hook
:config (ace-jump-mode))
(prog1 'ace-jump-mode
(autoload #'ace-jump-mode "ace-jump-mode" nil t)
(add-hook 'cc-mode-hook #'ace-jump-mode)
(eval-after-load 'ace-jump-mode
'(progn
(ace-jump-mode)))))
((leaf ace-jump-mode
:hook cc-mode-hook (prog-mode-hook . my-ace-jump-mode))
(prog1 'ace-jump-mode
(autoload #'ace-jump-mode "ace-jump-mode" nil t)
(autoload #'my-ace-jump-mode "ace-jump-mode" nil t)
(add-hook 'cc-mode-hook #'ace-jump-mode)
(add-hook 'prog-mode-hook #'my-ace-jump-mode)))
((leaf ace-jump-mode
:hook ((cc-mode-hook prog-mode-hook) . my-ace-jump-mode))
(prog1 'ace-jump-mode
(autoload #'my-ace-jump-mode "ace-jump-mode" nil t)
(add-hook 'cc-mode-hook #'my-ace-jump-mode)
(add-hook 'prog-mode-hook #'my-ace-jump-mode)))))
このPRでは v3.2.5
が付けられています。現在のHEADは v3.6.8
が付けられているので、単純には43件のPRがマージされたことになります。実際には外部の方のPRなどで、バージョンを上げてもらうことを言い忘れてマージしたことがあるので、少し誤差がありますが。。
leaf.el
Issue
Issueは期間中に55件ありました。
- #253 Treat pair which car is list in :hook, :defun etc
- #255 Add leaf-expand-minimally variable
- #257 What is difference between use-package and leaf?
- #258 Add leaf-available-keywords function
- #260 make leaf-avairable-keywords interacrive function
- #262 Use dockerize Emacs when running test
- #263 Fix readme; fix english error
- #265 {Question} What is the replacement for `use-package-always-ensure`?
- #266 Eager macro-expansion failure: (error "leaf: Unrecognized keyword :bind-keymap")
- #267 {Help} Unable to use leaf to initialize navbar
- #270 :config not working when :bind is used?
- #271 fix patreon logo
- #273 request: don't defer keymap bindings when :leaf-defer is nil
- #274 fix install code
- #276 Setting or customizing variables from plstore
- #277 not use leaf-error and leaf-warn at expansion form
- #279 Why don't :setq, :custom, etc. distribute when normalizing?
- #281 Disable indent-tabs-mode in emacs-lisp-mode via .dir-locals.el
- #283 Add default plstore variable and get from it if value omitted
- #285 Align code and testfile
- #287 Move :advice and :advice-remove documents to misc keyword
- #289 Add leaf-key-bindlist visualization
- #291 Update README
- #293 Fix :diminish expansion
- #294 :ensure keyword with package-selected-packages integration
- #295 :hook cannot handle lambda with multiple expressions
- #296 :hook should not autoload lambda expressions
- #299 :after should accept `t' symbol
- #301 Don't provide a leaf-tests feature
- #303 Fix leaf-handler-package error handling
- #305 :custom should expnad behind of :leaf-defer
- #306 init.el with leaf as a practical example
- #308 Sort expansion order, :init and :config keyword
- #310 Refactoring imenu support feature
- #312 Refactoring: mapcan declare when compile time
- #314 Remove :dummy list element
- #316 Refactoring; code sorting, adding docstring
- #319 Add leaf expand visualization feature
- #322 lambda function does not accepted by :bind keyword
- #323 Not working leaf-to-string with error
- #326 Suppress byte-compiler warnings, unused lexical variable, err
- #328 expand :init before :leaf-defer
- #330 Do configure autoload if not have already set autoload
- #332 Create :leaf-defun, auto declare function feature
- #334 Add :leaf-defvar keyword
- #336 fix :leaf-defvar expand statement
- #338 add auth-* keywords as alias of pl-* keywords
- #340 align keywords
- #342 Remove unneeded quote in plstore example
- #346 use with-output-to-temp-buffer to create leaf-expand buffer
- #347 add :let keyword
- #348 Add debug code in eval-after-load
- #349 Add vector test-case for :bind
- #351 update slack url
- #353 use shields.io badge for GitHub Actions
Pull request
Pull requestは期間中に45件ありました。つまりバージョン上げるのを2回見落としてます。。
- #254 feature#253
- #256 feature#255
- #259 feature#258
- #261 feature#260
- #264 feature#263
- #268 feature#262
- #269 Correctly eval cdr
- #272 feature#271
- #275 feature#274
- #278 feature#277
- #280 Implement plstore-related keywords
- #282 feature#279
- #284 feature#281
- #286 feature#285
- #288 feature#287
- #290 feature#283
- #292 feature#291
- #297 feature#296
- #298 feature#295
- #300 Add imenu support feature.
- #302 feature#301
- #304 feature#303
- #307 feature#305
- #309 feature#308
- #311 feature#310
- #313 feature#312
- #315 feature#314
- #317 feature#316
- #318 feature#289
- #320 Typo Corrections: faether.el and feathre.el -> feather.el
- #324 feature#323
- #325 feature#319
- #327 feature#326
- #329 feature#328
- #331 feature#330
- #333 feature#332
- #335 Feature#334
- #337 feature#336
- #339 feature#338
- #341 feature#340
- #343 feature#342
- #344 Fix uncountable nouns
- #345 Fix expressions in contributing.org
- #350 feature#349
- #352 feature#351
leaf-keywords.el
leaf-keywords.elは外部パッケージに依存するキーワードをまとめたパッケージです。
leaf.elをEmacs本体に入れるという野望のために分けてありますが、基本的にはleaf.elとleaf-keywords.elは両方入れることを前提にしてます。
Issue
- #42 add autoload magic comment
- #44 Add :selected keyword
- #45 Add :quelpa keyword
- #46 fix patreon logo
- #48 degrade with leaf-pair fixed
- #51 Fix :diminish expansion
- #52 Don't provide a leaf-keywords-tests feature
- #55 :diminish does not work
- #56 Automatically require packages feature
- #58 Fix auto require feature
- #60 Refactoring: code sorting, adding docstring
- #62 Remove eval-after-load statement
- #64 Remove straight from auto require packages
- #67 update slack url
- #69 Use Github Action instead of travis
Pull request
- #70 feature#69
- #68 feature#67
- #66 feature#55
- #65 feature#64
- #63 feature#62
- #61 feature#60
- #59 feature#58
- #57 feature#56
- #54 feature#34
- #53 feature#52
- #50 feature#51
- #49 feature#48
- #47 feature#46
- #43 feature#42
追加された機能
Add imenu support feature
この半年の目玉機能は、間違いなくgrugrutさんに提案頂いた、leafのImenu integrationでしょう。
use-packageにもあるみたいですが、どうやらバグっているとのことなので、leaf.elでしか使えません。便利すぎるので、デフォルトでオンにしてあります。
そもそもEmacsにはimenuという機能があり、 M-x imenu
で起動できます。ファイルをスキャンして関数定義や変数定義などの主要な定義についてのリストを提供し、その場所へのジャンプを提供します。
私はこのPRを受けて、この機能を知り、とても便利だったので C-s
を counsel-imenu
にあげることにしました。
ivy
の isearch
相当は swiper
なのですが、最近 helm-swoop
のメンテナになったので、 helm-swoop
を C-S-s
に割り当てて使っています。
(leaf ivy
:ensure t
:diminish ivy-mode
:custom ((ivy-re-builders-alist . '((t . ivy--regex-fuzzy)
(swiper . ivy--regex-plus)))
(ivy-use-selectable-prompt . t)
(ivy-mode . t)
(counsel-mode . t))
:init
(leaf *ivy-requirements
:config
(leaf swiper
:disabled t
:ensure t
:bind (([remap isearch-forward] . swiper)))
(leaf counsel
:ensure t
:diminish counsel-mode
:bind (([remap isearch-forward] . counsel-imenu)
("C-x C-r" . counsel-recentf)))))
(leaf helm
:ensure t
;; :require helm-config
:config
(leaf helm-swoop
:load-path `,(locate-user-emacs-file "site-lisp/helm-swoop")
:custom (helm-swoop-pre-input-function
. (lambda ()
(if mark-active
(buffer-substring-no-properties (mark) (point))
"")))
:bind ((helm-swoop-map ("C-s" . helm-multi-swoop-all-from-helm-swoop))
("C-S-s" . helm-swoop)
("C-c f" . hydra-helm-swoop/body))))
Implement plstore-related keywords
- #276 Setting or customizing variables from plstore
- #280 Implement plstore-related keywords
- #283 Add default plstore variable and get from it if value omitted
- #338 add auth-* keywords as alias of pl-* keywords
目玉機能その2です。機密情報をEmacsの外に保存して、動的に復号し、Emacsの中で使う auth-sources
のleafキーワードです。
この機能についてはちょっと説明が必要なので、別記事にし てAdvent Calenarの穴埋めをし ます。
Add leaf-key-bindlist visualization
目玉機能その3です。leafの :bind
や :bind*
で設定したキーバインドのリストを表示します。もしEmacsデフォルトのキーバインドを上書きしている場合は上書き前の関数名も表示されます。
これも、もともとuse-packageに実装されている describe-personal-bindings
の移植です。
しかし、use-packageでは単にbufferにフォーマットされた文字列を表示するだけですが、leaf-key-describe-bindings
ではEmacsビルドインの表データ表示ライブラリ、 tabulated-list
を使用して表示します。
それによって各列での昇順、降順のソートなどができることで、use-packageのそれより使いやすく/見やすくなっていると思います。
Add leaf-expand-minimally variable
leaf-emapand-minimally
という変数を追加しました。これは、use-packageにもある use-package-expannd-minimally
の移植です。移植といいつつ、機能だけの移植でどうやって実装されているかまで見ていません。
leafの leaf-expand-minimally
の実装は leaf-expand-minimally-suppress-keywords
に指定されたキーワードに対して、優先度最大で、 nil
を指定するというものです。
今のところ leaf-expand-minimally-suppress-keywords
には :leaf-protect
のみが指定されているので、有効にした場合は:leaf-protectだけが無効化されます。
(defmacro p (form)
"Output FORM processed `macroexpand-1' and `pp'."
`(progn
(pp (macroexpand-1 ',form))
nil))
;;=> p
(p (leaf ace-window
:ensure t
:bind (("M-o a w" . ace-window))))
;;=> (prog1 'ace-window
;; (leaf-handler-leaf-protect ace-window
;; (unless
;; (fboundp 'ace-window)
;; (autoload #'ace-window "ace-window" nil t))
;; (declare-function ace-window "ace-window")
;; (leaf-handler-package ace-window ace-window nil)
;; (leaf-keys
;; (("M-o a w" . ace-window)))))
(let ((leaf-expand-minimally t))
(p (leaf ace-window
:ensure t
:bind (("M-o a w" . ace-window)))))
;;=> (prog1 'ace-window
;; (unless
;; (fboundp 'ace-window)
;; (autoload #'ace-window "ace-window" nil t))
;; (declare-function ace-window "ace-window")
;; (leaf-handler-package ace-window ace-window nil)
;; (leaf-keys
;; (("M-o a w" . ace-window))))
ただ、leafの展開形を知りたいだけなら後述する leaf-expand
の方が使いやすいと思います。
Add leaf expand visualization feature
M-x leaf-expand
と M-x leaf-create-issue-template
を実装しました。
leaf-expand
は現在のポイントから上のS式をスキャンして、一番最初に見つかったleafを macroexpand-1
で展開し、新しいバッファに表示します。
leaf-create-issue-template
はついでに実装したものです。展開前と展開後のleafをmd形式で新しいバッファに表示します。これをコピペすれば簡単にissueが書けるのではないかなと思います。
Add leaf-available-keywords
leaf-available-keywords
を追加しました。
lispプログラムから実行すると、単に現在使用できるキーワードのリストを返し、 M-x leaf-avairable-keywords
と実行するとエコーエリアに表示します。
@@ image @@
あれ、どんなキーワードが使えるんだっけ。ってときに使えるかもしれません。このコマンドを実行する前にドキュメントを見てしまいそうですが。。
Why don't :setq, :custom, etc. distribute when normalizing?
:hook
や :mode
以外のキーワードの分配代入のサポートの提案です。結局、下記のキーワードの全てで分配代入をサポートしました。
:ensure :package
:hook :mode :interpreter :magic :magic-fallback :defun
:pl-setq :pl-pre-setq :pl-setq-default :pl-custom
:auth-custom :auth-pre-setq :auth-setq :auth-setq-default
:setq :pre-setq :setq-default :custom :custom-face
同じ値を複数の変数に設定するときに便利かもしれません。ただあんまり記述量変わらないし、変数名も長いので2行に渡ることもあり、私はあまり使ってません。
ただ、やっぱり :hook
や :mode
では便利。
Automatically require packages feature
- #56 Automatically require packages feature
- #58 Fix auto require feature
- #64 Remove straight from auto require packages
leaf-keywords.elの追加機能です。leaf-keywords.elで使えるパッケージがインストールされていた場合、 (leaf-keywords-setup)
をしたときに自動でrequireするようにしました。
この機能を使うには (leaf-keywords-setup)
をする前にel-get
, hydra
, key-combo
, smartrep
, key-chord
, diminish
, delight
のうちどれかをpackage.elなどでインストールしておく必要があります。
Remove eval-after-load statement
leaf-keywords.elの追加機能です。leaf-keywords.elによって出力されたS式は、追加パッケージがrequireされたときに発火するように eval-after-load
で囲われていました。
しかしこのおせっかい機能によって、 :diminish
キーワードを使用しているのに diminish
をインストールし忘れているという事故が起こっていました。
そこでエイヤッと取っぱらってしまいました。エラーによってきちんとユーザーに報告した方が良いだろうという判断です。なお leaf-protect
で囲われている限り、エラーは起こりますが内部のエラーは全てワーニングに変換されるので、その後のパッケージは読み込もうとします。
修正された機能
Correctly eval cdr
私は「ドット対のリスト」を指定するのが好みで、それをずっと使っていましたが、単に「ドット対」を渡したときに盛大にバグっていたことを教えて頂きました。
-
動く
(leaf some-mode :hook ((t . other-mode)))
-
動かない
(leaf some-mode :hook (t . other-mode))
leafは結構大きなDSL解析器になっていて、デバックも大変になってきています。とりあえず、テストケースは全部通ることを確認しているので、見つけ次第追加していくしかないですね。。
なお、Readmeや私が書くleafの記事で紹介しているものはテストケースから抜き出したものなので、動くことが保証されています。
Fix install code
Readmeにおいて leaf
のダウンロードに失敗したら package-refresh
してもう一度ダウンロードを試すようにしていましたが、通常、leafは一番最初にダウンロードされるだろうということを仮定してインストールコードを簡略化しました。
ついでに leaf-keywords.el
のインストールコードも prog1
を使わない形に変えました。
-
before
(prog1 "prepare leaf" (prog1 "package" (custom-set-variables '(package-archives '(("org" . "https://orgmode.org/elpa/") ("melpa" . "https://melpa.org/packages/") ("gnu" . "https://elpa.gnu.org/packages/")))) (package-initialize)) (prog1 "leaf" (unless (package-installed-p 'leaf) (unless (assoc 'leaf package-archive-contents) (package-refresh-contents)) (condition-case err (package-install 'leaf) (error (package-refresh-contents) ; renew local melpa cache if fail (package-install 'leaf)))) (leaf leaf-keywords :ensure t :config (leaf-keywords-init))) (prog1 "optional packages for leaf-keywords" ;; optional packages if you want to use :hydra, :el-get,,, (leaf hydra :ensure t) (leaf el-get :ensure t :custom ((el-get-git-shallow-clone . t)))))
-
after
(prog1 "leaf" (prog1 "install leaf" (custom-set-variables '(package-archives '(("org" . "https://orgmode.org/elpa/") ("melpa" . "https://melpa.org/packages/") ("gnu" . "https://elpa.gnu.org/packages/")))) (package-initialize) (unless (package-installed-p 'leaf) (package-refresh-contents) (package-install 'leaf))) (leaf leaf-keywords :ensure t :config ;; optional packages if you want to use :hydra, :el-get,,, (leaf hydra :ensure t) (leaf el-get :ensure t :custom ((el-get-git-shallow-clone . t))) ;; initialize leaf-keywords.el (leaf-keywords-init)))
Byte compile
- #326 Suppress byte-compiler warnings, unused lexical variable, err
- #332 Create :leaf-defun, auto declare function feature
- #334 Add :leaf-defvar keyword
- #336 fix :leaf-defvar expand statement
leafを使用したinit.elをバイトコンパイルしたときにワーニングが出たので直しました。バイトコンパイルについては少しコツがあるので、また別記事にするかもしれません。
Do configure autoload if not have already set autoload
二重にautoloadされることを防止するためにautoloadする前に fboundp
するようにしました。実はuse-packageの出力結果では fboundp
してautoloadしていたのですが、そのチェックは本当に必要なんだろうか。と思ってチェックを外していたのです。
しかし、実際のところpackage.elによって設定された正しいautoloadをleafが上書きしていることが分かったので、設定する前にチェックするようにしました。
:hook should not autoload lambda expressions
- #295 :hook cannot handle lambda with multiple expressions
- #296 :hook should not autoload lambda expressions
:hook
でlambda式を指定したときに、複数式だと動かないことと、変なautoloadが生成されていることの報告を受けて直しました。
この変更によって :hook
で正常にlambda式を使用できるようになりました。
なお、 :bind
, :bind*
ではlambda式をバインドすることが現状できないので、 :preface
で関数を宣言してバインドしてもらえればと思います。。
まとめ
leafはIssueやPRを放置することなく、後方互換性を保った上で精力的に機能拡張を行なっています。ぜひこの記事やリリース記事を参考にして、leafで快適なEmacs生活を送って頂ければと思います。
なお、12/18には「Emacs Advent Calendar 2019」の18日目の記事として @keita44_f4 さんによるleafの記事がでるようなので、それも参考にしていただければと思います。
最後になりますが、Patreonでご支援を頂ける方を募集しています。普段はleafなどのElispパッケージなどを中心にOSS活動をしつつ、学生をしています。ぜひよろしくお願いします。