#+title:    :checkers spell
#+subtitle: Tasing you for misspelling mispelling
#+created:  February 20, 2017
#+since:    2.0.0

* Description :unfold:
This modules provides spellchecking powered by =aspell=, =hunspell= or
=enchant=.

Spellcheck is automatically loaded in many ~text-mode~ derivatives, which
includes ~org-mode~, ~markdown-mode~, the Git Commit buffer (from magit),
~mu4e-compose-mode~, and others.

- Spell checking and correction using =aspell=, =hunspell= or =enchant=.
- Ignores source code inside org or markdown files.
- Lazily spellchecking recent changes only when idle.
- Choosing suggestions using completion interfaces ([[doom-package:ivy]] or [[doom-package:helm]]).

** Maintainers
/This module has no dedicated maintainers./ [[doom-contrib-maintainer:][Become a maintainer?]]

** Module flags
- +aspell ::
  Use =aspell= as a backend for correcting words.
- +enchant ::
  Use =enchant-2= as a backend for correcting words.
- +everywhere ::
  Spell check in programming modes as well (in comments only).
- +flyspell ::
  Use [[doom-package:flyspell]] instead of [[doom-package:spell-fu]]. It's significantly slower, but supports
  multiple languages and dictionaries.
- +hunspell ::
  Use =hunspell= as a backend for correcting words.

** Packages
- if [[doom-module:+flyspell]]
  - [[doom-package:flyspell-correct]]
  - [[doom-package:flyspell-correct-ivy]] if [[doom-module::completion ivy]]
  - [[doom-package:flyspell-correct-helm]] if [[doom-module::completion helm]]
  - [[doom-package:flyspell-correct-popup]] unless [[doom-module::completion ivy]], [[doom-module::completion helm]], or
    [[doom-module::completion vertico]]
  - [[doom-package:flyspell-lazy]]
- else
  - [[doom-package:spell-fu]]

** Hacks
/No hacks documented for this module./

** TODO Changelog
# This section will be machine generated. Don't edit it by hand.
/This module does not have a changelog yet./

* Installation
[[id:01cffea4-3329-45e2-a892-95a384ab2338][Enable this module in your ~doom!~ block.]]

This module requires one of =aspell=, =hunspell= or =enchant-2= installed on
your system and in your =$PATH=. They also need dictionaries for your
language(s).

#+begin_quote
 🚧 If you *are not* using [[doom-module:+flyspell]], you will need =aspell= (and a dictionary)
    installed whether or not you have [[doom-module:+hunspell]] or [[doom-module:+enchant]] enabled. This is
    because [[doom-package:spell-fu]] does not support generating the word list with anything
    other than =aspell= yet.
#+end_quote

** Aspell
- Ubuntu: ~$ apt-get install aspell aspell-en~
- macOS: ~$ brew install aspell~
- Arch Linux: ~$ pacman -S aspell aspell-en~
- NixOS:
  #+begin_src nix
  {
    environment.systemPackages = with pkgs; [
      (aspellWithDicts (dicts: with dicts; [ en en-computers en-science ]))
    ];
  }
  #+end_src

** Hunspell
- Ubuntu: ~$ apt-get install hunspell~
- macOS: ~$ brew install hunspell~
- Arch Linux: ~$ pacman -S hunspell~
- NixOS:
  #+begin_src nix
  {
    environment.systemPackages = with pkgs; [
      hunspell
    ];
  }
  #+end_src

** Enchant
- Ubuntu: ~$ apt-get install enchant-2~
- macOS: ~$ brew install enchant~
- Arch Linux: ~$ pacman -S enchant~
- NixOS:
  #+begin_src nix
  {
    environment.systemPackages = with pkgs; [
      enchant
    ];
  }
  #+end_src

  Enchant is just a wrapper for other spelling libraries and you will need to
  have at least one of the supported backends installed as well.

* TODO Usage
#+begin_quote
 🔨 /This module's usage documentation is incomplete./ [[doom-contrib-module:][Complete it?]]
#+end_quote

When using [[doom-module:+everywhere]], spell checking is performed for as many major modes as
possible, and not only ~text-mode~ derivatives. e.g. in comments for programming
major modes.

* TODO Configuration
#+begin_quote
 🔨 /This module's configuration documentation is incomplete./ [[doom-contrib-module:][Complete it?]]
#+end_quote

Dictionary is set by ~ispell-dictionary~ variable. Can be changed locally with
the function ~ispell-change-dictionary~.

** Changing how quickly spelling is checked after changes
*** Spell-fu users
Adjust ~spell-fu-idle-delay~ to change how long Emacs waits to spellcheck after
recent changes:
#+begin_src emacs-lisp
(after! spell-fu
  (setq spell-fu-idle-delay 0.5))  ; default is 0.25
#+end_src

*** Flyspell users
Lazy spellcheck is provided by [[doom-package:flyspell-lazy]] package.

~flyspell-lazy-idle-seconds~ sets how many idle seconds until spellchecking
recent changes (default as 1), while ~flyspell-lazy-window-idle-seconds~ sets
how many seconds until the whole window is spellchecked (default as 3):
#+begin_src emacs-lisp
(after! flyspell
  (setq flyspell-lazy-idle-seconds 2))
#+end_src

** Reducing false positives by disabling spelling on certain faces
*** Spell-fu users
Users can exclude what faces to preform spellchecking on by adjusting
~+spell-excluded-faces-alist~ in a buffer-local hook:
#+begin_src emacs-lisp
(setf (alist-get 'markdown-mode +spell-excluded-faces-alist)
      '(markdown-code-face
        markdown-reference-face
        markdown-link-face
        markdown-url-face
        markdown-markup-face
        markdown-html-attr-value-face
        markdown-html-attr-name-face
        markdown-html-tag-name-face))
#+end_src

*** Flyspell users
Flyspell will run a series of predicate functions to determine if a word should
be spell checked. You can add your own with ~set-flyspell-predicate!~:
#+begin_src emacs-lisp
(set-flyspell-predicate! '(markdown-mode gfm-mode)
  #'+markdown-flyspell-word-p)
#+end_src

Flyspell predicates take no arguments and must return a boolean to determine if
the word at point should be spell checked. For example:
#+begin_src emacs-lisp
(defun +markdown-flyspell-word-p ()
  "Return t if point is on a word that should be spell checked.

Return nil if on a link url, markup, html, or references."
  (let ((faces (ensure-list (get-text-property (point) 'face))))
    (or (and (memq 'font-lock-comment-face faces)
             (memq 'markdown-code-face faces))
        (not (cl-loop with unsafe-faces = '(markdown-reference-face
                                            markdown-url-face
                                            markdown-markup-face
                                            markdown-comment-face
                                            markdown-html-attr-name-face
                                            markdown-html-attr-value-face
                                            markdown-html-tag-name-face
                                            markdown-code-face)
                      for face in faces
                      if (memq face unsafe-faces)
                      return t)))))
#+end_src

** Adding or removing words to your personal dictionary
Use ~M-x +spell/add-word~ and ~M-x +spell/remove-word~ to whitelist words that
you know are not misspellings. For evil users these are bound to [[kbd:][zg]] and [[kbd:][zw]],
respectively. [[doom-module:+flyspell]] users can also add/remove words from the
[[doom-package:flyspell-correct]] popup interface (there will be extra options on the list of
corrections for "save word to dictionary").

* Troubleshooting
[[doom-report:][Report an issue?]]

** spell-fu highlights every single word
[[doom-package:spell-fu]] caches its word list. If it was activated before your dictionaries were
installed, it will generate an empty word list, causing it to highlight all
words as incorrect. Delete its cache files in =$EMACSDIR/.local/etc/spell-fu/=
to fix this.

** Cannot add [word] to any active dictionary
*** Aspell
If ~M-x +spell/add-word~ results in the above error this mean that the
personal dictionary file was not created for some reason. This can be
fixed by creating the required file manually. Note: the personal dictionary 
is defined by ~ispell-personal-dictionary~, you may need to check it first
with ~M-x describe-variable~.
#+begin_example shell
mkdir -p ~/.emacs.d/.local/etc/ispell
echo personal_ws-1.1 en 0 > ~/.emacs.d/.local/etc/ispell/.pws
#+end_example

Where ~personal_ws-1.1 en 0~ is the required header format for the personal
dictionary file. ~en~ is the language you're writing in and have a dict
installed and ~0~ is the number of added words in the dictionary. If you are
planning of updating the file with the list of words, update the number
accordingly.

After the file is created, restart emacs and adding words should work.

* Frequently asked questions
/This module has no FAQs yet./ [[doom-suggest-faq:][Ask one?]]

* TODO Appendix
#+begin_quote
 🔨 This module has no appendix yet. [[doom-contrib-module:][Write one?]]
#+end_quote
