aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAgustín Martín2013-02-28 20:01:34 +0100
committerAgustín Martín2013-02-28 20:01:34 +0100
commitef3761e3f4407fdd4d7329d296a00f51c66b52ec (patch)
tree891c152bab7b8c905bbae6685df3bb4ff8233e0f
parent930de676ac040f3e459bbd1ddfec0aa36131297d (diff)
downloademacs-ef3761e3f4407fdd4d7329d296a00f51c66b52ec.tar.gz
emacs-ef3761e3f4407fdd4d7329d296a00f51c66b52ec.zip
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.
-rw-r--r--lisp/ChangeLog22
-rw-r--r--lisp/textmodes/ispell.el230
2 files changed, 228 insertions, 24 deletions
diff --git a/lisp/ChangeLog b/lisp/ChangeLog
index a8d66839d12..66b18651695 100644
--- a/lisp/ChangeLog
+++ b/lisp/ChangeLog
@@ -1,3 +1,25 @@
12013-02-28 Agustín Martín Domingo <agustin.martin@hispalinux.es>
2
3 Initial support for hunspell dictionaries auto-detection (Bug#13639)
4
5 * textmodes/ispell.el (ispell-find-hunspell-dictionaries):
6 Ask hunspell about available and default dictionaries.
7 (ispell-parse-hunspell-affix-file): Extract relevant info from
8 hunspell affix file.
9 (ispell-hunspell-fill-dictionary-entry): Fill non-initialized
10 `ispell-dictionary-alist' entry for given dictionary after info
11 provided by `ispell-parse-hunspell-affix-file'.
12 (ispell-hunspell-dict-paths-alist): New defvar to contain an alist
13 of parsed hunspell dicts and associated affix files.
14 (ispell-hunspell-dictionary-alist): New defvar to contain an alist
15 of parsed hunspell dicts and associated parameters.
16 (ispell-set-spellchecker-params):
17 Call `ispell-find-hunspell-dictionaries' if hunspell and not
18 previously done.
19 (ispell-start-process):
20 Call `ispell-hunspell-fill-dictionary-entry' for current
21 dictionary if it is not initialized.
22
12013-02-28 Stefan Monnier <monnier@iro.umontreal.ca> 232013-02-28 Stefan Monnier <monnier@iro.umontreal.ca>
2 24
3 * imenu.el: Comment nitpicks. 25 * imenu.el: Comment nitpicks.
diff --git a/lisp/textmodes/ispell.el b/lisp/textmodes/ispell.el
index 50a10dba9a2..d785b938f67 100644
--- a/lisp/textmodes/ispell.el
+++ b/lisp/textmodes/ispell.el
@@ -1129,6 +1129,170 @@ Return the new dictionary alist."
1129 (push (cons aliasname (cdr realdict)) alist)))))) 1129 (push (cons aliasname (cdr realdict)) alist))))))
1130 alist)) 1130 alist))
1131 1131
1132;; Make ispell.el work better with hunspell.
1133
1134(defvar ispell-hunspell-dict-paths-alist nil
1135 "Alist of parsed hunspell dicts and associated affix files.
1136Will be used to parse corresponding .aff file and create associated
1137parameters to be inserted into `ispell-hunspell-dictionary-alist'.
1138Internal use.")
1139
1140(defvar ispell-hunspell-dictionary-alist nil
1141 "Alist of parsed hunspell dicts and associated parameters.
1142This alist will initially contain names of found dicts. Associated
1143parameters will be added when dict is used for the first time.
1144Internal use.")
1145
1146(defun ispell-hunspell-fill-dictionary-entry (dict)
1147 "Fill `ispell-dictionary-alist' uninitialized entries for `DICT' and aliases.
1148Value will be extracted from hunspell affix file and used for
1149all uninitialized dicts using that affix file."
1150 (if (cadr (assoc dict ispell-dictionary-alist))
1151 (message "ispell-hfde: Non void entry for %s. Skipping.\n" dict)
1152 (let ((dict-alias (cadr (assoc dict ispell-hunspell-dictionary-equivs-alist)))
1153 (use-for-dicts (list dict))
1154 (dict-args-cdr (cdr (ispell-parse-hunspell-affix-file dict)))
1155 newlist)
1156 ;; Get a list of unitialized dicts using the same affix file.
1157 (dolist (dict-equiv-alist-entry ispell-hunspell-dictionary-equivs-alist)
1158 (let ((dict-equiv-key (car dict-equiv-alist-entry))
1159 (dict-equiv-value (cadr dict-equiv-alist-entry)))
1160 (if (or (member dict dict-equiv-alist-entry)
1161 (member dict-alias dict-equiv-alist-entry))
1162 (dolist ( tmp-dict (list dict-equiv-key dict-equiv-value))
1163 (if (cadr (assoc tmp-dict ispell-dictionary-alist))
1164 (ispell-print-if-debug (format "ispell-hfde: %s already expanded. Skipping.\n" tmp-dict))
1165 (add-to-list 'use-for-dicts tmp-dict))))))
1166 (ispell-print-if-debug (format "ispell-hfde: Filling %s entry. Use for %s.\n" dict use-for-dicts))
1167 ;; The final loop
1168 (dolist (entry ispell-dictionary-alist)
1169 (if (member (car entry) use-for-dicts)
1170 (add-to-list 'newlist
1171 (append (list (car entry)) dict-args-cdr))
1172 (add-to-list 'newlist entry)))
1173 (setq ispell-dictionary-alist newlist))))
1174
1175(defun ispell-parse-hunspell-affix-file (dict-key)
1176 "Parse hunspell affix file to extract parameters for `DICT-KEY'.
1177Return a list in `ispell-dictionary-alist' format."
1178 (let ((affix-file (cadr (assoc dict-key ispell-hunspell-dict-paths-alist))))
1179 (unless affix-file
1180 (error "ispell-phaf: No matching entry for %s.\n" dict-name))
1181 (if (file-exists-p affix-file)
1182 (let ((dict-name (file-name-sans-extension (file-name-nondirectory affix-file)))
1183 otherchars-string otherchars-list)
1184 (with-temp-buffer
1185 (insert-file-contents affix-file)
1186 (setq otherchars-string
1187 (save-excursion
1188 (beginning-of-buffer)
1189 (if (search-forward-regexp "^WORDCHARS +" nil t )
1190 (buffer-substring (point)
1191 (progn (end-of-line) (point))))))
1192 ;; Remove trailing whitespace and extra stuff. Make list if non-nil.
1193 (setq otherchars-list
1194 (if otherchars-string
1195 (split-string
1196 (if (string-match " +.*$" otherchars-string)
1197 (replace-match "" nil nil otherchars-string)
1198 otherchars-string)
1199 "" t)))
1200
1201 ;; Fill dict entry
1202 (list dict-key
1203 "[[:alpha:]]"
1204 "[^[:alpha:]]"
1205 (if otherchars-list
1206 (regexp-opt otherchars-list)
1207 "")
1208 t ;; many-otherchars-p: We can't tell, set to t
1209 (list "-d" dict-name)
1210 nil ;; extended-char-mode: not supported by hunspell
1211 'utf-8)))
1212 (error "ispell-phaf: File \"%s\" not found.\n" affix-file))))
1213
1214(defun ispell-find-hunspell-dictionaries ()
1215 "Look for installed hunspell dictionaries.
1216Will initialize `ispell-hunspell-dictionary-alist' and
1217`ispell-hunspell-dictionary-alist' after values found
1218and remove `ispell-hunspell-dictionary-equivs-alist'
1219entries if a specific dict was found."
1220 (let ((hunspell-found-dicts
1221 (split-string
1222 (with-temp-buffer
1223 (ispell-call-process ispell-program-name
1224 null-device
1225 t
1226 nil
1227 "-D")
1228 (buffer-string))
1229 "[\n\r]+"
1230 t))
1231 hunspell-default-dict
1232 hunspell-default-dict-entry)
1233 (dolist (dict hunspell-found-dicts)
1234 (let* ((full-name (file-name-nondirectory dict))
1235 (basename (file-name-sans-extension full-name))
1236 (affix-file (concat dict ".aff")))
1237 (if (string-match "\\.aff$" dict)
1238 ;; Found default dictionary
1239 (if hunspell-default-dict
1240 (error "ispell-fhd: Default dict already defined as %s. Not using %s.\n"
1241 hunspell-default-dict dict)
1242 (setq affix-file dict)
1243 (setq hunspell-default-dict (list basename affix-file)))
1244 (if (and (not (assoc basename ispell-hunspell-dict-paths-alist))
1245 (file-exists-p affix-file))
1246 ;; Entry has an associated .aff file and no previous value.
1247 (progn
1248 (ispell-print-if-debug
1249 (format "++ ispell-fhd: dict-entry:%s name:%s basename:%s affix-file:%s\n"
1250 dict full-name basename affix-file))
1251 (add-to-list 'ispell-hunspell-dict-paths-alist
1252 (list basename affix-file)))
1253 (ispell-print-if-debug
1254 (format "-- ispell-fhd: Skipping entry: %s\n" dict))))))
1255 ;; Remove entry from aliases alist if explicit dict was found.
1256 (let (newlist)
1257 (dolist (dict ispell-hunspell-dictionary-equivs-alist)
1258 (if (assoc (car dict) ispell-hunspell-dict-paths-alist)
1259 (ispell-print-if-debug
1260 (format "-- ispell-fhd: Excluding %s alias. Standalone dict found.\n"
1261 (car dict)))
1262 (add-to-list 'newlist dict)))
1263 (setq ispell-hunspell-dictionary-equivs-alist newlist))
1264 ;; Add known hunspell aliases
1265 (dolist (dict-equiv ispell-hunspell-dictionary-equivs-alist)
1266 (let ((dict-equiv-key (car dict-equiv))
1267 (dict-equiv-value (cadr dict-equiv))
1268 (exclude-aliases (list ;; Exclude TeX aliases
1269 "esperanto-tex"
1270 "francais7"
1271 "francais-tex"
1272 "norsk7-tex")))
1273 (if (and (assoc dict-equiv-value ispell-hunspell-dict-paths-alist)
1274 (not (assoc dict-equiv-key ispell-hunspell-dict-paths-alist))
1275 (not (member dict-equiv-key exclude-aliases)))
1276 (let ((affix-file (cadr (assoc dict-equiv-value ispell-hunspell-dict-paths-alist))))
1277 (ispell-print-if-debug (format "++ ispell-fhd: Adding alias %s -> %s.\n"
1278 dict-equiv-key affix-file))
1279 (add-to-list
1280 'ispell-hunspell-dict-paths-alist
1281 (list dict-equiv-key affix-file))))))
1282 ;; Parse and set values for default dictionary.
1283 (setq hunspell-default-dict (car hunspell-default-dict))
1284 (setq hunspell-default-dict-entry
1285 (ispell-parse-hunspell-affix-file hunspell-default-dict))
1286 ;; Create an alist of found dicts with only names, except for default dict.
1287 (setq ispell-hunspell-dictionary-alist
1288 (list (append (list nil) (cdr hunspell-default-dict-entry))))
1289 (dolist (dict (mapcar 'car ispell-hunspell-dict-paths-alist))
1290 (if (string= dict hunspell-default-dict)
1291 (add-to-list 'ispell-hunspell-dictionary-alist
1292 hunspell-default-dict-entry)
1293 (add-to-list 'ispell-hunspell-dictionary-alist
1294 (list dict))))))
1295
1132;; Set params according to the selected spellchecker 1296;; Set params according to the selected spellchecker
1133 1297
1134(defvar ispell-last-program-name nil 1298(defvar ispell-last-program-name nil
@@ -1154,20 +1318,30 @@ aspell is used along with Emacs).")
1154 (setq ispell-library-directory (ispell-check-version)) 1318 (setq ispell-library-directory (ispell-check-version))
1155 t) 1319 t)
1156 (error nil)) 1320 (error nil))
1157 ispell-really-aspell
1158 ispell-encoding8-command 1321 ispell-encoding8-command
1159 ispell-emacs-alpha-regexp) 1322 ispell-emacs-alpha-regexp)
1160 (unless ispell-aspell-dictionary-alist 1323 ;; auto-detection will only be used if spellchecker is not
1161 (ispell-find-aspell-dictionaries))) 1324 ;; ispell, supports a way to set communication to UTF-8 and
1162 1325 ;; Emacs flavor supports [:alpha:]
1163 ;; Substitute ispell-dictionary-alist with the list of dictionaries 1326 (if ispell-really-aspell
1164 ;; corresponding to the given spellchecker. If a recent aspell, use 1327 (or ispell-aspell-dictionary-alist
1165 ;; the list of really installed dictionaries and add to it elements 1328 (ispell-find-aspell-dictionaries))
1166 ;; of the original list that are not present there. Allow distro info. 1329 (if ispell-really-hunspell
1330 (or ispell-hunspell-dictionary-alist
1331 (ispell-find-hunspell-dictionaries)))))
1332
1333 ;; Substitute ispell-dictionary-alist with the list of
1334 ;; dictionaries corresponding to the given spellchecker.
1335 ;; If a recent aspell or hunspell, use the list of really
1336 ;; installed dictionaries and add to it elements of the original
1337 ;; list that are not present there. Allow distro info.
1167 (let ((found-dicts-alist 1338 (let ((found-dicts-alist
1168 (if (and ispell-really-aspell 1339 (if (and ispell-encoding8-command
1169 ispell-encoding8-command) 1340 ispell-emacs-alpha-regexp)
1170 ispell-aspell-dictionary-alist 1341 (if ispell-really-aspell
1342 ispell-aspell-dictionary-alist
1343 (if ispell-really-hunspell
1344 ispell-hunspell-dictionary-alist))
1171 nil)) 1345 nil))
1172 (ispell-dictionary-base-alist ispell-dictionary-base-alist) 1346 (ispell-dictionary-base-alist ispell-dictionary-base-alist)
1173 ispell-base-dicts-override-alist ; Override only base-dicts-alist 1347 ispell-base-dicts-override-alist ; Override only base-dicts-alist
@@ -1237,19 +1411,21 @@ aspell is used along with Emacs).")
1237 (if ispell-emacs-alpha-regexp 1411 (if ispell-emacs-alpha-regexp
1238 (let (tmp-dicts-alist) 1412 (let (tmp-dicts-alist)
1239 (dolist (adict ispell-dictionary-alist) 1413 (dolist (adict ispell-dictionary-alist)
1240 (add-to-list 'tmp-dicts-alist 1414 (if (cadr adict) ;; Do not touch hunspell uninitialized entries
1241 (list 1415 (add-to-list 'tmp-dicts-alist
1242 (nth 0 adict) ; dict name 1416 (list
1243 "[[:alpha:]]" ; casechars 1417 (nth 0 adict) ; dict name
1244 "[^[:alpha:]]" ; not-casechars 1418 "[[:alpha:]]" ; casechars
1245 (nth 3 adict) ; otherchars 1419 "[^[:alpha:]]" ; not-casechars
1246 (nth 4 adict) ; many-otherchars-p 1420 (nth 3 adict) ; otherchars
1247 (nth 5 adict) ; ispell-args 1421 (nth 4 adict) ; many-otherchars-p
1248 (nth 6 adict) ; extended-character-mode 1422 (nth 5 adict) ; ispell-args
1249 (if ispell-encoding8-command 1423 (nth 6 adict) ; extended-character-mode
1250 'utf-8 1424 (if ispell-encoding8-command
1251 (nth 7 adict))))) 1425 'utf-8
1252 (setq ispell-dictionary-alist tmp-dicts-alist))))) 1426 (nth 7 adict))))
1427 (add-to-list 'tmp-dicts-alist adict)))
1428 (setq ispell-dictionary-alist tmp-dicts-alist)))))
1253 1429
1254(defun ispell-valid-dictionary-list () 1430(defun ispell-valid-dictionary-list ()
1255 "Return a list of valid dictionaries. 1431 "Return a list of valid dictionaries.
@@ -2737,6 +2913,12 @@ When asynchronous processes are not supported, `run' is always returned."
2737Keeps argument list for future Ispell invocations for no async support." 2913Keeps argument list for future Ispell invocations for no async support."
2738 ;; `ispell-current-dictionary' and `ispell-current-personal-dictionary' 2914 ;; `ispell-current-dictionary' and `ispell-current-personal-dictionary'
2739 ;; are properly set in `ispell-internal-change-dictionary'. 2915 ;; are properly set in `ispell-internal-change-dictionary'.
2916
2917 ;; Parse hunspell affix file if using hunspell and entry is uninitialized.
2918 (if ispell-really-hunspell
2919 (or (cadr (assoc ispell-current-dictionary ispell-dictionary-alist))
2920 (ispell-hunspell-fill-dictionary-entry ispell-current-dictionary)))
2921
2740 (let* ((default-directory 2922 (let* ((default-directory
2741 (if (and (file-directory-p default-directory) 2923 (if (and (file-directory-p default-directory)
2742 (file-readable-p default-directory)) 2924 (file-readable-p default-directory))