Since the first option is the right one when fixing a common misspelling. Is there a way to make ispell-word correct the spelling of a word to the first suggestion instead of prompting from a list of options?


There may be other ways of achieving that, but if you use flyspell then it has flyspell-auto-correct-previous-word which does just that. It's bound to C-; by default.

flyspell will put a wavy red line under spelling errors and so pressing C-; will have a go at correcting the first one before the current point, so you don't even need to move the point onto the misspelled word.

Works very nicely for me when I'm writing text in Org-mode, but I assume it works in any mode.

  • While this is a good answer, I'm using wcheck, not flyspell - because it can show all incorrect, visible words, not just while I'm editing. I've found a way to do this only with ispell, using cl-letf. Added own answer. – ideasman42 Feb 16 '20 at 3:03

This can be done by overriding the function that prompts for input.

This function runs ispell-word, always using the first option.

(defun ispell-word-immediate ()
  "Run `ispell-word', using the first suggestion."
      (((symbol-function 'ispell-command-loop)
        (lambda (miss _guess _word _start _end) (car miss))))

In the case you want to one of the other available options, it's possible to cycle through the options. Although the code needed is more involved.

(defmacro ispell-word-immediate--with-messages-as-list (message-list &rest body)
  "Run BODY adding any message call to the MESSAGE-LIST list."
  (declare (indent 1))
  (let ((temp-message-list (list)))
        (((symbol-function 'message)
          (lambda (&rest args)
            ;; Only check if non-null because this is a signal not to log at all.
            (when message-log-max
              (push (apply 'format-message args) temp-message-list)))))
        ;; Protected.
        (setq ,message-list (append ,message-list (reverse temp-message-list)))))))

(defvar-local ispell-word-immediate--alist nil
  "Internal properties for repeated `ispell-word-immediate'")

(defun ispell-word-immediate--impl (cycle-direction)
  "Run `ispell-word', using the first suggestion.
Argument CYCLE-DIRECTION The offset for cycling words, 1 or -1 for forward/backward."
  (let ((message-list (list))
        (index 0)
        (point-init (point))
        (display-text nil))

    ;; Roll-back and cycle through corrections.
          (eq last-command 'ispell-word-immediate-forward)
          (eq last-command 'ispell-word-immediate-backward)))

      ;; Roll-back correction.
      (let ((alist ispell-word-immediate--alist))

        ;; Roll back the edit.
        (delete-region (alist-get 'start alist) (alist-get 'end alist))
        (insert (alist-get 'word alist))

        ;; Update vars from previous state.
        (setq point-init (alist-get 'point alist))
        (setq index (+ cycle-direction (cdr (assq 'index alist))))

        ;; Roll back the buffer state.
        (setq buffer-undo-list (alist-get 'buffer-undo-list alist))
        (setq pending-undo-list (alist-get 'pending-undo-list alist))
        (goto-char point-init)))

    ;; Clear every time, ensures stale data is never used.
    (setq ispell-word-immediate--alist nil)

        (((symbol-function 'ispell-command-loop)
          (lambda (miss _guess word start end)
            ;; Wrap around in either direction.
            (setq index (mod index (length miss)))
            (let ((word-at-index (nth index miss)))

              ;; Generate display text.
              (setq display-text
                      (lambda (word-iter)
                        (if (eq word-at-index word-iter)
                            (format "[%s]" (propertize word-iter 'face 'match))
                          (format " %s " word-iter)))

              ;; Set the state for redoing the correction.
              (setq ispell-word-immediate--alist
                     ;; Tricky! but nicer usability.
                     (cons 'buffer-undo-list buffer-undo-list)
                     (cons 'pending-undo-list pending-undo-list)
                     (cons 'point point-init)

                     (cons 'index index)
                     (cons 'word word)
                     (cons 'start (marker-position start))
                     (cons 'end
                           (+ (marker-position end)
                              (- (length word-at-index) (length word))))))


      ;; Run quietly so message output doesn't flicker.
      (prog1 (ispell-word-immediate--with-messages-as-list message-list (ispell-word))

        ;; Log the message, only display if we don't have 'display-text'
        ;; This avoids flickering message output.
        (let ((inhibit-message (not (null display-text))))
          (dolist (message-text message-list)
            (message "%s" message-text)))

        ;; Run last so we can ensure it's the last text in the message buffer.
        ;; Don't log because it's not useful to keep the selection.
        (when display-text
          (let ((message-log-max nil))
            (message "%s" display-text)))))))

;; Public functions.
(defun ispell-word-immediate-forward ()
  "Run `ispell-word', using the first suggestion, or cycle forward."
  (ispell-word-immediate--impl 1))

(defun ispell-word-immediate-backward ()
  "Run `ispell-word', using the first suggestion, or cycle backward."
  (ispell-word-immediate--impl -1))

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.