-;;; ispell.el --- interface to International Ispell Versions 3.1 and 3.2
+;;; ispell.el --- interface to International Ispell Versions 3.1 and 3.2 -*- lexical-binding:t -*-
-;; Copyright (C) 1994-1995, 1997-2015 Free Software Foundation, Inc.
+;; Copyright (C) 1994-1995, 1997-2016 Free Software Foundation, Inc.
;; Author: Ken Stevens <k.stevens@ieee.org>
;; Maintainer: Ken Stevens <k.stevens@ieee.org>
;; your own dictionaries.
;; Depending on the mail system you use, you may want to include these:
-;; (add-hook 'news-inews-hook 'ispell-message)
-;; (add-hook 'mail-send-hook 'ispell-message)
-;; (add-hook 'mh-before-send-letter-hook 'ispell-message)
+;; (add-hook 'news-inews-hook #'ispell-message)
+;; (add-hook 'mail-send-hook #'ispell-message)
+;; (add-hook 'mh-before-send-letter-hook #'ispell-message)
;; Ispell has a TeX parser and a nroff parser (the default).
;; The parsing is controlled by the variable ispell-parser. Currently
;; Fixed bug in returning to nroff mode from tex mode.
;;; Compatibility code for XEmacs and (not too) older emacsen:
-
-(eval-and-compile ;; Protect against declare-function undefined in XEmacs
- (unless (fboundp 'declare-function) (defmacro declare-function (&rest r))))
-
-(declare-function ispell-check-minver "ispell" (v1 v2))
-(declare-function ispell-looking-back "ispell"
- (regexp &optional limit &rest ignored))
-
-(if (fboundp 'version<=)
- (defalias 'ispell-check-minver 'version<=)
- (defun ispell-check-minver (minver version)
- "Check if string VERSION is at least string MINVER.
+(defalias 'ispell-check-minver
+ (if (fboundp 'version<=) 'version<=
+ (lambda (minver version)
+ "Check if string VERSION is at least string MINVER.
Both must be in [0-9]+.[0-9]+... format. This is a fallback
compatibility function in case `version<=' is not available."
- (let ((pending t)
- (return t)
- start-ver start-mver)
- ;; Loop until an absolute greater or smaller condition is reached
- ;; or until no elements are left in any of version and minver. In
- ;; this case version is exactly the minimal, so return OK.
- (while pending
- (let (ver mver)
- (if (string-match "[0-9]+" version start-ver)
- (setq start-ver (match-end 0)
- ver (string-to-number (match-string 0 version))))
- (if (string-match "[0-9]+" minver start-mver)
- (setq start-mver (match-end 0)
- mver (string-to-number (match-string 0 minver))))
-
- (if (or ver mver)
- (progn
- (or ver (setq ver 0))
- (or mver (setq mver 0))
- ;; If none of below conditions match, this element is the
- ;; same. Go checking next element.
- (if (> ver mver)
- (setq pending nil)
- (if (< ver mver)
- (setq pending nil
- return nil))))
- (setq pending nil))))
- return)))
+ (let ((pending t)
+ (return t)
+ start-ver start-mver)
+ ;; Loop until an absolute greater or smaller condition is reached
+ ;; or until no elements are left in any of version and minver. In
+ ;; this case version is exactly the minimal, so return OK.
+ (while pending
+ (let (ver mver)
+ (if (string-match "[0-9]+" version start-ver)
+ (setq start-ver (match-end 0)
+ ver (string-to-number (match-string 0 version))))
+ (if (string-match "[0-9]+" minver start-mver)
+ (setq start-mver (match-end 0)
+ mver (string-to-number (match-string 0 minver))))
+
+ (if (or ver mver)
+ (progn
+ (or ver (setq ver 0))
+ (or mver (setq mver 0))
+ ;; If none of below conditions match, this element is the
+ ;; same. Go checking next element.
+ (if (> ver mver)
+ (setq pending nil)
+ (if (< ver mver)
+ (setq pending nil
+ return nil))))
+ (setq pending nil))))
+ return))))
;; XEmacs does not have looking-back
-(if (fboundp 'looking-back)
- (defalias 'ispell-looking-back 'looking-back)
- (defun ispell-looking-back (regexp &optional limit &rest ignored)
- "Return non-nil if text before point matches regular expression REGEXP.
+(defalias 'ispell-looking-back
+ (if (fboundp 'looking-back) 'looking-back
+ (lambda (regexp &optional limit &rest ignored)
+ "Return non-nil if text before point matches regular expression REGEXP.
Like `looking-at' except matches before point, and is slower.
LIMIT if non-nil speeds up the search by specifying a minimum
starting position, to avoid checking matches that would start
This is a stripped down compatibility function for use when
full featured `looking-back' function is missing."
- (save-excursion
- (re-search-backward (concat "\\(?:" regexp "\\)\\=") limit t))))
+ (save-excursion
+ (re-search-backward (concat "\\(?:" regexp "\\)\\=") limit t)))))
;;; XEmacs21 does not have `with-no-warnings'. Taken from org mode.
(defmacro ispell-with-no-warnings (&rest body)
;;; Code:
+(eval-when-compile (require 'cl-lib))
+
(defvar mail-yank-prefix)
(defgroup ispell nil
in the message headers, `ispell-local-dictionary' will be set to
DICTIONARY if `ispell-local-dictionary' is not buffer-local.
E.g. you may use the following value:
- '((\"^Newsgroups:[ \\t]*de\\\\.\" . \"deutsch8\")
+ ((\"^Newsgroups:[ \\t]*de\\\\.\" . \"deutsch8\")
(\"^To:[^\\n,]+\\\\.de[ \\t\\n,>]\" . \"deutsch8\"))"
:type '(repeat (cons regexp string))
:group 'ispell)
(defcustom ispell-grep-command
- ;; MS-Windows/MS-DOS have `egrep' as a Unix shell script, so they
- ;; cannot invoke it. Use "grep -E" instead (see ispell-grep-options
- ;; below).
- (if (memq system-type '(windows-nt ms-dos)) "grep" "egrep")
+ "grep"
"Name of the grep command for search processes."
:type 'string
:group 'ispell)
(defcustom ispell-grep-options
- (if (memq system-type '(windows-nt ms-dos)) "-Ei" "-i")
+ "-Ei"
"String of options to use when running the program in `ispell-grep-command'.
-Should probably be \"-i\" or \"-e\".
-Some machines (like the NeXT) don't support \"-i\"."
+Should probably be \"-Ei\"."
:type 'string
:group 'ispell)
"When non-nil ispell uses framepop to display choices in a dedicated frame.
You can set this variable to dynamically use framepop if you are in a
window system by evaluating the following on startup to set this variable:
- (and window-system (condition-case () (require 'framepop) (error nil)))"
+ (and window-system (condition-case () (require \\='framepop) (error nil)))"
:type 'boolean
:group 'ispell)
(defcustom ispell-personal-dictionary nil
"File name of your personal spelling dictionary, or nil.
If nil, the default personal dictionary, (\"~/.ispell_DICTNAME\" for ispell or
-\"~/.aspell.LANG.pws\" for aspell) is used, where DICTNAME is the name of your
+\"~/.aspell.LANG.pws\" for Aspell) is used, where DICTNAME is the name of your
default dictionary and LANG the two letter language code."
:type '(choice file
(const :tag "default" nil))
Each element of this list is also a list:
-\(DICTIONARY-NAME CASECHARS NOT-CASECHARS OTHERCHARS MANY-OTHERCHARS-P
- ISPELL-ARGS EXTENDED-CHARACTER-MODE CHARACTER-SET\)
+ (DICTIONARY-NAME CASECHARS NOT-CASECHARS OTHERCHARS MANY-OTHERCHARS-P
+ ISPELL-ARGS EXTENDED-CHARACTER-MODE CHARACTER-SET)
DICTIONARY-NAME is a possible string value of variable `ispell-dictionary',
nil means the default dictionary.
Note that with \"ispell\" as the speller, the CASECHARS and
OTHERCHARS slots of the alist should contain the same character
set as casechars and otherchars in the LANGUAGE.aff file \(e.g.,
-english.aff\). aspell and hunspell don't have this limitation.")
+english.aff). Aspell and Hunspell don't have this limitation.")
(defvar ispell-really-aspell nil
- "Non-nil if we can use aspell extensions.")
+ "Non-nil if we can use Aspell extensions.")
(defvar ispell-really-hunspell nil
- "Non-nil if we can use hunspell extensions.")
+ "Non-nil if we can use Hunspell 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
-the command line, this variable will contain \"--encoding=\" for aspell
-and \"-i \" for hunspell, so the appropriate mime charset can be selected.
-That will be set in `ispell-check-version' for hunspell >= 1.1.6 and
-aspell >= 0.60.
+the command line, this variable will contain \"--encoding=\" for Aspell
+and \"-i \" for Hunspell, so the appropriate mime charset can be selected.
+That will be set in `ispell-check-version' for Hunspell >= 1.1.6 and
+Aspell >= 0.60.
-For aspell, non-nil also means to try to automatically find its dictionaries.
+For Aspell, non-nil also means to try to automatically find its dictionaries.
-Earlier aspell versions do not consistently support charset encoding. Handling
+Earlier Aspell versions do not consistently support charset encoding. Handling
this would require some extra guessing in `ispell-aspell-find-dictionary'.")
(defvar ispell-aspell-supports-utf8 nil
- "Non-nil if aspell has consistent command line UTF-8 support. Obsolete.
+ "Non-nil if Aspell has consistent command line UTF-8 support. Obsolete.
ispell.el and flyspell.el will use for this purpose the more generic
-variable `ispell-encoding8-command' for both aspell and hunspell. Is left
+variable `ispell-encoding8-command' for both Aspell and Hunspell. Is left
here just for backwards compatibility.")
(make-obsolete-variable 'ispell-aspell-supports-utf8
(setq default-directory (expand-file-name "~/")))
(apply 'call-process-region args)))
+(defvar ispell-debug-buffer)
+
(defun ispell-create-debug-buffer (&optional append)
"Create an ispell debug buffer for debugging output.
-Use APPEND to append the info to previous buffer if exists,
+If APPEND is non-nil, append the info to previous buffer if exists,
otherwise is reset. Returns name of ispell debug buffer.
See `ispell-buffer-with-debug' for an example of use."
(let ((ispell-debug-buffer (get-buffer-create "*ispell-debug*")))
ispell-debug-buffer))
(defsubst ispell-print-if-debug (format &rest args)
- "Print message to `ispell-debug-buffer' buffer if enabled."
+ "Print message using FORMAT and ARGS to `ispell-debug-buffer' buffer if enabled."
(if (boundp 'ispell-debug-buffer)
(with-current-buffer ispell-debug-buffer
(goto-char (point-max))
(defvar ispell-async-processp (and (fboundp 'delete-process)
(fboundp 'process-send-string)
(fboundp 'accept-process-output)
- ;;(fboundp 'start-process)
+ ;;(fboundp 'make-process)
;;(fboundp 'set-process-filter)
;;(fboundp 'process-kill-without-query)
)
;; Make ispell.el work better with aspell.
(defvar ispell-aspell-dictionary-alist nil
- "An alist of parsed aspell dicts and associated parameters.
+ "An alist of parsed Aspell dicts and associated parameters.
Internal use.")
(defun ispell-find-aspell-dictionaries ()
"Find Aspell's dictionaries, and record in `ispell-dictionary-alist'."
(unless (and ispell-really-aspell ispell-encoding8-command)
- (error "This function only works with aspell >= 0.60"))
+ (error "This function only works with Aspell >= 0.60"))
(let* ((dictionaries
(split-string
(with-temp-buffer
(car (split-string (buffer-string)))))
(defun ispell-aspell-find-dictionary (dict-name)
- "For aspell dictionary DICT-NAME, return a list of parameters if an
+ "For Aspell dictionary DICT-NAME, return a list of parameters if an
associated data file is found or nil otherwise. List format is that
of `ispell-dictionary-base-alist' elements."
'utf-8)))))
(defun ispell-aspell-add-aliases (alist)
- "Find aspell's dictionary aliases and add them to dictionary ALIST.
+ "Find Aspell's dictionary aliases and add them to dictionary ALIST.
Return the new dictionary alist."
(let ((aliases
(file-expand-wildcards
;; Make ispell.el work better with hunspell.
(defvar ispell-hunspell-dict-paths-alist nil
- "Alist of parsed hunspell dicts and associated affix files.
+ "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.
+ "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
+ "Fill uninitialized entries in `ispell-dictionary-alist' for DICT and aliases.
+Value of those entries 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)
(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))
+ (dolist (tmp-dict (list dict-equiv-key dict-equiv-value))
(if (cadr (assoc tmp-dict ispell-dictionary-alist))
(ispell-print-if-debug
- "ispell-hfde: %s already expanded. Skipping.\n" tmp-dict)
- (add-to-list 'use-for-dicts tmp-dict))))))
+ "ispell-hfde: %s already expanded; skipping.\n" tmp-dict)
+ (cl-pushnew tmp-dict use-for-dicts :test #'equal))))))
(ispell-print-if-debug
- "ispell-hfde: Filling %s entry. Use for %s.\n" dict use-for-dicts)
+ "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)))
+ (cl-pushnew (if (member (car entry) use-for-dicts)
+ (cons (car entry) dict-args-cdr)
+ entry)
+ newlist :test #'equal))
(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-key))
- (if (not (file-exists-p affix-file))
- (error "ispell-phaf: File \"%s\" not found.\n" 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
- (goto-char (point-min))
- (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)))))
+ "Parse Hunspell affix file to extract parameters for DICT-KEY.
+Return a list in `ispell-dictionary-alist' format.
+
+DICT_KEY can be in the \"DICT1,DICT2,DICT3\" format, to invoke Hunspell
+with a list of dictionaries. The first dictionary in the list must have
+a corresponding .aff affix file; the rest are allowed to have no affix
+files, and will then use the affix file of the preceding dictionary that
+did."
+ (let ((dict-list (split-string dict-key "," t))
+ (first-p t)
+ (dict-arg "")
+ otherchars-list)
+ (dolist (dict-key dict-list)
+ (let ((affix-file
+ (cadr (assoc dict-key ispell-hunspell-dict-paths-alist))))
+ (unless affix-file
+ (error "ispell-phaf: No matching entry for %s in `ispell-hunspell-dict-paths-alist'.\n" dict-key))
+ (if (and first-p (not (file-exists-p affix-file)))
+ (error "ispell-phaf: File \"%s\" not found.\n" affix-file))
+ (and first-p (setq first-p nil))
+ (let ((dict-name (file-name-sans-extension
+ (file-name-nondirectory affix-file)))
+ otherchars-string)
+ (with-temp-buffer
+ (insert-file-contents affix-file)
+ (setq otherchars-string
+ (save-excursion
+ (goto-char (point-min))
+ (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.
+ (if otherchars-string
+ (let* ((otherchars-string
+ ;; Remove trailing junk.
+ (substring otherchars-string
+ 0 (string-match " +" otherchars-string)))
+ (chars-list (append otherchars-string nil)))
+ (setq chars-list (delq ?\ chars-list))
+ (dolist (ch chars-list)
+ (cl-pushnew ch otherchars-list :test #'equal)))))
+ ;; Cons the argument for the -d switch.
+ (setq dict-arg (concat dict-arg
+ (if (> (length dict-arg) 0) ",")
+ dict-name)))))
+
+ ;; Fill dict entry
+ (list dict-key
+ "[[:alpha:]]"
+ "[^[:alpha:]]"
+ (if otherchars-list
+ (regexp-opt (mapcar #'char-to-string otherchars-list))
+ "")
+ t ; many-otherchars-p: We can't tell, set to t.
+ (list "-d" dict-arg)
+ nil ; extended-char-mode: not supported by hunspell!
+ 'utf-8)))
+
+(defun ispell-hunspell-add-multi-dic (dict)
+ "Add DICT of the form \"DICT1,DICT2,...\" to `ispell-dictionary-alist'.
+
+Invoke this command before you want to start Hunspell for the first time
+with a particular combination of dictionaries. The first dictionary
+in the list must have an affix file where Hunspell affix files are kept."
+ (interactive "sMulti-dictionary combination: ")
+ ;; Make sure the first dictionary in the list is known to us.
+ (let ((first-dict (car (split-string dict "," t))))
+ (unless ispell-hunspell-dictionary-alist
+ (ispell-find-hunspell-dictionaries)
+ (setq ispell-dictionary-alist ispell-hunspell-dictionary-alist))
+ (or (assoc first-dict ispell-local-dictionary-alist)
+ (assoc first-dict ispell-dictionary-alist)
+ (error "Unknown dictionary: %s" first-dict)))
+ (cl-pushnew (list dict '()) ispell-dictionary-alist :test #'equal)
+ (ispell-hunspell-fill-dictionary-entry dict))
(defun ispell-find-hunspell-dictionaries ()
- "Look for installed hunspell dictionaries.
+ "Look for installed Hunspell dictionaries.
Will initialize `ispell-hunspell-dictionary-alist' and
`ispell-hunspell-dictionary-alist' after values found
and remove `ispell-dicts-name2locale-equivs-alist'
-entries if a specific dict was found."
+entries if a specific dictionary was found."
(let ((hunspell-found-dicts
(split-string
(with-temp-buffer
(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"
+ (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)))
(ispell-print-if-debug
"++ 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)))
+ (cl-pushnew (list basename affix-file)
+ ispell-hunspell-dict-paths-alist :test #'equal))
(ispell-print-if-debug
"-- ispell-fhd: Skipping entry: %s\n" dict)))))
;; Remove entry from aliases alist if explicit dict was found.
(dolist (dict ispell-dicts-name2locale-equivs-alist)
(if (assoc (car dict) ispell-hunspell-dict-paths-alist)
(ispell-print-if-debug
- "-- ispell-fhd: Excluding %s alias. Standalone dict found.\n"
+ "-- ispell-fhd: Excluding %s alias. Standalone dict found.\n"
(car dict))
- (add-to-list 'newlist dict)))
+ (cl-pushnew dict newlist :test #'equal)))
(setq ispell-dicts-name2locale-equivs-alist newlist))
;; Add known hunspell aliases
(dolist (dict-equiv ispell-dicts-name2locale-equivs-alist)
ispell-hunspell-dict-paths-alist))))
(ispell-print-if-debug "++ 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))))))
+ (cl-pushnew (list dict-equiv-key affix-file)
+ ispell-hunspell-dict-paths-alist :test #'equal)))))
;; 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))))))
+ (list (cons nil (cdr hunspell-default-dict-entry))))
+ (dolist (dict (mapcar #'car ispell-hunspell-dict-paths-alist))
+ (cl-pushnew (if (string= dict hunspell-default-dict)
+ hunspell-default-dict-entry
+ (list dict))
+ ispell-hunspell-dictionary-alist :test #'equal))))
;; Set params according to the selected spellchecker
(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 Hunspell equiv for \"%s\". Skipping."
dict-name)
(setq skip-dict t)))
(unless skip-dict
- (add-to-list 'tmp-dicts-alist
- (list
- dict-name ; dict name
- (nth 1 adict) ; casechars
- (nth 2 adict) ; not-casechars
- (nth 3 adict) ; otherchars
- (nth 4 adict) ; many-otherchars-p
- ispell-args ; ispell-args
- (nth 6 adict) ; extended-character-mode
- (nth 7 adict) ; dict encoding
- ))))
+ (cl-pushnew (list
+ dict-name ; dict name
+ (nth 1 adict) ; casechars
+ (nth 2 adict) ; not-casechars
+ (nth 3 adict) ; otherchars
+ (nth 4 adict) ; many-otherchars-p
+ ispell-args ; ispell-args
+ (nth 6 adict) ; extended-character-mode
+ (nth 7 adict) ; dict encoding
+ )
+ tmp-dicts-alist :test #'equal)))
(setq ispell-dictionary-base-alist tmp-dicts-alist))))
(run-hooks 'ispell-initialize-spellchecker-hook)
ispell-base-dicts-override-alist
ispell-dictionary-base-alist))
(unless (assoc (car dict) all-dicts-alist)
- (add-to-list 'all-dicts-alist dict)))
+ (push dict all-dicts-alist)))
(setq ispell-dictionary-alist all-dicts-alist))
;; If Emacs flavor supports [:alpha:] use it for global dicts. If
(if ispell-emacs-alpha-regexp
(let (tmp-dicts-alist)
(dolist (adict ispell-dictionary-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)))
+ (cl-pushnew (if (cadr adict) ;; Do not touch hunspell uninitialized entries
+ (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)))
+ adict)
+ tmp-dicts-alist :test #'equal))
(setq ispell-dictionary-alist tmp-dicts-alist)))))
(defun ispell-valid-dictionary-list ()
(defvar ispell-current-dictionary nil
"The name of the current dictionary, or nil for the default.
-This is passed to the ispell process using the `-d' switch and is
+This is passed to the Ispell process using the `-d' switch and is
used as key in `ispell-local-dictionary-alist' and `ispell-dictionary-alist'.")
(defvar ispell-current-personal-dictionary nil
"The name of the current personal dictionary, or nil for the default.
-This is passed to the ispell process using the `-p' switch.")
+This is passed to the Ispell process using the `-p' switch.")
(defun ispell-decode-string (str)
"Decodes multibyte character strings.
a `~' followed by an extended-character mode -- such as `~.tex'.
The last occurring definition in the buffer will be used.")
+(defun ispell--\\w-filter (char)
+ "Return CHAR in a string when CHAR doesn't have \"word\" syntax,
+nil otherwise. CHAR must be a character."
+ (let ((str (string char)))
+ (and
+ (not (string-match "\\w" str))
+ str)))
+
+(defun ispell--make-\\w-expression (chars)
+ "Make a regular expression like \"\\(\\w\\|[-_]\\)\".
+This (parenthesized) expression matches either a character of
+\"word\" syntax or one in CHARS.
+
+CHARS is a string of characters. A member of CHARS is omitted
+from the expression if it already has word syntax. (Be careful
+about special characters such as ?\\, ?^, ?], and ?- in CHARS.)
+If after this filtering there are no chars left, or only one, a
+special form of the expression is generated."
+ (let ((filtered
+ (mapconcat #'ispell--\\w-filter chars "")))
+ (concat
+ "\\(\\w"
+ (cond
+ ((equal filtered "")
+ "\\)")
+ ((eq (length filtered) 1)
+ (concat "\\|" filtered "\\)"))
+ (t
+ (concat "\\|[" filtered "]\\)"))))))
+
+(defun ispell--make-filename-or-URL-re ()
+ "Construct a regexp to match some file names or URLs or email addresses.
+The expression is crafted to match as great a variety of these
+objects as practicable, without too many false matches happening."
+ (concat ;"\\(--+\\|_+\\|"
+ "\\(/\\w\\|\\("
+ (ispell--make-\\w-expression "-_")
+ "+[.:@]\\)\\)"
+ (ispell--make-\\w-expression "-_")
+ "*\\([.:/@]+"
+ (ispell--make-\\w-expression "-_~=?&")
+ "+\\)+"
+ ;"\\)"
+ ))
+
;;;###autoload
(defvar ispell-skip-region-alist
`((ispell-words-keyword forward-line)
;; Matches e-mail addresses, file names, http addresses, etc. The
;; `-+' `_+' patterns are necessary for performance reasons when
;; `-' or `_' part of word syntax.
- (,(purecopy "\\(--+\\|_+\\|\\(/\\w\\|\\(\\(\\w\\|[-_]\\)+[.:@]\\)\\)\\(\\w\\|[-_]\\)*\\([.:/@]+\\(\\w\\|[-_~=?&]\\)+\\)+\\)"))
+; (,(purecopy "\\(--+\\|_+\\|\\(/\\w\\|\\(\\(\\w\\|[-_]\\)+[.:@]\\)\\)\\(\\w\\|[-_]\\)*\\([.:/@]+\\(\\w\\|[-_~=?&]\\)+\\)+\\)"))
;; above checks /.\w sequences
;;("\\(--+\\|\\(/\\|\\(\\(\\w\\|[-_]\\)+[.:@]\\)\\)\\(\\w\\|[-_]\\)*\\([.:/@]+\\(\\w\\|[-_~=?&]\\)+\\)+\\)")
;; This is a pretty complex regexp. It can be simplified to the following:
("\\\\add\\(tocontents\\|vspace\\)" ispell-tex-arg-end)
("\\\\\\([aA]lph\\|arabic\\)" ispell-tex-arg-end)
;;("\\\\author" ispell-tex-arg-end)
+ ("\\\\cref" ispell-tex-arg-end)
("\\\\bibliographystyle" ispell-tex-arg-end)
("\\\\makebox" ispell-tex-arg-end 0)
("\\\\e?psfig" ispell-tex-arg-end)
You can set this variable in hooks in your init file -- eg:
-\(add-hook 'tex-mode-hook (lambda () (setq ispell-parser 'tex)))")
+\(add-hook \\='tex-mode-hook (lambda () (setq ispell-parser \\='tex)))")
(defvar ispell-region-end (make-marker)
"Marker that allows spelling continuations.")
(defun ispell-accept-output (&optional timeout-secs timeout-msecs)
- "Wait for output from ispell process, or TIMEOUT-SECS and TIMEOUT-MSECS.
+ "Wait for output from Ispell process, or TIMEOUT-SECS and TIMEOUT-MSECS.
If asynchronous subprocesses are not supported, call function `ispell-filter'
-and pass it the output of the last ispell invocation."
+and pass it the output of the last Ispell invocation."
(if ispell-async-processp
(accept-process-output ispell-process timeout-secs timeout-msecs)
(if (null ispell-process)
(erase-buffer)))))))
(defun ispell-send-replacement (misspelled replacement)
- "Notify aspell that MISSPELLED should be spelled REPLACEMENT.
-This allows it to improve the suggestion list based on actual misspellings."
+ "Notify Aspell that MISSPELLED should be spelled REPLACEMENT.
+This allows improving the suggestion list based on actual misspellings."
(and ispell-really-aspell
(ispell-send-string (concat "$$ra " misspelled "," replacement "\n"))))
If optional argument FOLLOWING is non-nil or if `ispell-following-word'
is non-nil when called interactively, then the following word
-\(rather than preceding\) is checked when the cursor is not over a word.
+\(rather than preceding) is checked when the cursor is not over a word.
When the optional argument QUIETLY is non-nil or `ispell-quietly' is non-nil
when called interactively, non-corrective messages are suppressed.
nil word is correct or spelling is accepted.
0 word is inserted into buffer-local definitions.
\"word\" word corrected from word list.
-\(\"word\" arg\) word is hand entered.
+\(\"word\" arg) word is hand entered.
quit spell session exited."
(interactive (list ispell-following-word ispell-quietly current-prefix-arg t))
(cond
"Return the word for spell-checking according to ispell syntax.
If optional argument FOLLOWING is non-nil or if `ispell-following-word'
is non-nil when called interactively, then the following word
-\(rather than preceding\) is checked when the cursor is not over a word.
+\(rather than preceding) is checked when the cursor is not over a word.
Optional second argument contains otherchars that can be included in word
many times (see the doc string of `ispell-dictionary-alist' for details
about otherchars).
(setq ispell-pdict-modified-p nil))
+(defvar ispell-update-post-hook nil
+ "A normal hook invoked from the ispell command loop.
+It is called once per iteration, before displaying a prompt to
+the user.")
+
(defun ispell-command-loop (miss guess word start end)
"Display possible corrections from list MISS.
GUESS lists possibly valid affix construction of WORD.
(insert "\n\t"))
(insert (car guess) " ")
(setq guess (cdr guess)))
- (insert "\nUse option `i' to accept this spelling and put it in your private dictionary.\n")))
+ (insert (substitute-command-keys
+ "\nUse option `i' to accept this spelling and put it in your private dictionary.\n"))))
(while choices
(when (> (+ 7 (current-column)
(length (car choices))
count (ispell-int-char (1+ count))))
(setq count (ispell-int-char (- count ?0 skipped))))
+ (run-hooks 'ispell-update-post-hook)
+
;; ensure word is visible
- (if (not (pos-visible-in-window-p end))
+ (if (not (pos-visible-in-window-group-p end))
(sit-for 0))
;; Display choices for misspelled word.
nil)
((or (= char ?a) (= char ?A)) ; accept word without insert
(ispell-send-string (concat "@" word "\n"))
- (add-to-list 'ispell-buffer-session-localwords word)
+ (cl-pushnew word ispell-buffer-session-localwords
+ :test #'equal)
(and (fboundp 'flyspell-unhighlight-at)
(flyspell-unhighlight-at start))
(or ispell-buffer-local-name ; session localwords might conflict
(defun ispell-lookup-words (word &optional lookup-dict)
"Look up WORD in optional word-list dictionary LOOKUP-DICT.
A `*' serves as a wild card. If no wild cards, `look' is used if it exists.
-Otherwise the variable `ispell-grep-command' contains the command used to
-search for the words (usually egrep).
+Otherwise the variable `ispell-grep-command' contains the command
+\(usually \"grep\") used to search for the words.
Optional second argument contains the dictionary to use; the default is
`ispell-alternate-dictionary', overridden by `ispell-complete-word-dict'
;; This is the case when a process dies or fails. The default behavior
;; in this case treats the next input received as fresh input.
-(defun ispell-filter (process output)
+(defun ispell-filter (_process output)
"Output filter function for ispell, grep, and look."
(let ((start 0)
(continue t)
(regexp-quote (buffer-substring-no-properties start end))
"\\b"))
(isearch-regexp t)
+ (isearch-regexp-function nil)
(isearch-case-fold-search nil)
(isearch-forward t)
(isearch-other-end start)
(prog1
(condition-case nil
(split-window
- nil (- ispell-choices-win-default-height) 'above)
+ ;; Chose the last of a window group, since
+ ;; otherwise, the lowering of another window's
+ ;; TL corner would cause the logical order of
+ ;; the windows to be changed.
+ (car (last (selected-window-group)))
+ (- ispell-choices-win-default-height) 'above)
(error nil))
(modify-frame-parameters frame '((unsplittable . t))))))
(and (not unsplittable)
(condition-case nil
(split-window
- nil (- ispell-choices-win-default-height) 'above)
+ ;; See comment above.
+ (car (last (selected-window-group)))
+ (- ispell-choices-win-default-height) 'above)
(error nil)))
(display-buffer buffer))))
(if (not window)
(ispell-send-string "\032\n") ; so Ispell prints version and exits
t)))
-
(defun ispell-init-process ()
"Check status of Ispell process and start if necessary."
(let* (;; Basename of dictionary used by the spell-checker
(dict-bname (or (car (cdr (member "-d" (ispell-get-ispell-args))))
ispell-current-dictionary))
;; The directory where process was started.
- (current-ispell-directory default-directory)
+ (current-ispell-directory default-directory) ;FIXME: Unused?
;; The default directory for the process.
;; Use "~/" as default-directory unless using Ispell with per-dir
;; personal dictionaries and not in a minibuffer under XEmacs
;; Otherwise we get cool errors like "Can't open ".
(sleep-for 1)
(ispell-accept-output 3)
- (error "%s" (mapconcat 'identity ispell-filter "\n"))))
+ (error "%s" (mapconcat #'identity ispell-filter "\n"))))
(setq ispell-filter nil) ; Discard version ID line
(let ((extended-char-mode (ispell-get-extended-character-mode)))
(if extended-char-mode ; ~ extended character mode
(list (completing-read
"Use new dictionary (RET for current, SPC to complete): "
(and (fboundp 'ispell-valid-dictionary-list)
- (mapcar 'list (ispell-valid-dictionary-list)))
+ (mapcar #'list (ispell-valid-dictionary-list)))
nil t)
current-prefix-arg))
(ispell-set-spellchecker-params) ; Initialize variables and dicts alists
Includes `ispell-skip-region-alist' plus tex, tib, html, and comment keys.
Must be called after `ispell-buffer-local-parsing' due to dependence on mode."
(mapconcat
- 'identity
+ #'identity
(delq nil
(list
;; messages
(if (string= "" comment-end) "^" (regexp-quote comment-end)))
(if (and (null ispell-check-comments) comment-start)
(regexp-quote comment-start))
- (ispell-begin-skip-region ispell-skip-region-alist)))
+ (ispell-begin-skip-region ispell-skip-region-alist)
+ (ispell--make-filename-or-URL-re)))
"\\|"))
The list is of the form described by variable `ispell-skip-region-alist'.
Must be called after `ispell-buffer-local-parsing' due to dependence on mode."
(let ((skip-alist ispell-skip-region-alist))
+ (setq skip-alist (append (list (list (ispell--make-filename-or-URL-re)))
+ skip-alist))
;; only additional explicit region definition is tex.
(if (eq ispell-parser 'tex)
(setq case-fold-search nil
;;;###autoload
(defun ispell-buffer-with-debug (&optional append)
"`ispell-buffer' with some output sent to `ispell-debug-buffer' buffer.
-Use APPEND to append the info to previous buffer if exists."
+If APPEND is non-n il, append the info to previous buffer if exists."
(interactive)
(let ((ispell-debug-buffer (ispell-create-debug-buffer append)))
(ispell-buffer)))
;;;###autoload
(defun ispell-complete-word (&optional interior-frag)
- "Try to complete the word before or under point.
-If optional INTERIOR-FRAG is non-nil then the word may be a character
+ "Try to complete the word before or at point.
+If optional INTERIOR-FRAG is non-nil, then the word may be a character
sequence inside of a word.
Standard ispell choices are then available."
(setq case-fold-search nil) ; Try and respect case of word.
(cond
((string-equal (upcase word) word)
- (setq possibilities (mapcar 'upcase possibilities)))
+ (setq possibilities (mapcar #'upcase possibilities)))
((eq (upcase (aref word 0)) (aref word 0))
(setq possibilities (mapcar (function
(lambda (pos)
spelled.
All the buffer-local variables and dictionaries are ignored. To
-read them into the running ispell process, type \\[ispell-word]
+read them into the running Ispell process, type \\[ispell-word]
SPC.
For spell-checking \"on the fly\", not just after typing SPC or
To spell-check whenever a message is sent, include the appropriate lines
in your init file:
- (add-hook 'message-send-hook 'ispell-message) ;; GNUS 5
- (add-hook 'news-inews-hook 'ispell-message) ;; GNUS 4
- (add-hook 'mail-send-hook 'ispell-message)
- (add-hook 'mh-before-send-letter-hook 'ispell-message)
+ (add-hook \\='message-send-hook #\\='ispell-message) ;; GNUS 5
+ (add-hook \\='news-inews-hook #\\='ispell-message) ;; GNUS 4
+ (add-hook \\='mail-send-hook #\\='ispell-message)
+ (add-hook \\='mh-before-send-letter-hook #\\='ispell-message)
You can bind this to the key C-c i in GNUS or mail by adding to
`news-reply-mode-hook' or `mail-mode-hook' the following lambda expression:
- (function (lambda () (local-set-key \"\\C-ci\" 'ispell-message)))"
+ (function (lambda () (local-set-key \"\\C-ci\" \\='ispell-message)))"
(interactive)
(save-excursion
(goto-char (point-min))
(ispell-non-empty-string vm-included-text-prefix)))
(t default-prefix)))
(ispell-skip-region-alist
- (cons (list (concat "^\\(" cite-regexp "\\)")
- (function forward-line))
- ispell-skip-region-alist))
+ (cons (list (ispell--make-filename-or-URL-re))
+ (cons (list (concat "^\\(" cite-regexp "\\)")
+ (function forward-line))
+ ispell-skip-region-alist)))
(old-case-fold-search case-fold-search)
(dictionary-alist ispell-message-dictionary-alist)
(ispell-checking-message t))
(insert comment-end)))))
(insert (concat " " word))))))))
+;;FIXME: Use `user-error' instead!
(add-to-list 'debug-ignored-errors "^No word found to check!$")
(provide 'ispell)