1
0
mirror of https://git.savannah.gnu.org/git/emacs.git synced 2024-11-27 07:37:33 +00:00

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.

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.
This commit is contained in:
Agustín Martín 2013-02-28 20:01:34 +01:00
parent 930de676ac
commit ef3761e3f4
2 changed files with 227 additions and 23 deletions

View 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.

View 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)))
;; 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, use
;; the list of really installed dictionaries and add to it elements
;; of the original list that are not present there. Allow distro info.
;; 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))