4

First some disambiguation:

  • This Endless Parentheses page demonstrates how to use ispell-skip-region-alist to skip =code= and =verbatim= blocks with ispell…

    (defun endless/org-ispell ()
      "Configure `ispell-skip-region-alist' for `org-mode'."
      (make-local-variable 'ispell-skip-region-alist)
      (add-to-list 'ispell-skip-region-alist '(org-property-drawer-re))
      (add-to-list 'ispell-skip-region-alist '("~" "~"))
      (add-to-list 'ispell-skip-region-alist '("=" "="))
      (add-to-list 'ispell-skip-region-alist '("^#\\+BEGIN_SRC" . "^#\\+END_SRC")))
    (add-hook 'org-mode-hook #'endless/org-ispell)
    

    …however, flyspell does not use ispell-skip-region-alist.

  • This question on Emacs SX demonstrates how to skip flyspell checking for source blocks…

    ;; NO spell check for embedded snippets
    (defadvice org-mode-flyspell-verify (after org-mode-flyspell-verify-hack activate)
      (let* ((rlt ad-return-value)
             (begin-regexp "^[ \t]*#\\+begin_\\(src\\|html\\|latex\\|example\\|quote\\)")
             (end-regexp "^[ \t]*#\\+end_\\(src\\|html\\|latex\\|example\\|quote\\)")
             (case-fold-search t)
             b e)
        (when ad-return-value
          (save-excursion
            (setq b (re-search-backward begin-regexp nil t))
            (if b (setq e (re-search-forward end-regexp nil t))))
          (if (and b e (< (point) e)) (setq rlt nil)))
        (setq ad-return-value rlt)))
    

    …but not for inline code and verbatim region.

How can I achieve the ispell solution for flyspell, where Flyspell skips any regions surrounded by ~ or =? Generally I use these for things like variable names, which naturally tend to fail spellchecking.

Matthew Piziak
  • 5,958
  • 3
  • 29
  • 77

2 Answers2

3

It looks like this works out-of-the box with Emacs 26.3 and Orgmode 9.1.9. Source blocks and code snippets are not highlighted by flyspell with that Orgmode version. Maybe, you should just update.

To address the general problem:

You can ignore corrections proposed by flyspell by adding a function to flyspell-incorrect-hook. This hook already exists since Emacs 21.1. So it should be available even if you use an Emacs that old (for whatever reason).

I cite here the doc-string of flyspell-incorrect-hook:

List of functions to be called when incorrect words are encountered. Each function is given three arguments. The first two arguments are the beginning and the end of the incorrect region. The third is either the symbol ‘doublon’ or the list of possible corrections as returned by ‘ispell-parse-output’.

If any of the functions return non-nil, the word is not highlighted as incorrect.

It is easy to detect source blocks and code snippets with org-element-context.

Here comes the code that skips flyspell on source blocks and code snippets:

(defun org+-flyspell-skip-code (b e _ignored)
  "Returns non-nil if current word is code.
This function is intended for `flyspell-incorrect-hook'."
  (save-excursion
    (goto-char b)
    (memq (org-element-type (org-element-context))
      '(code src-block))))

(defun org+-config-flyspell ()
  "Configure flyspell for org-mode."
  (add-hook 'flyspell-incorrect-hook #'org+-flyspell-skip-code nil t))

(add-hook 'org-mode-hook #'org+-config-flyspell)

Tested on Emacs 26.3.

Tobias
  • 32,569
  • 1
  • 34
  • 75
  • 1
    I'm on Org version 9.2.3, which does not seem to skip those regions by default. Your solution is the cleanest I've seen, since it uses a purpose-built hook rather than an advice wrapper. Thank you! – Matthew Piziak Jan 15 '20 at 18:16
  • you could easily mix up the multiple major mode's spell setup and slow down the Emacs. `flyspell-incorrect-hook` is only for light weight and generic setup. See my updated answer – chen bin Jan 16 '20 at 10:09
  • @chenbin `#'org+-flyspell-skip-code` is added [buffer locally](https://www.gnu.org/software/emacs/manual/html_node/elisp/Setting-Hooks.html) to `flyspell-incorrect-hook`. So there is no interference with other major modes. Let the OP decide whether the performance is a killer argument. Aspell is a background process that keeps running when it is started once. Therefore there is not so much overhead as one would guess at the first sight. – Tobias Jan 16 '20 at 10:23
  • @Tobias, it's better to use the variable `flyspell-generic-check-word-predicate` which is buffer local out of box so user has not chance to to make the mistake. Besides, `flyspell-incorrect-hook` is executed AFTER the predicate. So it can't override predicate's logic. – chen bin Jan 16 '20 at 10:32
  • @chenbin You are right that `flyspell-generic-check-word-predicate` should be used. I had a look into `org-compat.el`. There stands `(put 'org-mode 'flyspell-mode-predicate 'org-mode-flyspell-verify)`. This should make flyspell work correctly out of the box. Maybe, we should help Matthew in a chat to identify the problem with his org version. – Tobias Jan 16 '20 at 11:53
  • See the end of my answer at https://emacs.stackexchange.com/questions/9333/how-does-one-use-flyspell-in-org-buffers-without-flyspell-triggering-on-tangled/9347, use `(setq flyspell-generic-check-word-predicate org-mode-flyspell-verify)` in `org-mode-hook` – chen bin Jan 16 '20 at 12:05
  • Works with Org 9.4.5 and Emacs 27.2. No more spell checking in `~code~` and `=verbatim=`. Thank you! – Rudolf Adamkovic May 15 '21 at 22:44
1

Check the current font face at point, if it's org-verbatim or org-code, then set the predicate's return value to nil. So Emacs knows the word at point is NOT a typo.

Here is the function to test font face at point,

(defun font-belongs-to (pos fonts)
  "Current font at POS belongs to FONTS."
  (let* ((fontfaces (get-text-property pos 'face)))
    (when (not (listp fontfaces))
      (setf fontfaces (list fontfaces)))
    (delq nil
          (mapcar (lambda (f)
                    (member f fonts))
                  fontfaces))))

To use this function, in https://emacs.stackexchange.com/a/9347/594 code, just below (when ad-return-value, insert one line,

(if (font-belongs-to (point) '(org-verbatim org-code)) (setq rlt nil))

BTW, you can check my org setup at https://github.com/redguardtoo/emacs.d/blob/98bef295ac515e5af43716d72a377494646653bf/lisp/init-org.el#L120 . The predicate there has more features and its code is more efficient.

Please note flyspell-incorrect-hook is NOT the right place for per mode setup. I explain the details in https://emacs.stackexchange.com/a/9347/594

chen bin
  • 4,781
  • 18
  • 36