Emacs has had a spell-checker for a very long time. In fact, I’ve been using ispell-word for at least several years now, as I have mentioned for example here. However, I’ve only used Flyspell mode once, over two decades ago, when I tried it out on a machine with maybe 8MB RAM and a Pentium-class processor clocked at about 100MHz and decided that it’s too slow to be really useful.
Well, things have changed a bit since then, haven’t they?
My current laptop has 32GB RAM and a 6-core i7 processor at 2.2GHz. I figured it should be fast enough to run a spell-checker, so I tried to enable flyspell-mode again.
For some reason, it still felt laggy. It may have had something to do with the fact that I ran it on a several-megabytes-large Org mode file. Happily, I remembered reading about Jinx, and I quickly tried it out. After a few minutes I decided to install it and added this to my init.el:
(add-hook 'emacs-startup-hook #'global-jinx-mode) (keymap-global-set "M-$" #'jinx-correct)
The default settings were not enough for me, though. I had quite specific needs. First of all, I needed Jinx to support two languages – English and Polish. That is easy enough:
(setq jinx-languages "en_US pl_PL")
Probably a better solution would be to have some code to detect and set the language of a file when visiting it. That would be a bit more complex, though, and would not help anyway since my personal Org files sometimes have both English and Polish inside.
The next problem was slightly more tricky. I needed Jinx to only use Polish in one specific directory. (I wanted spell-checking to work for my 9-year-old son, who’s just started to learn English, so he only writes in Polish.) Easy enough with per-directory local variables, especially that Emacs has the convenient add-dir-local-variable command. Here are the contents of the .dir-locals.el file in the directory where my son’s files live:
;;; Directory Local Variables -*- no-byte-compile: t -*- ;;; For more information see (info "(emacs) Directory Variables") ((nil . ((jinx-languages . "pl_PL"))))
Of course, having Emacs variable-safety mechanism kick in whenever any file from that directory is visited is no good, either:
(add-to-list 'safe-local-variable-values '(jinx-languages . "pl_PL"))
The next customizations are even more tricky. Jinx’s way of selecting the correct spelling of a word is pretty sophisticated and I like it quite a lot. However, it turns out that Ivy interferes with it. I could write a simple wrapper around jinx-correct, disabling Ivy for a moment, but that would require rebinding M-$ in the jinx-overlay-map keymap (which is used by jinx-overlay). This means that I want jinx-correct itself to disable Ivy. Advice to the rescue:
(defun disable-ivy (orig-fun &rest args)
"Run ORIG-FUN with Ivy disabled."
(interactive)
(let ((completing-read-function #'completing-read-default))
(apply orig-fun args)))
(advice-add 'jinx-correct :around #'disable-ivy)
The next tweak I wanted was related to an excellent article about Avy, another of Oleh Krehel’s incredibly useful packages. It turns out that Avy can be used for a lot more things than just quickly jumping to places, and spell-checking is one of them. You can use Avy to point Emacs to a misspelled word anywhere in the buffer and select the ispell Avy action to correct it without moving point. (Well actually, the point moves internally of course, but the whole action is wrapped in save-excursion so that the user never notices that. Also, it turns out that when the misspelled word is in another buffer, the point is moved to its current position in that buffer. While technically this is probably a bug, I can definitely live with that – correcting a word in another buffer is weird enough a case that I don’t care too much about this behavior.) So, I want that Avy action to use Jinx, too:
(defun avy-action-jinx-correct (pt)
"Use Jinx to correct word at PT."
(save-excursion
(goto-char pt)
(jinx-correct)))
(setf (alist-get ?i avy-dispatch-alist)
#'avy-action-jinx-correct)
Another issue I wanted to change was that I really did not want Jinx to insert its name together with a very lengthy language name (en_US
pl_PL) into my modeline. My normal way of using Emacs is with two windows side-by-side, and too much stuff in the modeline really annoys me, so I added jinx-mode to mode-line-collapse-minor-modes. (I used to use Diminish, but Emacs now has a good replacement. Notice that as of writing this, you need to compile Emacs from sources to have this very new feature!)
The final thing is that I do have some files I don’t want to be spell-checked. As I learned recently, it is enough to say
# Local Variables: # jinx-mode: nil # End:
in such files.
And that’s it for my Jinx config. I have to say that I love it! Apart from being fast, it has quite a few nice features. For example, jinx-correct just corrects the nearest word if the current one is already spelled correctly. (In that case, “nearest” means the nearest one before point, and if all words visible in the buffer before point are spelled correctly, the nearest one after point.)
Happy spell-checking!