プログラムを書いているさい、スペルミスで恥をかいたことがある人は多いと思います。 特に日本人の方は普段馴染ないため、違和感に気付きにくかったりします。
emacs ではデフォルトでスペルチェックを行なう機能が搭載されていますが、プログラムのように実際の単語ではない単語や書き方が多いと大量に余計なものにヒットしてしまい鬱陶しいです。
そこで、go の linter として使ったりもする、misspell という Go のツールを使用していい感じにできないかを考えました。
emacs で misspell の使用
探したら、すでにやっている人がいました。考えることは一緒ですね。
https://syohex.hatenablog.com/entry/2016/02/15/105101
ただ、これだとバッファが立ち上がってうざいので、minibuffer に結果が出るようにして最初は運用していました。 また、コードを書いている最中に何度もコマンドを実行して確認するのも面倒なので、save-hook に入れて楽をしようと考えました。
(defun run-lint ()
(interactive)
(let ((current (file-name-nondirectory (buffer-file-name))))
(let ((err (with-temp-buffer
(call-process "misspell" nil (current-buffer) nil current)
(buffer-string))))
(message (string-trim err)))))
(add-hook 'after-save-hook 'run-lint)
しかし、interactive に呼ぶときはいいのですが、save-hook で呼ばれるときは、体感一瞬しか表示されないので、すごい見辛く感じました。
間違えている箇所のハイライト
そこで、今度は間違えている所をハイライトして、目視で確認しやすくしようと考えました。 以下のようなハイライトコードを用意します。
(defun highlight-word (line col)
"Highlight Word"
(interactive)
(save-excursion
(goto-line line)
(let* ((begin (progn (forward-char col) (point)))
(end (progn (forward-word) (point)))
(overlay-highlight (make-overlay begin end)))
(overlay-put overlay-highlight 'face '(:background "OrangeRed4"))
(overlay-put overlay-highlight 'line-highlight-overlay-marker t))))
(defun remove-all-highlight ()
"remove all line highlight"
(interactive)
(remove-overlays (point-min) (point-max)))
次に misspell から行と列の情報を抜き出して上記の関数に食わせます。これをフックさせる感じです。
(defun check-highlight ()
"check and highlight misspell"
(interactive)
(let ((err (run-lint "misspell")))
(cl-loop for x in (split-string err "\n")
collect (let ((splits (split-string x ":")))
(when (< 2 (length splits))
(let* ((line (string-to-number (cadr splits)))
(col (string-to-number (caddr splits))))
(highlight-word line col)))))))
(add-hook 'before-save-hook 'remove-all-highlight)
(add-hook 'after-save-hook 'check-highlight)
動作イメージとしては以下のような感じです。
エピローグ
せっかく作ったけどそんなタイポしないやろと思ってましたが、数日で結構発火してかなりの恩恵を受けています。
追記(というか後で知った)
emacs のスペルチェックでプログラムのコメントにだけ反応させる方法がありました…。以下のように書けばいいみたいです。悲しいなぁ 😢
(use-package flyspell
:hook
(prog-mode . flyspell-prog-mode))