From 60d417545a2852d36427799691792e4ddff8f86c Mon Sep 17 00:00:00 2001 From: Reuben Thomas Date: Sun, 4 Dec 2016 22:39:27 +0000 Subject: [PATCH] Add Enchant support to ispell.el (Bug#17742) MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit * lisp/textmodes/ispell.el (ispell-program-name): Add “enchant”. (ispell-really-enchant): Add variable. (ispell-check-version): If using Enchant, check it’s new enough (at least 1.6.1). (Like the ispell check, this is absolute: cannot work without.) (ispell-enchant-dictionary-alist): Add variable. (ispell-find-enchant-dictionaries): Add function, based on ispell-find-aspell-dictionaries. (ispell-set-spellchecker-params): Allow dictionary auto-detection for Enchant, and call ispell-find-enchant-dictionaries to find them. Use old ispell name to locale mapping code for Enchant too. (ispell-send-replacement): Make it work with Enchant. --- lisp/textmodes/ispell.el | 92 ++++++++++++++++++++++++++++++++++------ 1 file changed, 78 insertions(+), 14 deletions(-) diff --git a/lisp/textmodes/ispell.el b/lisp/textmodes/ispell.el index 773023a34a6..e6ca32f20d9 100644 --- a/lisp/textmodes/ispell.el +++ b/lisp/textmodes/ispell.el @@ -208,6 +208,10 @@ Must be greater than 1." :type 'integer :group 'ispell) +;; XXX Add enchant to this list once enchant >= 2.1.0 is widespread. +;; Before that, adding it is useless, as if it is found, it will just +;; cause an error; and one of the other spelling engines below is +;; almost certainly installed in any case, for enchant to use. (defcustom ispell-program-name (or (executable-find "aspell") (executable-find "ispell") @@ -605,6 +609,8 @@ english.aff). Aspell and Hunspell don't have this limitation.") "Non-nil if we can use Aspell extensions.") (defvar ispell-really-hunspell nil "Non-nil if we can use Hunspell extensions.") +(defvar ispell-really-enchant nil + "Non-nil if we can use Enchant extensions.") (defvar ispell-encoding8-command nil "Command line option prefix to select encoding if supported, nil otherwise. If setting the encoding is supported by spellchecker and is selectable from @@ -739,17 +745,26 @@ Otherwise returns the library directory name, if that is defined." (and (search-forward-regexp "(but really Hunspell \\([0-9]+\\.[0-9\\.-]+\\)?)" nil t) + (match-string 1))) + (setq ispell-really-enchant + (and (search-forward-regexp + "(but really Enchant \\([0-9]+\\.[0-9\\.-]+\\)?)" + nil t) (match-string 1))))) (let* ((aspell8-minver "0.60") (ispell-minver "3.1.12") (hunspell8-minver "1.1.6") + (enchant-minver "2.1.0") (minver (cond ((not (version<= ispell-minver ispell-program-version)) ispell-minver) ((and ispell-really-aspell (not (version<= aspell8-minver ispell-really-aspell))) - aspell8-minver)))) + aspell8-minver) + ((and ispell-really-enchant + (not (version<= enchant-minver ispell-really-enchant))) + enchant-minver)))) (if minver (error "%s release %s or greater is required" @@ -1183,6 +1198,49 @@ dictionary from that list was found." (list dict)) ispell-hunspell-dictionary-alist :test #'equal)))) +;; Make ispell.el work better with enchant. + +(defvar ispell-enchant-dictionary-alist nil + "An alist of parsed Enchant dicts and associated parameters. +Internal use.") + +(defun ispell--call-enchant-lsmod (&rest args) + "Call enchant-lsmod with ARGS and return the output as string." + (with-output-to-string + (with-current-buffer + standard-output + (apply 'ispell-call-process + (concat ispell-program-name "-lsmod") nil t nil args)))) + +(defun ispell--get-extra-word-characters (&optional lang) + "Get the extra word characters for LANG as a character class. +If LANG is omitted, get the extra word characters for the default language." + (concat "[" (string-trim-right (apply 'ispell--call-enchant-lsmod + (append '("-word-chars") (if lang `(,lang))))) "]")) + +(defun ispell-find-enchant-dictionaries () + "Find Enchant's dictionaries, and record in `ispell-enchant-dictionary-alist'." + (let* ((dictionaries + (split-string + (ispell--call-enchant-lsmod "-list-dicts" (buffer-string)) " ([^)]+)\n")) + (found + (mapcar #'(lambda (lang) + `(,lang "[[:alpha:]]" "[^[:alpha:]]" + ,(ispell--get-extra-word-characters) t nil nil utf-8)) + dictionaries))) + ;; Merge into FOUND any elements from the standard ispell-dictionary-base-alist + ;; which have no element in FOUND at all. + (dolist (dict ispell-dictionary-base-alist) + (unless (assoc (car dict) found) + (setq found (nconc found (list dict))))) + (setq ispell-enchant-dictionary-alist found) + ;; Add a default entry + (let ((default-dict + `(nil "[[:alpha:]]" "[^[:alpha:]]" + ,(ispell--get-extra-word-characters) + t nil nil utf-8))) + (push default-dict ispell-enchant-dictionary-alist)))) + ;; Set params according to the selected spellchecker (defvar ispell-last-program-name nil @@ -1208,7 +1266,7 @@ aspell is used along with Emacs).") (setq ispell-library-directory (ispell-check-version)) t) (error nil)) - ispell-encoding8-command) + (or ispell-encoding8-command ispell-really-enchant)) ;; auto-detection will only be used if spellchecker is not ;; ispell and supports a way to set communication to UTF-8. (if ispell-really-aspell @@ -1216,11 +1274,14 @@ aspell is used along with Emacs).") (ispell-find-aspell-dictionaries)) (if ispell-really-hunspell (or ispell-hunspell-dictionary-alist - (ispell-find-hunspell-dictionaries))))) + (ispell-find-hunspell-dictionaries)) + (if ispell-really-enchant + (or ispell-enchant-dictionary-alist + (ispell-find-enchant-dictionaries)))))) ;; Substitute ispell-dictionary-alist with the list of ;; dictionaries corresponding to the given spellchecker. - ;; If a recent aspell or hunspell, use the list of really + ;; With programs that support it, use the list of really ;; installed dictionaries and add to it elements of the original ;; list that are not present there. Allow distro info. (let ((found-dicts-alist @@ -1229,17 +1290,19 @@ aspell is used along with Emacs).") ispell-aspell-dictionary-alist (if ispell-really-hunspell ispell-hunspell-dictionary-alist)) - nil)) + (if ispell-really-enchant + ispell-enchant-dictionary-alist + nil))) (ispell-dictionary-base-alist ispell-dictionary-base-alist) ispell-base-dicts-override-alist ; Override only base-dicts-alist all-dicts-alist) ;; While ispell and aspell (through aliases) use the traditional - ;; dict naming originally expected by ispell.el, hunspell - ;; uses locale based names with no alias. We need to map + ;; dict naming originally expected by ispell.el, hunspell & Enchant + ;; use locale-based names with no alias. We need to map ;; standard names to locale based names to make default dict - ;; definitions available for hunspell. - (if ispell-really-hunspell + ;; definitions available to these programs. + (if (or ispell-really-hunspell ispell-really-enchant) (let (tmp-dicts-alist) (dolist (adict ispell-dictionary-base-alist) (let* ((dict-name (nth 0 adict)) @@ -1264,7 +1327,7 @@ aspell is used along with Emacs).") (setq ispell-args (nconc ispell-args (list "-d" dict-equiv))) (message - "ispell-set-spellchecker-params: Missing Hunspell equiv for \"%s\". Skipping." + "ispell-set-spellchecker-params: Missing equivalent for \"%s\". Skipping." dict-name) (setq skip-dict t))) @@ -1306,7 +1369,7 @@ aspell is used along with Emacs).") (nth 4 adict) ; many-otherchars-p (nth 5 adict) ; ispell-args (nth 6 adict) ; extended-character-mode - (if ispell-encoding8-command + (if (or ispell-encoding8-command ispell-really-enchant) 'utf-8 (nth 7 adict))) adict) @@ -1742,9 +1805,10 @@ and pass it the output of the last Ispell invocation." (erase-buffer))))))) (defun ispell-send-replacement (misspelled replacement) - "Notify Aspell that MISSPELLED should be spelled REPLACEMENT. -This allows improving the suggestion list based on actual misspellings." - (and ispell-really-aspell + "Notify spell checker that MISSPELLED should be spelled REPLACEMENT. +This allows improving the suggestion list based on actual misspellings. +Only works for Aspell and Enchant." + (and (or ispell-really-aspell ispell-really-enchant) (ispell-send-string (concat "$$ra " misspelled "," replacement "\n")))) -- 2.39.2