Goodbye LanguageTool, Hello Harper
I found out about Harper the other day. Ever since I heard about it, I've been itching to set it up in my config and get rid of the horrible LanguageTool setup I had going. Now that I've got it working, I've gotta say I'm quite impressed. My previous LanguageTool setup was not smooth at all, and definitely not as fast as Harper is. I'm not sure how anyone else is doing spellchecking in Emacs (and particularly in org-mode
) at the moment, but I recommend anyone using Emacs for most of their writing to give it a shot.
I won't embarrass myself with a comparison of my LanguageTool config. Just know that it was slow and terrible. Copied from the Harper docs, this is what my config looks like:
(when (and (not (equal system-type 'windows-nt)) (locate-file "harper-ls" exec-path)) (with-eval-after-load 'eglot (add-to-list 'eglot-server-programs '(org-mode . ("harper-ls" "--stdio")))) (setq-default eglot-workspace-configuration '(:harper-ls (:dialect "Australian"))) (add-hook 'org-mode-hook 'eglot-ensure))
Some caveats: it doesn't look like Harper has support for org-mode
specifically yet, but it seems to generally work OK despite that. I have however noticed that it doesn't lint property tags, so for example your #+title:
won't be spellchecked. Another minorly annoying little niggle is that straight seems to misbehave with eglot, or eldoc more specifically, printing eldoc error: (invalid-function incf)
in the minibuffer instead of an actual error message. I was using lsp-mode
before this - although I'm beginning to like the eglot interface more and more as I write this, so I think I may swap over to it where possible - so I never realised this issue. Per that GitHub issue link, (use-package eldoc :straight (:type built-in))
seems to be the solution.
The only thing that's missing, and to be fair LanguageTool doesn't implement this either (in fact to my knowledge nothing implements this, I don't even think there's a Word plugin), is text style metrics analysis. Every so often I get the impulse to try to untangle what the source code for that website is doing, and then write a simple elisp wrapper for it, but I always get sidetracked before I start. Maybe now is the time.
Addendum
I've discovered another few quirks in setting this up (mostly to do with eglot and company), so I figured I'd go back to update this post and catalogue what I've had to do for future reference.
eglot overwrites company-backends
by default. This can be disabled with (setq eglot-stay-out-of '(company))
. Figuring this out also convinced me to finally fix company-ispell
, which I could not get working last time I tried.
To get it working with both Ispell and flyspell it seems to need to following code-block:
(if (file-exists-p "/usr/bin/hunspell") (progn (eval-after-load "ispell" '(progn (setq ispell-program-name "hunspell" ispell-dictionary "en_AU" ispell-alternate-dictionary (file-truename (concat user-emacs-directory "en_AU.dict")) ispell-local-dictionary-alist '(("en_AU" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "en_AU,en_AU-med") nil utf-8))) (defun ispell-get-coding-system () 'utf-8))) (eval-after-load "flyspell" '(progn (setq ispell-program-name "hunspell" ispell-dictionary "en_AU" ispell-alternate-dictionary (file-truename (concat user-emacs-directory "en_AU.dict")) ispell-local-dictionary-alist '(("en_AU" "[[:alpha:]]" "[^[:alpha:]]" "[']" nil ("-d" "en_AU,en_AU-med") nil utf-8))) (defun ispell-get-coding-system () 'utf-8)))))
I'm using Hunspell here - if you don't also eval-after-load
for flyspell, then ispell-program-name
gets overwritten (in my case, to enchant-2
, which wasn't working). The alternate dictionary needs to be created, Hunspell doesn't ship a plaintext dictionary. You can make it by going to /usr/share/hunspell/
and running unmunch en_AU.dic en_AU.aff >> /~//.emacs.d/en_AU.dict
. You'll notice that I'm using file-truename
to turn that user Emacs directory into an absolute path - for some reason, it wasn't working with just the relative path.