ivyインターフェイスを用いたispell/aspellに基づく英単語補完


かなり前の記事に、ispell / aspell による英単語補完をhelmインターフェイスを使って実装した話があった。

今回はそれをhelmでなくivyで実装したわけである。英語アルファベット3文字以上を入力して、M-x ivy-ispell とすれば補完が走る。適当なキーバインドをアサインするのも一興だろう。ちなみにバイナリはispell/aspell/hunspellのどれかを用意しておく必要がある (cf. ispell-program-name)。

(require 'ispell)
(require 'ivy)
(require 'thingatpt)

(defvar ivy-ispell--word-at-point nil)

(defun ivy-ispell--case-function (input)
  (let ((case-fold-search nil))
    (cond ((string-match-p "\\`[A-Z]\\{2\\}" input) 'upcase)
          ((string-match-p "\\`[A-Z]\\{1\\}" input) 'capitalize)
          (t 'identity))))

(defun ivy-ispell--compare-length (a b)
  (< (length a) (length b)))

(defun ivy-ispell--make-candidate ()
  (let ((input (downcase ivy-ispell--word-at-point))
        (case-func (ivy-ispell--case-function
                    ivy-ispell--word-at-point)))
    (when (string-match-p "\\`[a-z]+\\'" input)
      (mapcar case-func
              (sort (ispell-lookup-words (concat input "*")
                                         ispell-complete-word-dict)
                    'ivy-ispell--compare-length)))))

(defun ivy-ispell ()
  (interactive)
  (setq ivy-ispell--word-at-point (thing-at-point 'word))
  (if (not ivy-ispell--word-at-point)
      (message "Nothing to complete.")
    (let ((candidate (ivy-read "Completion: "
                               (funcall 'ivy-ispell--make-candidate)
                               :initial-input ivy-ispell--word-at-point))
          (curpoint (point)))
      (backward-word 1)
      (delete-region (point) curpoint)
      (insert candidate))))

動作例
"nece"と入力し、ivyインターフェイスで絞り込みを行って"necessarily"を入力している様子。