GNU Emacs 27.2 for Windows で Emacs に入門したときの個人的な備忘録 (2)

の続きです。

leaf に入門した

`package-install-selected-packages` でパッケージをインストールし、各パッケージの設定を setq でずらーっと書くようにしておりましたが、 leaf で管理をするのが綺麗になると実感できましたので、そっちに移行しました。

各パッケージの設定も標準化できました。例えば imenu 関連のパッケージインストール・設定は以下のように記述ができます。沢山のパッケージを利用しているとき、かなり「しゅっとしている」のが実感できます。

(leaf imenu
  :tag "builtin"
  :custom
  (imenu-auto-rescan . t))

(leaf imenu-list
  :ensure t
  :custom
  ((imenu-list-auto-resize . nil)
   (imenu-list-focus-after-activation . nil)
   (imenu-list-position . 'left))
  :hook
  (imenu-list-major-mode-hook . (lambda ()
				  (setq mode-line-format nil)
				  (display-line-numbers-mode 0))))

また custom-file を別ファイルに逃がすことによって、 emacs が勝手に追加・修正をした custom-set-variables や custom-set-faces が意図せずに汚されているということがなく、 git の add, commit の時に不機嫌になる、ということもなくなりました。

;; bye bye custom-set-variables and custom-set-faces settings
(setq-default custom-file (concat user-emacs-directory "/customs.el"))

いらなくなったパッケージは init.el の leaf を消してしまえばいいですし、パッケージの管理は Emacs 本体の操作を必要としなくなりました。

undo/redo 改善

サクラエディタなどよくある Windows のエディタの場合 undo/redo を行う場合は一文字ずつ、もしくは選択して消去した単位など「そのキーを押す一つ前」の状態に戻る、という動作をするのが多いかと思います。
一方 emacs は 1 つのコマンド、もしくは複数のコマンドの集りである「融合 (amalgamating) コマンド」となります。文字を一つキーの直後に挿入する、つまり文字入力も一つのコマンド self-insert-command となり、 self-insert-command によるキー入力は「融合 (amalgamating) コマンド」となります。「融合 (amalgamating) コマンド」がどれだけの集まりになるかというのはデフォルトでは 20 となります。

つまり、文字入力は何か他のコマンドが実行されるまで 20 文字で一つの集まりとなり、 undo は最大 20 文字ずつということになります。それだと細かく undo をしたいとき不便ですので、多少 undo のキー入力回数が多くなったとしても、任意でコントロールできたほうが嬉しいと思います。なので、以下を設定することで 1 文字ずつ undo できるようにしました。

;; undo/redo for 1 character
(advice-add 'undo-auto--last-boundary-amalgamating-number
	    :override #'ignore)

また redo は undo-tree の undo-tree-redo を使うようにしました。所謂 Windows のエディタでいうところの C-y のような普通の redo ができるようになります。

(leaf undo-tree
  :tag "core"
  :ensure t
  :blackout t
  :custom (global-undo-tree-mode . t)
  :bind (
	 ("C-S-z" . undo-tree-redo)
	 ("M-/" . undo-tree-redo)))

ずらっと setq が並んでいると、どれがどんな設定か分からなくなってきますが、leaf にすると、こんな感じで各設定ごとさっぱりと記載ができます。

デフォルトの `messages` バッファを消す

基本使わないので停止。

;; disable *messages*
(setq-default message-log-max nil)
(kill-buffer "*Messages*")

*scratch* に何か書いてある場合は C-x C-c ですぐに終了させない

*scratch* バッファに後で保存する前提でメモを書いておいて、それを忘れて C-x C-c をして絶望する、ということを 5 回以上はしたと思います。というのも、私自身 Windows 出身で、 Windows のエディタはコマンドラインからファイルを作成しそれを開く、という動作ではなく、アプリケーションを起動しアプリケーション上からファイルを保存する、という文化であることが大抵かと思います。
Stackoverflow で *scratch* バッファを保存し忘れるから何とかしたい、という投稿も「scratch は勝手に消えてもいいようはメモなどのを書くべき。最初にファイルを作成し開いていないほうが悪い。文化の違いだ」というコメントがありました。それもそうなんだろうな、と思いましたが、やはり癖は変えることはできそうにないので *scratch* に何かが書いてある場合は、他のバッファ同様、削除時に警告を出すようにしてもらいました。

;; ask me saving buffer anyway
(defun my--func-scratch-buffer ()
    (with-current-buffer "*scratch*"
      (put 'buffer-offer-save 'permanent-local t)
      (setq buffer-offer-save t)))
(add-hook 'after-init-hook 'my--func-scratch-buffer)

*scratch* には buffer-offer-save が nil のため、それを起動時に t にする関数を hook に追加してます。

他には persistent-scratch のパッケージなども使用してみましたが、 Emacs を server-start で動かしているからか上手く動作しませんでした。

Emacs を起動すると server-start を実行し、他に server がある場合、別の名前で server を起動する

https://emacs.stackexchange.com/a/33584 を参考にしました。オリジナルの関数から少々手を加えて server の名前を好みに変更してます。

;; server-start automatically
;; https://emacs.stackexchange.com/a/33584
(require 'server)
(defun my--server-start ()
  (let ((server-num 0))
    (if (not (server-running-p))
	(setq server-name (concat "server-" (number-to-string server-num))))
    (while (server-running-p (unless (eq server-num 0) (concat "server-" (number-to-string server-num))))
      (setq server-num (+ server-num 1)))
    (unless (eq server-num 0)
      (setq server-name (concat "server-" (number-to-string server-num))))
    (server-start)))
(my--server-start)

runemacs から複数の Emacs を立ち上げ、各ウインドウごとで色々なファイルを見る、ということをすることが多いので便利になりました。

タイトルバーに server の名前を表示させる +α

ファイル名他、ファイルの状態なども表示させるようにしました。 GUI 版の Emacs を使用しているときは便利です。

(setq-default frame-title-format '("emacs * [" server-name "] " mode-line-modified mode-line-remote " %b (%m)")))

関連付けなどからファイルを Emacs に開かせるとき、複数の Emacs から一番上のウインドウの Emacs を選び、それが開く

めっちゃ長いんで gist です。

server が起動した状態の Emacs が複数ある場合にファイルを開かせたいとします。その場合、任意の Emacs のウインドウを指定しファイルを開かせたい場合、ちょっと手間かと思います。

  • ファイルパスをクリップボードにコピーして emacs の `C-x C-f` してペースト

  • もしくは、ファイルを emacs にファイルをドラッグアンドドロップ

スマートにやりたかったら、関連付けなどから任意の Emacs で開きたいですよね。それを簡単に実現するのが上の Powershell スクリプトです。
ちょっと無理やりなことをしてますが、複数の Emacs の server ファイルの PID を割り出し、それぞれのウインドウの重なりを確認、そして一番の上のウインドウの Emacs がファイルを開く、という挙動をしてくれます。xyzzy というエディタの xyzzycli.exe が同じ挙動をします。

開きたい Emacs を一旦アクティブウインドウにして、その後関連付けなどから実行し 1 秒から 2 秒程度待てば(残念ながら、どうしても Powershell のウインドウが表示されます)目的の emacs で開くことができます。
関連付けは以下のように設定すればよいです。

powershell -nologo -NonInteractive -ExecutionPolicy Unrestricted "C:\Path\To\openemacs.ps1" "%1"

Powershell から実行している C# のソースコードは以下の Stackoverflow のコメントを参考にしました。

どうもスマートではありませんが、止むを得ないですね。

ivy, swiper, counsel から vertico, consult に移行

leaf に全面移行した際、大変勉強になりました以下リンクを参考にミニバッファ補完系のパッケージも変更しました。

Emacsの次世代ミニバッファ補完UI
https://blog.tomoya.dev/posts/a-new-wave-has-arrived-at-emacs/

counsel などの挙動に不満はありませんでしたが、まあ新しいパッケージを使ってみようと思った次第です。

(leaf vertico
  :tag "minibuffer"
  :ensure t
  :bind
  (:vertico-map
   ("C-r" . vertico-previous)
   ("C-s" . vertico-next))
  :custom (vertico-count . 10)
  :hook (after-init-hook . vertico-mode))

(leaf consult
  :tag "completion"
  :ensure t
  :package t
  :bind
  (
   ("C-s" . consult-line)
   ("C-x C-b" . switch-to-buffer)))

(leaf orderless
  :tag "completion"
  :ensure t
  :custom
  `((completion-styles . '(orderless))
    (orderless-matching-styles
     . '(
	 ; orderless-prefixes
         ; orderless-flex
         orderless-regexp
         ;; orderless-initialism
	 ;; orderless-literal
	 ))))

(leaf marginalia
  :tag "minibuffer"
  :ensure t
  :hook
  (after-init-hook . marginalia-mode))

orderless は正規表現ありモード以外使わないな、と思ったので、有効にしているのはこれだけです。
counsel もそうですが、このようなミニバッファ補完・検索は非常に性に合っておりまして、ここ数年で一番衝撃を感じております。なんでもかんでも検索できて、検索方法も高度(スペース区切りで or 検索 + 正規表現、は本当非常に便利)。ウインドウを分割して作るのではなくミニバッファを利用するというシームレスさも快適です。

一方 40MB くらいのテキストファイルを consult-line すると、タスクマネージャで見たとき Emacs が 2GB くらいメモリを食っているときがあり、やや挙動に不安があるときがあります。バッファを kill してもメモリ使用量は減っていないのが不思議です。やはり server だからでしょうか……。特に挙動の追求はしてませんが、ファイルサイズに気をつけながら使ってます。

whitespace で改行を分かりやすくする文字の指定

ルーン文字の「」を指定すると良い感じになりますよ、ということをお伝えしたいです。ユニコードの符号位置では「16CE」の文字です。 utf8 が収録されている大抵のフォントに入っているかと思います。ヒラギノでも大丈夫でした。
https://util.unicode.org/UnicodeJsps/character.jsp?a=16CE

(leaf whitespace
  :tag "builtin"
  :ensure t
  :blackout
  (global-whitespace-mode)
  (whitespace-mode)
  :when window-system
  :hook (after-init-hook . global-whitespace-mode)
  :custom
  (whitespace-style
   . '(face
       newline
       newline-mark))
  (whitespace-display-mappings
   . '((newline-mark ?\n   [?\u16CE ?\n])))
  :custom-face
  (whitespace-newline . '((t :foreground "brown"))))
小さな下向きの矢印

個人的に一番しっくり来ました。

改行したときに行末の半角スペースを勝手に削除しない

Emacs では electric というパッケージが行末の半角スペースを自動で削除してくれます。
プログラムを書いているときには便利ですが `text-mode` ではオフにしました。わざと半角スペースを入れている場合もあり、また `text-mode` ではできる限り自動で何かをやって欲しくはないため。

(leaf electric
  :tag "core" "builtin"
  :ensure t
  :hook (text-hook . (lambda () (electric-indent-local-mode -1))))

leaf ではこのように :hook を増やしていけば、他のモードにも対応できます。

困っていること

  • Mac で EMP Emacs + AquaSKK の場合 `C-h` で文字を DEL したときなど、 AquaSKK のモードが ASCII に変わってしまって辛い

    • ことえりなどの場合はよしなにやってくれるようだが AquaSKK は対応していない

    • tr-ime もそうだけど Emacs 本体側で IME の考慮をして欲しい

  • cua-mode と tr-ime 使用時、高速でセレクションやマークを `C-c` でコピーしようとしたりすると、偶にコピーに失敗していることがある。

    • cua-mode に依存せず、 `M-w` などを使用するように癖を変えている。

  • lsp-mode を導入するか悩んでいる

老後、奥さんと世界一周のための費用にします。