]> code.delx.au - gnu-emacs/commitdiff
Initial support for hunspell dictionaries auto-detection (Bug#13639)
authorAgustín Martín <agustin.martin@hispalinux.es>
Thu, 28 Feb 2013 19:01:34 +0000 (20:01 +0100)
committerAgustín Martín <agustin.martin@hispalinux.es>
Thu, 28 Feb 2013 19:01:34 +0000 (20:01 +0100)
* textmodes/ispell.el (ispell-find-hunspell-dictionaries):
Ask hunspell about available and default dictionaries.
(ispell-parse-hunspell-affix-file): Extract relevant info from
hunspell affix file.
(ispell-hunspell-fill-dictionary-entry): Fill non-initialized
`ispell-dictionary-alist' entry for given dictionary after info
provided by `ispell-parse-hunspell-affix-file'.
(ispell-hunspell-dict-paths-alist): New defvar to contain an alist
of parsed hunspell dicts and associated affix files.
(ispell-hunspell-dictionary-alist): New defvar to contain an alist
of parsed hunspell dicts and associated parameters.
(ispell-set-spellchecker-params):
Call `ispell-find-hunspell-dictionaries' if hunspell and not
previously done.
(ispell-start-process):
Call `ispell-hunspell-fill-dictionary-entry' for current
dictionary if it is not initialized.

Some additional details about the implementation:

(ispell-hunspell-dict-paths-alist): Alist that contains a list of
  parsed hunspell dicts and associated affix files.

(ispell-hunspell-dictionary-alist): Alist of parsed hunspell dicts and
  associated parameters. It is initially just a list of found
  dictionaries except for the default dictionary where is filled with
  proper parameters.

When spellchecker is initialized by (ispell-set-spellchecker-params)
if the conditions: is hunspell, communication can be set to UTF-8 and
Emacs flavor supports [:alpha:] are matched installed hunspell
dictionaries are parsed and info passed to
`ispell-hunspell-dictionary-alist', either full for default dictionary
or just name for other dictionaries. These entries are used for
`ispell-dictionary-alist' if not overriden.

Before starting hunspell process in (ispell-start-process), if
`ispell-dictionary-alist' entry is not yet initialized
(ispell-hunspell-fill-dictionary-entry) is called to fill that entry
(and all pending entries using the same affix file) after info
extracted by (ispell-parse-hunspell-affix-file) from the associated
affix file.

hunspell process will then be started as usual. This delayed procedure
is used to avoid that in systems containing many hunspell dictionaries
all affix files are parsed (if there are many, time may be noticeable)
for just one used dictionary.

lisp/ChangeLog
lisp/textmodes/ispell.el

index a8d66839d120b7bc8a12ccade6560b58f9df137d..66b18651695aff59218416d68dc2c1600e48beb2 100644 (file)
@@ -1,3 +1,25 @@
+2013-02-28  Agustín Martín Domingo  <agustin.martin@hispalinux.es>
+
+       Initial support for hunspell dictionaries auto-detection (Bug#13639)
+
+       * textmodes/ispell.el (ispell-find-hunspell-dictionaries):
+       Ask hunspell about available and default dictionaries.
+       (ispell-parse-hunspell-affix-file): Extract relevant info from
+       hunspell affix file.
+       (ispell-hunspell-fill-dictionary-entry): Fill non-initialized
+       `ispell-dictionary-alist' entry for given dictionary after info
+       provided by `ispell-parse-hunspell-affix-file'.
+       (ispell-hunspell-dict-paths-alist): New defvar to contain an alist
+       of parsed hunspell dicts and associated affix files.
+       (ispell-hunspell-dictionary-alist): New defvar to contain an alist
+       of parsed hunspell dicts and associated parameters.
+       (ispell-set-spellchecker-params):
+       Call `ispell-find-hunspell-dictionaries' if hunspell and not
+       previously done.
+       (ispell-start-process):
+       Call `ispell-hunspell-fill-dictionary-entry' for current
+       dictionary if it is not initialized.
+
 2013-02-28  Stefan Monnier  <monnier@iro.umontreal.ca>
 
        * imenu.el: Comment nitpicks.
index 50a10dba9a2aedfa7fd45208f82e2b2a16a8a9ca..d785b938f6774371bc4aef68f60838fc90bc449f 100644 (file)
@@ -1129,6 +1129,170 @@ Return the new dictionary alist."
              (push (cons aliasname (cdr realdict)) alist))))))
     alist))
 
+;; Make ispell.el work better with hunspell.
+
+(defvar ispell-hunspell-dict-paths-alist nil
+      "Alist of parsed hunspell dicts and associated affix files.
+Will be used to parse corresponding .aff file and create associated
+parameters to be inserted into `ispell-hunspell-dictionary-alist'.
+Internal use.")
+
+(defvar ispell-hunspell-dictionary-alist nil
+      "Alist of parsed hunspell dicts and associated parameters.
+This alist will initially contain names of found dicts.  Associated
+parameters will be added when dict is used for the first time.
+Internal use.")
+
+(defun ispell-hunspell-fill-dictionary-entry (dict)
+  "Fill `ispell-dictionary-alist' uninitialized entries for `DICT' and aliases.
+Value will be extracted from hunspell affix file and used for
+all uninitialized dicts using that affix file."
+  (if (cadr (assoc dict ispell-dictionary-alist))
+      (message "ispell-hfde: Non void entry for %s. Skipping.\n" dict)
+    (let ((dict-alias (cadr (assoc dict ispell-hunspell-dictionary-equivs-alist)))
+         (use-for-dicts (list dict))
+         (dict-args-cdr (cdr (ispell-parse-hunspell-affix-file dict)))
+         newlist)
+      ;; Get a list of unitialized dicts using the same affix file.
+      (dolist (dict-equiv-alist-entry ispell-hunspell-dictionary-equivs-alist)
+       (let ((dict-equiv-key (car dict-equiv-alist-entry))
+             (dict-equiv-value (cadr dict-equiv-alist-entry)))
+         (if (or (member dict dict-equiv-alist-entry)
+                 (member dict-alias dict-equiv-alist-entry))
+             (dolist ( tmp-dict (list dict-equiv-key dict-equiv-value))
+               (if (cadr (assoc tmp-dict ispell-dictionary-alist))
+                   (ispell-print-if-debug (format "ispell-hfde: %s already expanded. Skipping.\n" tmp-dict))
+                 (add-to-list 'use-for-dicts tmp-dict))))))
+      (ispell-print-if-debug (format "ispell-hfde: Filling %s entry. Use for %s.\n" dict use-for-dicts))
+      ;; The final loop
+      (dolist (entry ispell-dictionary-alist)
+       (if (member (car entry) use-for-dicts)
+           (add-to-list 'newlist
+                        (append (list (car entry)) dict-args-cdr))
+         (add-to-list 'newlist entry)))
+      (setq ispell-dictionary-alist newlist))))
+
+(defun ispell-parse-hunspell-affix-file (dict-key)
+  "Parse hunspell affix file to extract parameters for `DICT-KEY'.
+Return a list in `ispell-dictionary-alist' format."
+  (let ((affix-file (cadr (assoc dict-key ispell-hunspell-dict-paths-alist))))
+    (unless affix-file
+      (error "ispell-phaf: No matching entry for %s.\n" dict-name))
+    (if (file-exists-p affix-file)
+       (let ((dict-name (file-name-sans-extension (file-name-nondirectory affix-file)))
+             otherchars-string otherchars-list)
+         (with-temp-buffer
+           (insert-file-contents affix-file)
+           (setq otherchars-string
+                 (save-excursion
+                   (beginning-of-buffer)
+                   (if (search-forward-regexp "^WORDCHARS +" nil t )
+                       (buffer-substring (point)
+                                         (progn (end-of-line) (point))))))
+           ;; Remove trailing whitespace and extra stuff. Make list if non-nil.
+           (setq otherchars-list
+                 (if otherchars-string
+                     (split-string
+                      (if (string-match " +.*$" otherchars-string)
+                          (replace-match "" nil nil otherchars-string)
+                        otherchars-string)
+                      "" t)))
+
+           ;; Fill dict entry
+           (list dict-key
+                 "[[:alpha:]]"
+                 "[^[:alpha:]]"
+                 (if otherchars-list
+                     (regexp-opt otherchars-list)
+                   "")
+                 t                      ;; many-otherchars-p: We can't tell, set to t
+                 (list "-d" dict-name)
+                 nil                    ;; extended-char-mode: not supported by hunspell
+                 'utf-8)))
+      (error "ispell-phaf: File \"%s\" not found.\n" affix-file))))
+
+(defun ispell-find-hunspell-dictionaries ()
+  "Look for installed hunspell dictionaries.
+Will initialize `ispell-hunspell-dictionary-alist' and
+`ispell-hunspell-dictionary-alist' after values found
+and remove `ispell-hunspell-dictionary-equivs-alist'
+entries if a specific dict was found."
+  (let ((hunspell-found-dicts
+        (split-string
+         (with-temp-buffer
+           (ispell-call-process ispell-program-name
+                                null-device
+                                t
+                                nil
+                                "-D")
+           (buffer-string))
+         "[\n\r]+"
+         t))
+       hunspell-default-dict
+       hunspell-default-dict-entry)
+    (dolist (dict hunspell-found-dicts)
+      (let* ((full-name (file-name-nondirectory dict))
+            (basename  (file-name-sans-extension full-name))
+            (affix-file (concat dict ".aff")))
+       (if (string-match "\\.aff$" dict)
+           ;; Found default dictionary
+           (if hunspell-default-dict
+               (error "ispell-fhd: Default dict already defined as %s. Not using %s.\n"
+                      hunspell-default-dict dict)
+             (setq affix-file dict)
+             (setq hunspell-default-dict (list basename affix-file)))
+         (if (and (not (assoc basename ispell-hunspell-dict-paths-alist))
+                  (file-exists-p affix-file))
+             ;; Entry has an associated .aff file and no previous value.
+             (progn
+               (ispell-print-if-debug
+                (format "++ ispell-fhd: dict-entry:%s name:%s basename:%s affix-file:%s\n"
+                        dict full-name basename affix-file))
+               (add-to-list 'ispell-hunspell-dict-paths-alist
+                            (list basename affix-file)))
+           (ispell-print-if-debug
+            (format "-- ispell-fhd: Skipping entry: %s\n" dict))))))
+    ;; Remove entry from aliases alist if explicit dict was found.
+    (let (newlist)
+      (dolist (dict ispell-hunspell-dictionary-equivs-alist)
+       (if (assoc (car dict) ispell-hunspell-dict-paths-alist)
+           (ispell-print-if-debug
+            (format "-- ispell-fhd: Excluding %s alias. Standalone dict found.\n"
+                    (car dict)))
+         (add-to-list 'newlist dict)))
+      (setq ispell-hunspell-dictionary-equivs-alist newlist))
+    ;; Add known hunspell aliases
+    (dolist (dict-equiv ispell-hunspell-dictionary-equivs-alist)
+      (let ((dict-equiv-key (car dict-equiv))
+           (dict-equiv-value (cadr dict-equiv))
+           (exclude-aliases (list   ;; Exclude TeX aliases
+                             "esperanto-tex"
+                             "francais7"
+                             "francais-tex"
+                             "norsk7-tex")))
+       (if (and (assoc dict-equiv-value ispell-hunspell-dict-paths-alist)
+                (not (assoc dict-equiv-key ispell-hunspell-dict-paths-alist))
+                (not (member dict-equiv-key exclude-aliases)))
+           (let ((affix-file (cadr (assoc dict-equiv-value ispell-hunspell-dict-paths-alist))))
+             (ispell-print-if-debug (format "++ ispell-fhd: Adding alias %s -> %s.\n"
+                                            dict-equiv-key affix-file))
+             (add-to-list
+              'ispell-hunspell-dict-paths-alist
+              (list dict-equiv-key affix-file))))))
+    ;; Parse and set values for default dictionary.
+    (setq hunspell-default-dict (car hunspell-default-dict))
+    (setq hunspell-default-dict-entry
+         (ispell-parse-hunspell-affix-file hunspell-default-dict))
+    ;; Create an alist of found dicts with only names, except for default dict.
+    (setq ispell-hunspell-dictionary-alist
+         (list (append (list nil) (cdr hunspell-default-dict-entry))))
+    (dolist (dict (mapcar 'car ispell-hunspell-dict-paths-alist))
+      (if (string= dict hunspell-default-dict)
+         (add-to-list 'ispell-hunspell-dictionary-alist
+                      hunspell-default-dict-entry)
+       (add-to-list 'ispell-hunspell-dictionary-alist
+                    (list dict))))))
+
 ;; Set params according to the selected spellchecker
 
 (defvar ispell-last-program-name nil
@@ -1154,20 +1318,30 @@ aspell is used along with Emacs).")
                   (setq ispell-library-directory (ispell-check-version))
                   t)
               (error nil))
-            ispell-really-aspell
             ispell-encoding8-command
             ispell-emacs-alpha-regexp)
-       (unless ispell-aspell-dictionary-alist
-         (ispell-find-aspell-dictionaries)))
-
-    ;; Substitute ispell-dictionary-alist with the list of dictionaries
-    ;; corresponding to the given spellchecker. If a recent aspell, use
-    ;; the list of really installed dictionaries and add to it elements
-    ;; of the original list that are not present there. Allow distro info.
+       ;; auto-detection will only be used if spellchecker is not
+       ;; ispell, supports a way  to set communication to UTF-8 and
+       ;; Emacs flavor supports [:alpha:]
+       (if ispell-really-aspell
+           (or ispell-aspell-dictionary-alist
+               (ispell-find-aspell-dictionaries))
+         (if ispell-really-hunspell
+             (or ispell-hunspell-dictionary-alist
+                 (ispell-find-hunspell-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
+    ;; installed dictionaries and add to it elements of the original
+    ;; list that are not present there. Allow distro info.
     (let ((found-dicts-alist
-          (if (and ispell-really-aspell
-                   ispell-encoding8-command)
-              ispell-aspell-dictionary-alist
+          (if (and ispell-encoding8-command
+                   ispell-emacs-alpha-regexp)
+              (if ispell-really-aspell
+                  ispell-aspell-dictionary-alist
+                (if ispell-really-hunspell
+                    ispell-hunspell-dictionary-alist))
             nil))
          (ispell-dictionary-base-alist ispell-dictionary-base-alist)
          ispell-base-dicts-override-alist ; Override only base-dicts-alist
@@ -1237,19 +1411,21 @@ aspell is used along with Emacs).")
     (if ispell-emacs-alpha-regexp
        (let (tmp-dicts-alist)
          (dolist (adict ispell-dictionary-alist)
-           (add-to-list 'tmp-dicts-alist
-                        (list
-                         (nth 0 adict)  ; dict name
-                         "[[:alpha:]]"  ; casechars
-                         "[^[:alpha:]]" ; not-casechars
-                         (nth 3 adict)  ; otherchars
-                         (nth 4 adict)  ; many-otherchars-p
-                         (nth 5 adict)  ; ispell-args
-                         (nth 6 adict)  ; extended-character-mode
-                         (if ispell-encoding8-command
-                             'utf-8
-                           (nth 7 adict)))))
-         (setq ispell-dictionary-alist tmp-dicts-alist)))))
+           (if (cadr adict) ;; Do not touch hunspell uninitialized entries
+               (add-to-list 'tmp-dicts-alist
+                            (list
+                             (nth 0 adict)  ; dict name
+                             "[[:alpha:]]"  ; casechars
+                             "[^[:alpha:]]" ; not-casechars
+                             (nth 3 adict)  ; otherchars
+                             (nth 4 adict)  ; many-otherchars-p
+                             (nth 5 adict)  ; ispell-args
+                             (nth 6 adict)  ; extended-character-mode
+                             (if ispell-encoding8-command
+                                 'utf-8
+                               (nth 7 adict))))
+             (add-to-list 'tmp-dicts-alist adict)))
+         (setq ispell-dictionary-alist tmp-dicts-alist)))))
 
 (defun ispell-valid-dictionary-list ()
   "Return a list of valid dictionaries.
@@ -2737,6 +2913,12 @@ When asynchronous processes are not supported, `run' is always returned."
 Keeps argument list for future Ispell invocations for no async support."
   ;; `ispell-current-dictionary' and `ispell-current-personal-dictionary'
   ;; are properly set in `ispell-internal-change-dictionary'.
+
+  ;; Parse hunspell affix file if using hunspell and entry is uninitialized.
+  (if ispell-really-hunspell
+      (or (cadr (assoc ispell-current-dictionary ispell-dictionary-alist))
+         (ispell-hunspell-fill-dictionary-entry ispell-current-dictionary)))
+
   (let* ((default-directory
            (if (and (file-directory-p default-directory)
                     (file-readable-p default-directory))