Emacs: emacs 28.1

emacs 28.1からelispのネイティブコンパイルが導入されたが、それ以外にもいろいろと変更があるようで、.emacsがエラーとなるようになった。そこで久しぶりに.emacs.dを大幅に修正したのでメモを残す。

1. elpa-stableよりもelpaを使う

  • elpa-stableは安定バージョンというわけではないようで、emacsのどのバージョン向けなのかよくわからない。
  • 2022年現在、elpaのパッケージはemacs 27とemacs 28向けのようだ。

2. .emacsの代わりに.emacs.d/init.elを使う

  • .emacs.d配下の方がgitで管理しやすい。
  • 自分は自作のelispは.emacs.d/myelに置いている。

3. linumの代わりにdisplay-line-numbersを使う

  • wb-line-numberからlinumに乗り換えたら軽くなったので軽いイメージがあったが、 emacs 28.1だと異常に重くなった(数万行のソースコードの末尾から先頭に移動すると固まる)。
  • 代わりにdisplay-line-numbersを使う。linum-modeの代わりにdisplay-line-numbers を呼び出す。
(display-line-numbers-width-start t)

4. lsp-modeを導入する

  • 今までは各種プログラミング言語の補完にそれぞれ別のelispパッケージをインストールしていた。LSP(Language Server Protocol)を利用することで、クライアント側のemacsはLSPクライアントのみを設定すれば良い。
  • LSPクライアントの実装がlsp-modeとeglotだ。lsp-ui-modeがあるので、自分は lsp-modeを選択した。各種ファイルのhookでlsp-modeとlsp-ui-modeを有効にする。
  • LSPサーバとしてC/C++ならclangd、Javaならeclipse.jdt.ls、シェルスクリプトなら bash-language-server、Pythonならpython-ls-server等がある。システムのパッケージャ、npm、pip、あるいはlsp-mode初回起動時にインストールできる。

5. シンボル定義・参照の閲覧用にGNU Globalの代わりにxrefを使う

  • コード補完と同様に、LSPサーバにシンボル定義・参照の閲覧機能をリクエストする。
  • clangdの場合はcompile_commands.jsonを作成する必要がある。コンパイラと同等のシンボルの追跡が可能となる。cmakeの場合は-DCMAKE_EXPORT_COMPILE_COMMANDS=ON を指定する。makeの場合はbearを噛ませてmakeを実行する。
  • lsp-ui-modeとの連携でシンボル参照のリストが同じウィンドウ上にポップアップされる。

6. helmをやめる

  • コード補完の表示はcompanyに任せる。
  • コード補完以外の補完はミニバッフ用の補完パッケージを利用する。

6.1. ミニバッファ用の補完パッケージを導入する

  • helm-find-fileではなく、ミニバッファ用の補完パッケージをインストールして find-fileを使う。
  • ミニバッファ用のパッケージとしてverticoがある。
  • emacs 28.1からfido-vertical-modeを利用できる。ただし、ファイル名にマッチする候補がある場合に新規ファイルを作成できない。
  • 以下はverticoで補完方法をsubstringで指定している。さらにorderlessを導入すると快適な補完が可能になる(以下のsubstringをorderlessに置き換える)。
(vertico-mode)

(setq completion-style '(substring)
      completion-category-overrides
      '((file (styles . (substring)))
        (buffer (styles . (substring)))
        (info-menu (styles . (substring)))))

6.2. ibufferでバッファのリストを表示する

  • バッファのリストを表示するのに、helm-miniではなく、ibufferを使う。ibufferはフィルタリング機能が充実して上、バッファのカテゴライズが可能である。
(global-set-key "\C-x\C-b" 'ibuffer)

(setq ibuffer-saved-filter-groups
      (quote (("default"
               ("emacs" (or (name . "^\\*scratch\\*$")
                            (mode . messages-buffer-mode)
                            (mode . compilation-mode)
                            (mode . Buffer-menu-mode)
                            (name . "^\\*Find\\*$")
                            (mode . grep-mode)
                            (mode . Info-mode)
                            (mode . Man-mode)
                            (mode . help-mode)
                            (derived-mode . completion-list-mode)
                            (derived-mode . comint-mode)
                            (derived-mode . gud-mode)
                            (derived-mode . debugger-mode)
                            (derived-mode . backtrace-mode)
                            (derived-mode . gdb-parent-mode)))
               ("lsp" (or (name . "^\\*lsp-log\\*$")
                          (name . "^\\*bash-ls\\*$")
                          (name . "^\\*bash-ls::stderr\\*$")
                          (name . "^\\*clangd\\*$")
                          (name . "^\\*clangd::stderr\\*$")
                          (name . "^\\*jdtls\\*$")
                          (name . "^\\*jdtls::stderr\\*$")
                          (name . "^\\*pylsp\\*$")
                          (name . "^\\*pylsp::stderr\\*$")))
               ("magit" (or (derived-mode . magit-mode)
                            (mode . ediff-mode)
                            (mode . ediff-meta-mode)
                            (name . "^\\*ediff-errors\\*$")
                            (name . "^\\*ediff-diff\\*$")))
               ("term" (mode . term-mode))
               ("dired" (mode . dired-mode))))))

(add-hook 'ibuffer-mode-hook
          (lambda ()
            (ibuffer-switch-to-saved-filter-groups "default")))

6.3. helm関数をcompleting-read関数に置き換える

  • 自作のelispでhelm関数を使っている場合はcompleting-read関数に変更する。 completing-read関数はミニバッファ補完用パッケージ対象となる。
  • 以下はansi-term起動時に既存のansi-termを一覧表示する自作のelispで、helm関数を用いていたところをcompleting-read関数に変更したもの。
                    (when (string-prefix-p "\*ansi-term\*" (buffer-name buffer))
         (add-to-list 'buffers (buffer-name buffer))))
     (setq buffers (append buffers (list "new")))
-    (unless (null buffers)
-      (helm :sources
-            '((name . "List of *ansi-term*")
-              (candidates . buffers)
-              (action . (lambda(buffer)
-                          (if (string= buffer "new")
-                              (ansi-term "/bin/bash")
-                            (switch-to-buffer buffer)))))))))
+    (let ((buffer (completing-read "ansi-term: " buffers)))
+      (if (string= buffer "new")
+          (ansi-term "/bin/bash")
+        (switch-to-buffer buffer)))))

 (global-set-key "\C-x\C-g\C-t" 'myel-ansi-term)
 (global-set-key "\C-c\C-\M-j" 'term-line-mode)