Emacs 31 の最初のプリテスト版(=emacs-31.0.90=)が公開された。 正式な 31.1 はまだ出ていないが、機能の中身はすでに固まっている。
そこで自分の設定ファイル(=~/.emacs.d=)を読み返してみたところ、 ある事実に気づいた。
「これ、もう自分で書かなくていいやつだ」というコードが、そこかしこにある。
長く Emacs を使っていると、設定は自然と分厚くなる。 「本体にない機能」を補うために書いたコードが、少しずつ積み上がっていくからだ。 ところが Emacs 本体も進化する。数年前に自分で書いた機能が、 いつのまにか標準機能として取り込まれていることがある。
そうなったとき、自作コードは「便利な資産」から「余計な負債」に変わる。 本体の実装と微妙にケンカしたり、バージョンアップのたびに壊れたりするからだ。
この記事は、そんな「捨てられる自作コード」を Emacs 31 で棚卸ししてみた記録である。
その前に:いま Emacs のバージョンはどうなっているのか#
話を始める前に、事実関係を整理しておく。
| 呼び名 | バージョン | 状態 |
|---|---|---|
| 安定版 | 30.2 | いま普通に使うならこれ |
| Emacs 31 | 31.0.90 | プリテスト(第1版)。正式リリース前 |
| master | 32.0.50 | 開発の最先端。31 の機能をすべて含む |
つまり *2026年7月時点で Emacs 31.1 はまだ正式リリースされていない*。 ただし、master ブランチを自分でビルドしている人なら、 31 の新機能はすべて手元で今すぐ試せる。
以下で紹介する機能は、すべて =etc/NEWS=(Emacs 本体に同梱されている変更履歴)と 実際のソースコードで裏を取ったものだけを挙げている。 「31 で強化されたらしい」という噂レベルの話は入れていない。
捨てられるもの、その1:tree-sitter まわりの自作コード#
これまでの苦労#
tree-sitter は、プログラムの構造を正確に解析して色分け(シンタックスハイライト)や インデントを賢くしてくれる仕組みだ。Emacs 29 から使えるようになった。
ただ、これまでは使い始めるまでが面倒だった。
- 言語ごとに「文法定義(grammar)」を自分でダウンロード・コンパイルする
python-modeをpython-ts-modeに読み替える設定を書く- 起動時に文法定義が揃っているか確認する
この3つを自動化するために、私は 155 行ほどのコードを書いていた。
core-treesit.el というファイルだ。
Emacs 31 での変化#
31 では、この3つが *ほぼ全部、本体の機能になった*。
treesit-enabled-modesをtにすると、 「文法定義があるモードは自動的に ts-mode 版を使う」ようになる。 モードの読み替え表を自分で書く必要がなくなった。treesit-auto-install-grammarを設定すると、 文法定義がないときに「入れますか?」と聞いて自動導入してくれる。 起動時の一括インストール処理も要らなくなった。
結果、155 行あったファイルは *55 行程度まで縮んだ*。 残ったのは「どの言語の文法定義をどこから取ってくるか」という一覧表だけである。
(leaf treesit
:straight nil
:if (and (fboundp 'treesit-available-p) (treesit-available-p))
:custom
;; 文法定義がある ts-mode はすべて有効化する
((treesit-enabled-modes . t)
;; 文法定義がなければ、確認のうえ自動インストールする
(treesit-auto-install-grammar . ask)
(treesit-font-lock-level . 4)))たった数行。100 行が消えた。
ひとつ落とし穴がある#
treesit-enabled-modes が面倒を見てくれるのは、
*Emacs に組み込まれている ts-mode だけ*だ。
サードパーティ製のモード(=json-mode= や toml-mode など)からの読み替えは対象外なので、
そのままだと期待どおりに動かない。
これは「困った問題」ではなく、むしろ整理のチャンスだと考えている。
json-ts-mode や toml-ts-mode は Emacs 本体に入っているのだから、
サードパーティのパッケージのほうを消してしまえばいい。
パッケージが2つ減る。
捨てられるもの、その2:「保存時に行末の空白を消す」設定#
これは Emacs 使いなら誰もが一度は書く設定だと思う。
;; 昔の書き方
(add-hook 'before-save-hook #'delete-trailing-whitespace)「ファイルを保存するとき、行末の余計な空白を自動的に消す」というものだ。 自分のコードをきれいに保つには良い設定である。
ただし、これは事故のもとでもある#
問題は、この設定が 開いたファイルすべてに効いてしまう ことだ。
他人が管理しているリポジトリのファイルを、ちょっと確認するために開いて、
うっかり C-x C-s を押したとする。
すると、行末の空白がある全部の行が「変更あり」と判定されてしまう。
たった1文字直すつもりが、200 行の差分が出る。 プルリクエストを送る前に気づけばいいが、気づかないこともある。 これは典型的な事故パターンだ。
Emacs 31 での解決#
31 は delete-trailing-whitespace-mode という新しいマイナーモードを用意した。
「全ファイルに効くフック」ではなく、
「そのバッファだけに効くモード」として空白削除を有効にできる。
(leaf files
:straight nil
:custom ((require-final-newline . t)
(delete-trailing-lines . t))
;; プログラム・Org・設定ファイルのときだけ有効にする
:hook ((prog-mode-hook . delete-trailing-whitespace-mode)
(org-mode-hook . delete-trailing-whitespace-mode)
(conf-mode-hook . delete-trailing-whitespace-mode)))効く範囲を自分で決められる。これが本来あるべき形だと思う。
捨てられるもの、その3:「定期的に保存する」ための自作タイマー#
Emacs には、最近開いたファイル(=recentf=)や、 ファイル内の最後のカーソル位置(=saveplace=)を記録してくれる機能がある。
ところがこれらは、標準では *Emacs を終了するときにしか保存されない*。 Emacs がクラッシュしたり、強制終了したりすると、記録が失われる。
そこで多くの人が、こういうコードを書く。
;; 5分ごとに保存する自作タイマー
(run-with-timer 300 300 #'recentf-save-list)Emacs 31 は、これを設定項目として用意した。
(leaf recentf :straight nil
:custom ((recentf-autosave-interval . 300))) ; 300秒ごとに自動保存
(leaf saveplace :straight nil
:custom ((save-place-autosave-interval . 300)))ここで大事な注意点がひとつある。
これらの設定は「値をセットする」だけでなく、 「タイマーを仕掛ける」という副作用を持っている。 そのため、=setq= で書いても *効かない*。必ず =setopt=(leaf なら =:custom=)を使うこと。 これは NEWS でもはっきり警告されている。
捨てられるもの、その4:連打を抑える処理#
私の設定には orgx という Org 拡張のレイヤーがあって、
そこで「保存のたびに重い処理が走らないように間引く」コードを3か所書いていた。
たとえば私の設定は README.org という 13,826 行のファイル1本から生成されている。
このファイルを保存するたびに、全体を解析して各設定ファイルを吐き出す(tangle する)。
連続して C-x C-s を押すと、そのたびに 13,826 行の処理が走る。重い。
そこで「最後の保存から2秒経つまで待つ」という制御を自分で書いていた。 プログラミングの世界では「デバウンス」「スロットル」と呼ばれる、よくあるパターンだ。
Emacs 31 は timeout.el というライブラリを標準で同梱するようになった。
(require 'timeout nil t)
;; 「2秒に1回まで」に制限する
(timeout-throttle #'orgx-auto-tangle--tangle 2.0)1行である。
ただし、これで自作コードを全部消していいわけではない。
timeout-throttle は「呼び出し頻度を制限する」だけで、
「すでに走っている処理に二重で入ってくるのを防ぐ」わけではない。
再入防止のフラグは 残しておくべき だ。
こういう「本体機能で置き換えられる範囲と、そうでない範囲」を見極めるのが、 リファクタリングでいちばん頭を使うところである。
ここからは「増やす」話:Emacs 31 の新機能で嬉しかったもの#
捨てる話ばかりしてきたが、31 には積極的に取り入れたい新機能も多い。 特に印象的だったものを挙げる。
Git 操作が本体だけでかなり戦えるようになった#
Emacs で Git を使うなら Magit、というのがここ10年の常識だった。 Emacs 標準の VC は、正直そこまで便利ではなかったからだ。
ところが 31 の NEWS を読むと、VC 関連の変更項目が *40 を超えている*。
- 複数 worktree の操作
- cherry-pick / revert
- incoming / outgoing の差分表示
- コミットの非同期実行(大きなリポジトリで待たされない)
- 外部で git 操作したときのバッファ自動更新
とりあえず、事故防止に効くものだけでも入れておく価値がある。
(leaf vc
:straight nil
:custom
(;; 公開済みの履歴を書き換えようとしたら止める(force push 事故の防止)
(vc-allow-rewriting-published-history . nil)
;; コミットを非同期にする
(vc-async-checkin . t))
:config
(vc-auto-revert-mode 1))「じゃあ Magit を消せるか」というと、そこは急がないほうがいい。 私は まず VC 側を有効にして、1〜2か月ふつうに使ってみてから決める つもりだ。 道具を捨てる判断は、実際に使ってみた実感でしか下せない。
ターミナルの Emacs でもポップアップが出る#
これは地味だが大きい。
31 では、ターミナル(TTY)上の Emacs でも「子フレーム」が使えるようになった。
これまで GUI 版でしか出せなかった補完のポップアップ(=corfu= など)が、
emacs -nw でも動く可能性がある。
一方で、同じ流れで ターミナルのマウス操作モードが標準で有効になる 変更も入った。 これは iTerm2 や Ghostty で「ドラッグして選択してコピー」ができなくなる、 という体感の変化を生む。
ターミナルで Emacs を使う人は、方針を決めておいたほうがいい。
;; 端末側のコピー&ペーストを優先したい場合
(unless (display-graphic-p) (xterm-mouse-mode -1))macOS 向けの新機能がふたつ#
私は Mac で Emacs を使っているので、これは嬉しかった。
system-taskbar-mode— Dock アイコンにバッジや進捗バーを出せるsystem-sleep— 長時間処理の最中にスリープするのを防げる
たとえば「Emacs 本体を自分でビルドしている最中に Mac が寝てしまう」という 地味な困りごとが、これで解決する。
(when (and (eq system-type 'darwin) (fboundp 'system-taskbar-mode))
(system-taskbar-mode 1))さらに native-comp-async-on-battery-power という設定が増えた。
Emacs は裏でコードを機械語にコンパイルして高速化する仕組み(native-comp)を持っているが、
これがノートパソコンのバッテリーをけっこう食う。
バッテリー駆動中はコンパイルを止める、という指定ができるようになった。
(setopt native-comp-async-on-battery-power nil)コードの折りたたみが実用レベルになった#
Emacs には昔から hideshow という「関数を折りたたむ」機能があるのだが、
正直あまり使い勝手が良くなく、多くの人はサードパーティ製で代替していた。
31 でこれが一気に強化された。 折りたたみの循環(親→入れ子→全展開)、余白への目印表示、 隠れている行数の表示など、必要なものが一通り揃った。
(leaf hideshow
:straight nil
:hook ((prog-mode-hook . hs-minor-mode))
:custom
((hs-show-indicators . t) ; 折りたたみ位置に目印を出す
(hs-display-lines-hidden . t)) ; 何行隠れているか表示する
:bind ((:hs-minor-mode-map
("<backtab>" . hs-cycle))))移行するときに気をつけること#
新しいバージョンに上げるとき、いちばん怖いのは「気づかないうちに壊れている」ことだ。 31 の NEWS には「互換性のない変更」という節があり、 そこから自分の設定に影響しそうなものを拾っておいた。
| 変更点 | どう困るか |
|---|---|
if-let / when-let が非推奨に | 末尾に * が付いた形(=if-let*=)に書き換えが必要 |
| ts-mode のインデント設定の変数名が変わった | 独自に設定していると効かなくなる |
| Pcase の入れ子バッククォートが使えない | pcase を凝った使い方をしていると壊れる |
| 文字列の書き換え制限が厳しくなった | リテラル文字列を破壊的に変更するコードがエラーに |
| Org 9.8 が同梱される | 自分で Org のバージョンを固定していると二重管理になる |
site-start.el の読み込み順が変わった | Homebrew などが置いたファイルの挙動が変わりうる |
これらは、いきなり本番の設定に適用してハマるのではなく、 ブランチを切って、ひとつずつ試す のが確実だ。
私の場合はこういう手順で進めている。
git switch -c feat/emacs31でブランチを切る- 「自作コードを消す」系(tree-sitter、空白削除、タイマー)を先に適用する
- 消した関数がどこかから呼ばれていないか grep で確認する
- リスクの低い小さな設定をまとめて適用する
- 大きめの新機能は 1項目ずつ 入れて、そのつど起動時間とメモリを測る
- 実測してから、不要になったパッケージを消す判断をする
特に 5 が大事だと思っている。 「新機能を全部入れたら遅くなった」となったとき、 まとめて適用していると、どれが原因かわからなくなる。
結局、何が得られるのか#
数字で言えば、こうなる。
| 項目 | 現状 | 移行後 |
|---|---|---|
| tree-sitter 設定 | 約155行 | 約55行 |
| 自作タイマー | 手書き | 設定項目1つ |
| 自作デバウンス | 3か所 | ライブラリ呼び出し |
| 削除できるパッケージ | — | 最大5個 |
ただ、正直なところ *行数が減ること自体はどうでもいい*。
本当に意味があるのは、 「自分が保守しなければならないコード」が 「Emacs 本体が動作を保証してくれるコード」に置き換わったことだ。
自作コードは、Emacs がバージョンアップするたびに壊れる可能性がある。 本体の機能は、そう簡単には壊れない(壊れるときは NEWS に書いてある)。
設定を育てるというのは、コードを足していくことだと思われがちだが、 実際には 「本体が追いついたら、自分のコードを引っ込める」 という 引き算の作業も同じくらい重要なのだと、今回あらためて感じた。
おまけ:自分で確かめる方法#
この記事の内容は、すべて Emacs 本体のソースと NEWS で確認できる。 噂や又聞きではなく、一次情報にあたる方法を書いておく。
# Emacs の中から NEWS を読む
C-h n # view-emacs-news
C-u C-h n # バージョンを指定して絞り込む
# ソースから直接確認する
git -C ~/src/emacs show emacs-31:etc/NEWS | less
git -C ~/src/emacs grep -n "defcustom treesit-enabled-modes" emacs-31 -- lisp/treesit.el新機能の正確な引数や挙動は、=C-h f= で関数のドキュメントを引くのがいちばん早い。 ブログ記事(この記事も含めて)を鵜呑みにせず、手元で確かめてほしい。



