Emacs Spell Checker: Configuring Flyspell, Ispell, and Aspell to Minimize False Positives in Source Code and Prose

The ispell package serves as the underlying interface in Emacs for communicating with external spell checking programs. Building upon this, the flyspell package is a built-in minor mode that provides on-the-fly spell checking. It highlights misspelled words as you type and offers interactive corrections. This article presents a practical configuration for Emacs that sets up ispell to use Aspell for both programming and standard text editing.

Additionally, it addresses a common issue for developers. While writing code, variables such as filepath or buffername in comments or docstrings are often flagged as errors, generating visual noise in your programming buffers. This article contains a specific configuration to reduce these false positives.

Setting the backend and dictionary

The first step is to configure Emacs to use Aspell as the spell-checking engine, set the default dictionary, and reduce unnecessary messages when checking individual words:

;; Set the ispell program name to aspell
;; (switching to aspell will generally offer better performance than ispell.)
(setq ispell-program-name "aspell")

;; Set the global default dictionary for the Ispell process.
(setq ispell-dictionary "en_US")

;; Reduce unnecessary messages when checking individual words.
(setq ispell-quietly t)Code language: Lisp (lisp)

Note that this configuration explicitly targets the US English dictionary "en_US". If you write in a different language or require a different regional dialect, such as "en_GB" or "fr_FR", replace "en_US" with your preferred language code.

Configuring aspell flags

The following configures Aspell's suggestion mode to "ultra", which favors very close spelling and phonetic matches when generating suggestions:

;; Configure Aspell's suggestion mode to "ultra", which favors very close
;; spelling and phonetic matches when generating suggestions.
(setq ispell-extra-args '("--sug-mode=ultra"))Code language: Lisp (lisp)

Handling compound words in source code

Comments and string literals in source code are heavily populated with compound variable names and technical terms, such as filepath, buffername, or checkbox. Aspell may flag these as errors, generating visual noise in programming buffers.

To resolve this, we pass the --run-together flag to Aspell. This flag allows Aspell to recognize words formed by combining valid dictionary words without spaces. (It only affects words in the comments and strings that Flyspell checks; it does not cause Flyspell to check arbitrary identifiers elsewhere in the source buffer.)

To apply this cleanly, we can define a custom wrapper function that we will later attach to our programming modes using add-hook. This function activates flyspell-prog-mode and then safely appends the required flags buffer-locally using add-to-list, which inherently prevents duplicate arguments if the mode is toggled off and on:

(defun my-flyspell-prog-mode (&rest _args)
  "Enable `flyspell-prog-mode' with buffer-local Aspell arguments."
  ;; The --run-together flag instructs Aspell to accept words formed by
  ;; combining two or more valid dictionary words without spaces, treating the
  ;; resulting string as valid.
  ;;
  ;; This is excellent for source code. Code is heavily populated with
  ;; compound variable names and technical terms (e.g., filepath, buffername,
  ;; checkbox).
  ;; URL: https://www.jamescherti.com/emacs-spell-checker-flyspell-ispell-aspell/
  (make-local-variable 'ispell-extra-args)
  (dolist (item '("--run-together"
                  ;; "--ignore=2"
                  ;; "--run-together-min=3"
                  ;; "--run-together-limit=4"
                  ;; "--camel-case"
                  ))
    (add-to-list 'ispell-extra-args item))
  (flyspell-prog-mode))Code language: Lisp (lisp)

Here are four additional flags worth uncommenting in the my-flyspell-prog-mode function above. Each provides specific advantages for programming workflows, along with some tradeoffs to consider:

  • --run-together-limit=4: Increases the maximum number of combined words allowed in a single string from the default limit of two up to 4. (A higher limit increases the probability that a genuine typo will be silently ignored because Aspell manages to break the misspelling down into a sequence of several shorter valid words. It also adds a slight processing overhead.)
  • --run-together-min=3 sets the minimum length of the individual words that Aspell is allowed to use when recognizing a run-together compound. For example, with a value of 3, Aspell can recognize filepath as file + path, but it is less likely to accept a compound that depends on very short components such as a or in. Increasing this value can therefore reduce false positives in source code by preventing Aspell from accepting suspicious compounds made up of short, common words.
  • --ignore=2: Configures Aspell to completely skip words containing two characters or fewer. Tradeoff: You lose spell checking for actual two-letter English words (such as "is", "an", "to", "of").
  • --camel-case: Enables Aspell's camel-case word handling, allowing camelCase identifiers to be checked more appropriately. It can reduce false positives for identifiers such as myHttpRequest that appear in docstrings and comments. Tradeoff: It can occasionally mask a typo if its components are individually recognized as valid words.

Initializing the Packages

Finally, we use add-hook to bind the spell checking functions to the appropriate Emacs hooks:

;; The flyspell package is a built-in Emacs minor mode that provides on-the-fly
;; spell checking. It highlights misspelled words as you type, offering
;; interactive corrections.
;; URL: https://www.jamescherti.com/emacs-spell-checker-flyspell-ispell-aspell/
(defun my-flyspell-enable-appropriate-mode ()
  "Enable the appropriate Flyspell mode based on the current major mode."
  (if (or (derived-mode-p 'conf-mode)
          (derived-mode-p 'yaml-mode)
          (derived-mode-p 'yaml-ts-mode)
          (derived-mode-p 'ansible-mode)
          (derived-mode-p 'nxml-mode)
          (derived-mode-p 'sgml-mode))
      (my-flyspell-prog-mode)
    (flyspell-mode 1)))

(add-hook 'prog-mode-hook #'my-flyspell-prog-mode)
(add-hook 'conf-mode-hook #'my-flyspell-enable-appropriate-mode)
(add-hook 'text-mode-hook #'my-flyspell-enable-appropriate-mode)Code language: Lisp (lisp)

Note: Configuration files such as YAML and Ansible playbooks can present an edge case because some source-like modes are based on text-mode rather than prog-mode. Without an explicit override, such modes may receive ordinary Flyspell behavior rather than programming-mode behavior. Since these files behave more like source code, we use a custom dispatcher function to apply my-flyspell-prog-mode to them instead.

A few additional interesting options

Several lesser-known variables can be adjusted to change Flyspell's behavior and, in some cases, reduce the amount of work it performs. These options involve tradeoffs: reducing spell-checking activity can improve responsiveness, but may also reduce the amount of text that is checked or the feedback that Flyspell provides. The effects depend on the buffer, editing pattern, and spell-checking workload.

  • Setting flyspell-issue-message-flag and flyspell-issue-welcome-flag to nil suppresses Flyspell's checking messages and its startup welcome message. This removes minibuffer messages generated by Flyspell without changing the actual spell-checking operation.
  • Setting flyspell-check-changes to a non-nil value causes Flyspell to check only words that have been typed or edited, instead of also checking words that point moves across. This can reduce spell-checking activity when navigating through existing text. The tradeoff is that existing misspellings are not checked because point moves across them, so they may remain undetected until the text is edited or checked explicitly.
  • Setting flyspell-delay-use-timer to a non-nil value makes Flyspell use a timer instead of sit-for when waiting after delayed commands. This allows idle timers and other Emacs code to run during the delay. The option therefore changes how Flyspell waits before performing a check rather than disabling the delay itself.
  • Lowering flyspell-large-region causes Flyspell to use its large-region checking path for smaller regions. This delegates the spell-checking operation to the external Ispell-compatible process rather than checking the region incrementally in the ordinary Flyspell path. A lower threshold can therefore change the performance characteristics of checking larger text regions, although whether it is faster depends on the spell checker and workload. Repeated-word detection is not implemented for large regions.
  • Setting flyspell-mark-duplications-flag to nil prevents Flyspell from reporting repeated words as errors. This eliminates duplicate-word detection, so accidental repetitions such as "the the" are no longer reported. Repeated-word detection is also not implemented when Flyspell checks large regions.
  • Setting ispell-silently-savep to a non-nil value causes new words added to the personal dictionary to be saved without an interactive confirmation prompt. This avoids the prompt when saving personal-dictionary additions, but also removes that opportunity to review each addition before it is saved.
  • Setting flyspell-highlight-properties to nil prevents Flyspell from highlighting incorrect words when the relevant text already has text properties. The spelling check can still identify the word as incorrect, but Flyspell will not add its normal misspelling highlight when an applicable text property is present. This can be useful when existing text properties should take precedence over Flyspell's visual indication.
  • Increasing flyspell-delay increases the number of seconds Flyspell waits before checking after a command classified as delayed. This can reduce how frequently spell checking is triggered during rapid editing, at the cost of delaying the appearance of misspelling highlights. The default delayed commands include commands such as self-insert-command and delete-backward-char.