Subject: Re: bug#17742: Acknowledgement (Support for enchant?)

From: Reuben Thomas <[email-masked]>
Date: Wed, 9 Aug 2017 12:35:16 +0100

On 21 December 2016 at 17:32, Reuben Thomas <[email-masked]> wrote:

> On 21 December 2016 at 17:13, Eli Zaretskii <[email-masked]> wrote:
>
>>
>> Maybe we should simply wait until Enchant acquires those APIs.
>>
>
> ​I'll revisit this when a new Enchant release is made. The timing and
> future plans for Enchant and Emacs might affect the decision at that point,
> or I might have found more energy to implement hunspell dictionary scanning
> for Enchant!
>
​
I attach updated patches, one for Enchant support, and the other to remove
a little historical cruft from ispell.el.

Enchant 2.1.0 (just released) adds support for getting the otherchars
character list (currently only supported on hunspell), and the ispell.el
support uses it.

Hence, I believe that the Enchant support should now satisfy Eli's
requirements.

-- 
https://rrt.sc3d.org <http://rrt.sc3d.org>
From 76ff43068ec914fdb405b675d139a94439dc698c Mon Sep 17 00:00:00 2001
From: Reuben Thomas <[email-masked]>
Date: Sun, 4 Dec 2016 22:39:27 +0000
Subject: [PATCH 1/2] Add Enchant support to ispell.el (Bug#17742)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
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 773023a..e6ca32f 100644
--- a/lisp/textmodes/ispell.el
+++ b/lisp/textmodes/ispell.el
@@ -208,6 +208,10 @@ ispell-choices-win-default-height
   :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 @@ ispell-really-aspell
   "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 @@ ispell-check-version
 		  (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 @@ ispell-find-hunspell-dictionaries
                     (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 @@ ispell-set-spellchecker-params
 		   (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 @@ ispell-set-spellchecker-params
 		(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 @@ ispell-set-spellchecker-params
 		   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 @@ ispell-set-spellchecker-params
 			(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 @@ ispell-set-spellchecker-params
                          (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 @@ ispell-accept-output
 	    (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.7.4

From 11a78847f2ffb87cdf93f00fd13ad7a5ed3f2a08 Mon Sep 17 00:00:00 2001
From: Reuben Thomas <[email-masked]>
Date: Tue, 8 Aug 2017 15:56:03 +0100
Subject: [PATCH 2/2] Remove old comments and a redundant FIXME

* lisp/textmodes/ispell.el (ispell-process-line): Remove some old
commented code, a redundant FIXME, and outdated usage instructions.
---
 lisp/textmodes/ispell.el | 30 ++++--------------------------
 1 file changed, 4 insertions(+), 26 deletions(-)

diff --git a/lisp/textmodes/ispell.el b/lisp/textmodes/ispell.el
index e6ca32f..7ae2c0c 100644
--- a/lisp/textmodes/ispell.el
+++ b/lisp/textmodes/ispell.el
@@ -1,10 +1,8 @@
-;;; ispell.el --- interface to International Ispell Versions 3.1 and 3.2  -*- lexical-binding:t -*-
+;;; ispell.el --- interface to spell checkers  -*- lexical-binding:t -*-
 
 ;; Copyright (C) 1994-1995, 1997-2017 Free Software Foundation, Inc.
 
 ;; Author:           Ken Stevens <[email-masked]>
-;; Status          : Release with 3.1.12+ and 3.2.0+ ispell.
-;; Keywords: unix wp
 
 ;; This file is part of GNU Emacs.
 
@@ -21,23 +19,11 @@
 ;; You should have received a copy of the GNU General Public License
 ;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
 
-;; Note: version numbers and time stamp are not updated
-;;   when this file is edited for release with GNU Emacs.
-
 ;;; Commentary:
 
 ;; INSTRUCTIONS
 
-;;   This code contains a section of user-settable variables that you
-;; should inspect prior to installation.  Look past the end of the history
-;; list.  Set them up for your locale and the preferences of the majority
-;; of the users.  Otherwise the users may need to set a number of variables
-;; themselves.
-;;   You particularly may want to change the default dictionary for your
-;; country and language.
-;;   Most dictionary changes should be made in this file so all users can
-;; enjoy them.  Local or modified dictionaries are supported in your .emacs
-;; file.  Use the variable `ispell-local-dictionary-alist' to specify
+;;   Use the variable `ispell-local-dictionary-alist' to specify
 ;; your own dictionaries.
 
 ;;  Depending on the mail system you use, you may want to include these:
@@ -112,7 +98,7 @@
 ;;  Need a way to select between different character mappings without separate
 ;;    dictionary entries.
 ;;  Multi-byte characters if not defined by current dictionary may result in the
-;;    evil "misalignment error" in some versions of MULE Emacs.
+;;    evil "misalignment error" in some versions of Emacs.
 ;;  On some versions of Emacs, growing the minibuffer fails.
 ;;    see `ispell-help-in-bufferp'.
 ;;  Recursive edits (?C-r or ?R) inside a keyboard text replacement check (?r)
@@ -3524,17 +3510,9 @@ ispell-process-line
                       (setq ispell-filter recheck-region
                             recheck-region nil
                             replace replace-word)))))
+              (setq shift (+ shift (- (length replace) word-len)))))
 
-              (setq shift (+ shift (- (length replace) word-len)))
-
-              ;; Move line-start across word...
-              ;; new shift function does this now...
-              ;;(set-marker line-start (+ line-start
-              ;;			(- (length replace)
-              ;;			   (length (car poss)))))
-              ))
             (if (not ispell-quit)
-                ;; FIXME: remove redundancy with identical code above.
                 (let (message-log-max)
                   (message
                    "Continuing spelling check using %s with %s dictionary..."
-- 
2.7.4