reddit / emacs
Aspell, flyspell, and 'smart' quotes
graduale · 2022-02-15 23:10
(**Edit**: fixed unfortunate typo, pointed out by u/yurikhan...)
I'm trying to get `flyspell` to play nice with `electric-quote-mode`. Basically, especially in `org-mode`, I want to be able to use 'smart' quotes without each instance of e.g. `isn’t` being marked as a spelling error.
[Aspell only seems to like straight quotes](https://preview.redd.it/bp5jy0eow2i81.png?width=898&format=png&auto=webp&s=b9b917a72debcdd52034c319b1d157df8b350cb6)
I've looked around the customization options for `flyspell` but haven't found anything that looks relevant. And it occurred to me that I cannot be the only person dealing with this problem. So I come here asking for help. Anyone?
(I found [a post](http://endlessparentheses.com/ispell-and-apostrophes.html) by Artur Malabarba offering a solution to this problem but I'm wondering if others have other fixes.)
yurikhan · 2022-02-16 07:49
But `isn‘t` _is_ a spelling error. The correct spelling is `isn’t`.
(FWIW I agree with your main point, and I’d like straight quotes to be marked as spelling errors as well.)
redguardtoo · 2022-02-16 13:22
The easiest way is to add `shouldn` into "~/.aspell.en.pws"
Another solution is to set the varialbe `flyspell-generic-check-word-predicate`.
graduale · 2022-02-16 17:15
Oops. Unfortunate error indeed…
graduale · 2022-02-16 17:23
(I never get the keyboard shortcuts for inserting non-straight quotes on a Mac right… thank goodness for `electric-quote-mode` )
graduale · 2022-02-16 17:35
But wouldn't this require manually adding each possible contraction, possessive, etc. to the dictionary? If so, it's not very practical for my purposes. (From what I can tell setting `flyspell-generic-check-word-predicate` would involve basically the same thing, but I'm not super clear on how it works from just looking at the docstring.)
Illiamen · 2022-02-16 19:42
I think you can do this by customizing `ispell-local-dictionaries-alist`. The variable is a list of dictionaries and their settings.
See the fourth element of the entry for each dictionary.
graduale · 2022-02-16 20:58
Oh, interesting. The solution by Artur Malabarba that I mentioned in the post involves setting that variable, but somehow does more than that:
````
(setq ispell-local-dictionary-alist
`((nil "[[:alpha:]]" "[^[:alpha:]]"
"['\x2019]" nil ("-B") nil utf-8)))
;;; Don't send ’ to the subprocess.
(defun endless/replace-apostrophe (args)
(cons (replace-regexp-in-string
"’" "'" (car args))
(cdr args)))
(advice-add #'ispell-send-string :filter-args
#'endless/replace-apostrophe)
;;; Convert ' back to ’ from the subprocess.
(defun endless/replace-quote (args)
(if (not (derived-mode-p 'org-mode))
args
(cons (replace-regexp-in-string
"'" "’" (car args))
(cdr args))))
(advice-add #'ispell-parse-output :filter-args
#'endless/replace-quote)
````
I started out by blindly copying and pasting from that blog post and got to what seemed like a solution, but didn't quite see what the two bits of advice were doing. Now that I read the docstring for `ispell-local-dictionary-alist` (as I should have!) and your comment, I'm trying to just set the value of `ispell-local-dictionary-alist` to all for `’` as part of words, and it looks like this works!
redguardtoo · 2022-02-16 22:26
The predicate is a function without parameters.
Inside the funciton,
- current buffer is the buffer being spell checked
- the word at point is the typo candidate. If the predicate returns boolean value `t`, the candidate will be highlighted as typo
The complexity of predicate is many major modes pre-configures `flyspell-generic-check-word-predicate`, so maybe you can't change it directly. You might need study the major mode's own predicate instead.
graduale · 2022-02-17 07:06
For those following at home, it turns out (unsurprisingly) the two bits of advice actually do something—they allow you to correct a word using flyspell without having the non-straight apostrophe being replaced with a straight one.
graduale · 2022-02-18 05:48
Thanks, I’ll have a look!
TheSitarHero · 2022-03-03 16:59
Having this same problem - what does your init.el/`ispell-local-dictionary-alist` look like now?
As far as I can tell, my `ispell-local-dictionary-alist` already has the right symbol in the right place but clearly something is still wrong.
graduale · 2022-03-03 18:05
((nil "[[:alpha:]]" "[^[:alpha:]]" "['’]" nil
("-B")
nil utf-8))
Why is something still wrong?
TheSitarHero · 2022-03-03 18:46
So did you get rid of the other bits of advice?
I was still getting lines under correct words with apostrophes.
I ended up fixing it by changing my spellchecking program to hunspell, following the instructions on [this page](https://www.emacswiki.org/emacs/InteractiveSpell). Converting the hunspell dicts to utf-8 and adding the lines:
`ICONV 1`
`ICONV ’ '`
`WORDCHARS 0123456789'’`
to the .aff file fixed it.
In my init.el:
`(add-to-list 'ispell-local-dictionary-alist '("english-hunspell"`
`"[[:alpha:]]"`
`"[^[:alpha:]]"`
`"[']"`
`t`
`("-d" "en_GB")`
`nil`
`utf-8))`
graduale · 2022-03-03 19:07
Weird, I just used the solution from Malabarba's post and it works great. My config:
(use-feature flyspell
:ensure-system-package aspell
:bind
("C-c e s" . my/flyspell-save-word)
:custom
(ispell-program-name "aspell")
(flyspell-use-meta-tab nil)
:init
(add-hook 'text-mode-hook 'flyspell-mode)
(add-hook 'prog-mode-hook 'flyspell-prog-mode)
:config
;; Tell ispell.el that ’ can be part of a word, from http://endless parentheses.com/ispell-and-apostrophes.html
(setq ispell-local-dictionary-alist
`((nil "[[:alpha:]]" "[^[:alpha:]]"
"['\x2019]" nil ("-B") nil utf-8)))
;;; Don't send ’ to the subprocess.
(defun endless/replace-apostrophe (args)
(cons (replace-regexp-in-string
"’" "'" (car args))
(cdr args)))
(advice-add #'ispell-send-string :filter-args
#'endless/replace-apostrophe)
;;; Convert ' back to ’ from the subprocess.
(defun endless/replace-quote (args)
(if (not (derived-mode-p 'org-mode))
args
(cons (replace-regexp-in-string
"'" "’" (car args))
(cdr args))))
(advice-add #'ispell-parse-output :filter-args
#'endless/replace-quote)
(flyspell-mode 1))
(Here, `use-feature` is just a macro I got from somewhere which basically calls `use-package` with `:straight` set to `nil`.)
(defmacro use-feature (name &rest args)
"Like `use-package', but with `straight-use-package-by-default' disabled.
NAME and ARGS are as in `use-package'."
(declare (indent defun))
`(use-package ,name
:straight nil
,@args))
TheSitarHero · 2022-03-03 20:57
Thanks. Seems like you're using aspell, which I didn't try. When I started, my default ispell program was set to enchant-2 i.e. ispell, which may be a bit primitive for this.