don't require grep in vc-git
[bpt/emacs.git] / lisp / textmodes / flyspell.el
CommitLineData
e8af40ee 1;;; flyspell.el --- on-the-fly spell checker
60371a2e 2
ba318903 3;; Copyright (C) 1998, 2000-2014 Free Software Foundation, Inc.
60371a2e 4
2da34788 5;; Author: Manuel Serrano <Manuel.Serrano@sophia.inria.fr>
34dc21db 6;; Maintainer: emacs-devel@gnu.org
1d8a80f0 7;; Keywords: convenience
60371a2e 8
e8af40ee 9;; This file is part of GNU Emacs.
60371a2e 10
1fecc8fe 11;; GNU Emacs is free software: you can redistribute it and/or modify
60371a2e 12;; it under the terms of the GNU General Public License as published by
1fecc8fe
GM
13;; the Free Software Foundation, either version 3 of the License, or
14;; (at your option) any later version.
60371a2e
RS
15
16;; GNU Emacs is distributed in the hope that it will be useful,
17;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19;; GNU General Public License for more details.
20
21;; You should have received a copy of the GNU General Public License
1fecc8fe 22;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
60371a2e 23
3215afc4 24;;; Commentary:
60371a2e
RS
25;;
26;; Flyspell is a minor Emacs mode performing on-the-fly spelling
1d8a80f0 27;; checking.
3215afc4 28;;
1f44857f 29;; To enable Flyspell minor mode, type M-x flyspell-mode.
65a5c06a 30;; This applies only to the current buffer.
3215afc4
GM
31;;
32;; To enable Flyspell in text representing computer programs, type
1f44857f
DL
33;; M-x flyspell-prog-mode.
34;; In that mode only text inside comments is checked.
2fced6f9 35;;
60371a2e
RS
36;; Some user variables control the behavior of flyspell. They are
37;; those defined under the `User variables' comment.
60371a2e
RS
38
39;;; Code:
2fced6f9 40
60371a2e
RS
41(require 'ispell)
42
042c6fb7
SM
43;;*---------------------------------------------------------------------*/
44;;* Group ... */
45;;*---------------------------------------------------------------------*/
60371a2e 46(defgroup flyspell nil
25f2ad05 47 "Spell checking on the fly."
60371a2e
RS
48 :tag "FlySpell"
49 :prefix "flyspell-"
a9c6d330 50 :group 'ispell
3215afc4 51 :group 'processes)
60371a2e 52
042c6fb7
SM
53;;*---------------------------------------------------------------------*/
54;;* User configuration ... */
55;;*---------------------------------------------------------------------*/
60371a2e 56(defcustom flyspell-highlight-flag t
042c6fb7 57 "How Flyspell should indicate misspelled words.
0a67052f 58Non-nil means use highlight, nil means use minibuffer messages."
60371a2e
RS
59 :group 'flyspell
60 :type 'boolean)
61
0a67052f 62(defcustom flyspell-mark-duplications-flag t
042c6fb7 63 "Non-nil means Flyspell reports a repeated word as an error.
08fea928 64See `flyspell-mark-duplications-exceptions' to add exceptions to this rule.
ae3defd0 65Detection of repeated words is not implemented in
9fc9a531 66\"large\" regions; see variable `flyspell-large-region'."
60371a2e
RS
67 :group 'flyspell
68 :type 'boolean)
69
08fea928 70(defcustom flyspell-mark-duplications-exceptions
c11325f7
CY
71 '((nil . ("that" "had")) ; Common defaults for English.
72 ("\\`francais" . ("nous" "vous")))
08fea928 73 "A list of exceptions for duplicated words.
c11325f7
CY
74It should be a list of (LANGUAGE . EXCEPTION-LIST).
75
76LANGUAGE is nil, which means the exceptions apply regardless of
77the current dictionary, or a regular expression matching the
78dictionary name (`ispell-local-dictionary' or
79`ispell-dictionary') for which the exceptions should apply.
80
81EXCEPTION-LIST is a list of strings. The checked word is
82downcased before comparing with these exceptions."
08fea928 83 :group 'flyspell
c11325f7
CY
84 :type '(alist :key-type (choice (const :tag "All dictionaries" nil)
85 string)
86 :value-type (repeat string))
87 :version "24.1")
08fea928 88
3215afc4 89(defcustom flyspell-sort-corrections nil
042c6fb7 90 "Non-nil means, sort the corrections alphabetically before popping them."
60371a2e 91 :group 'flyspell
1f44857f 92 :version "21.1"
60371a2e
RS
93 :type 'boolean)
94
3215afc4 95(defcustom flyspell-duplicate-distance -1
042c6fb7 96 "The maximum distance for finding duplicates of unrecognized words.
2ed1f669
RS
97This applies to the feature that when a word is not found in the dictionary,
98if the same spelling occurs elsewhere in the buffer,
c43aed5a 99Flyspell uses a different face (`flyspell-duplicate') to highlight it.
2ed1f669 100This variable specifies how far to search to find such a duplicate.
65a5c06a 101-1 means no limit (search the whole buffer).
2ed1f669 1020 means do not search for duplicate unrecognized spellings."
60371a2e 103 :group 'flyspell
1f44857f 104 :version "21.1"
e039c773
RS
105 :type '(choice (const :tag "no limit" -1)
106 number))
60371a2e
RS
107
108(defcustom flyspell-delay 3
042c6fb7 109 "The number of seconds to wait before checking, after a \"delayed\" command."
60371a2e
RS
110 :group 'flyspell
111 :type 'number)
112
113(defcustom flyspell-persistent-highlight t
042c6fb7 114 "Non-nil means misspelled words remain highlighted until corrected.
84770261
RS
115If this variable is nil, only the most recently detected misspelled word
116is highlighted."
60371a2e
RS
117 :group 'flyspell
118 :type 'boolean)
119
120(defcustom flyspell-highlight-properties t
042c6fb7 121 "Non-nil means highlight incorrect words even if a property exists for this word."
60371a2e
RS
122 :group 'flyspell
123 :type 'boolean)
124
125(defcustom flyspell-default-delayed-commands
126 '(self-insert-command
127 delete-backward-char
3215afc4
GM
128 backward-or-forward-delete-char
129 delete-char
a8c453e6
RS
130 scrollbar-vertical-drag
131 backward-delete-char-untabify)
0a67052f
RS
132 "The standard list of delayed commands for Flyspell.
133See `flyspell-delayed-commands'."
60371a2e 134 :group 'flyspell
1f44857f 135 :version "21.1"
60371a2e
RS
136 :type '(repeat (symbol)))
137
0a67052f
RS
138(defcustom flyspell-delayed-commands nil
139 "List of commands that are \"delayed\" for Flyspell mode.
65a5c06a
RS
140After these commands, Flyspell checking is delayed for a short time,
141whose length is specified by `flyspell-delay'."
60371a2e
RS
142 :group 'flyspell
143 :type '(repeat (symbol)))
144
3215afc4 145(defcustom flyspell-default-deplacement-commands
60c4db3a
SM
146 '(next-line previous-line
147 handle-switch-frame handle-select-window
9fc9a531
AH
148 scroll-up
149 scroll-down)
3215afc4 150 "The standard list of deplacement commands for Flyspell.
9fc9a531 151See variable `flyspell-deplacement-commands'."
3215afc4 152 :group 'flyspell
1f44857f 153 :version "21.1"
3215afc4
GM
154 :type '(repeat (symbol)))
155
156(defcustom flyspell-deplacement-commands nil
157 "List of commands that are \"deplacement\" for Flyspell mode.
158After these commands, Flyspell checking is performed only if the previous
159command was not the very same command."
160 :group 'flyspell
1f44857f 161 :version "21.1"
3215afc4
GM
162 :type '(repeat (symbol)))
163
60371a2e 164(defcustom flyspell-issue-welcome-flag t
042c6fb7 165 "Non-nil means that Flyspell should display a welcome message when started."
60371a2e
RS
166 :group 'flyspell
167 :type 'boolean)
168
73194d67 169(defcustom flyspell-issue-message-flag t
042c6fb7 170 "Non-nil means that Flyspell emits messages when checking words."
73194d67
PJ
171 :group 'flyspell
172 :type 'boolean)
173
3215afc4 174(defcustom flyspell-incorrect-hook nil
042c6fb7 175 "List of functions to be called when incorrect words are encountered.
91346f54
RS
176Each function is given three arguments. The first two
177arguments are the beginning and the end of the incorrect region.
178The third is either the symbol `doublon' or the list
ebb7de84 179of possible corrections as returned by `ispell-parse-output'.
3215afc4 180
91346f54 181If any of the functions return non-nil, the word is not highlighted as
3215afc4
GM
182incorrect."
183 :group 'flyspell
1f44857f 184 :version "21.1"
3215afc4
GM
185 :type 'hook)
186
487301c2 187(defcustom flyspell-default-dictionary nil
3215afc4 188 "A string that is the name of the default dictionary.
1f44857f 189This is passed to the `ispell-change-dictionary' when flyspell is started.
487301c2
RS
190If the variable `ispell-local-dictionary' or `ispell-dictionary' is non-nil
191when flyspell is started, the value of that variable is used instead
192of `flyspell-default-dictionary' to select the default dictionary.
193Otherwise, if `flyspell-default-dictionary' is nil, it means to use
194Ispell's ultimate default dictionary."
3215afc4 195 :group 'flyspell
1f44857f 196 :version "21.1"
eb1d71ab 197 :type '(choice string (const :tag "Default" nil)))
3215afc4
GM
198
199(defcustom flyspell-tex-command-regexp
200 "\\(\\(begin\\|end\\)[ \t]*{\\|\\(cite[a-z*]*\\|label\\|ref\\|eqref\\|usepackage\\|documentclass\\)[ \t]*\\(\\[[^]]*\\]\\)?{[^{}]*\\)"
201 "A string that is the regular expression that matches TeX commands."
202 :group 'flyspell
1f44857f 203 :version "21.1"
3215afc4
GM
204 :type 'string)
205
206(defcustom flyspell-check-tex-math-command nil
da3d757b 207 "Non-nil means check even inside TeX math environment.
be7748e7
KR
208TeX math environments are discovered by `texmathp', implemented
209inside AUCTeX package. That package may be found at
210URL `http://www.gnu.org/software/auctex/'"
60371a2e
RS
211 :group 'flyspell
212 :type 'boolean)
213
3215afc4
GM
214(defcustom flyspell-dictionaries-that-consider-dash-as-word-delimiter
215 '("francais" "deutsch8" "norsk")
216 "List of dictionary names that consider `-' as word delimiter."
217 :group 'flyspell
1f44857f 218 :version "21.1"
3215afc4 219 :type '(repeat (string)))
60371a2e 220
3215afc4 221(defcustom flyspell-abbrev-p
a8c453e6 222 nil
042c6fb7 223 "If non-nil, add correction to abbreviation table."
60371a2e 224 :group 'flyspell
1f44857f 225 :version "21.1"
60371a2e
RS
226 :type 'boolean)
227
3215afc4
GM
228(defcustom flyspell-use-global-abbrev-table-p
229 nil
042c6fb7 230 "If non-nil, prefer global abbrev table to local abbrev table."
3215afc4 231 :group 'flyspell
1f44857f 232 :version "21.1"
3215afc4 233 :type 'boolean)
2fced6f9 234
3215afc4 235(defcustom flyspell-mode-line-string " Fly"
37269466
CY
236 "String displayed on the mode line when flyspell is active.
237Set this to nil if you don't want a mode line indicator."
3215afc4 238 :group 'flyspell
5c2012be 239 :type '(choice string (const :tag "None" nil)))
3215afc4
GM
240
241(defcustom flyspell-large-region 1000
042c6fb7 242 "The threshold that determines if a region is small.
fc2cae59
SE
243If the region is smaller than this number of characters,
244`flyspell-region' checks the words sequentially using regular
245flyspell methods. Else, if the region is large, a new Ispell process is
a8c453e6
RS
246spawned for speed.
247
ae3defd0
RS
248Doubled words are not detected in a large region, because Ispell
249does not check for them.
250
c2e161b2 251If this variable is nil, all regions are treated as small."
3215afc4 252 :group 'flyspell
1f44857f 253 :version "21.1"
ae3defd0 254 :type '(choice number (const :tag "All small" nil)))
3215afc4 255
73194d67 256(defcustom flyspell-insert-function (function insert)
042c6fb7 257 "Function for inserting word by flyspell upon correction."
73194d67
PJ
258 :group 'flyspell
259 :type 'function)
260
261(defcustom flyspell-before-incorrect-word-string nil
262 "String used to indicate an incorrect word starting."
263 :group 'flyspell
264 :type '(choice string (const nil)))
265
266(defcustom flyspell-after-incorrect-word-string nil
267 "String used to indicate an incorrect word ending."
268 :group 'flyspell
269 :type '(choice string (const nil)))
270
cb92c150
GM
271(defvar flyspell-mode-map)
272
a8c453e6 273(defcustom flyspell-use-meta-tab t
042c6fb7 274 "Non-nil means that flyspell uses M-TAB to correct word."
a8c453e6 275 :group 'flyspell
cb92c150
GM
276 :type 'boolean
277 :initialize 'custom-initialize-default
278 :set (lambda (sym val)
279 (define-key flyspell-mode-map "\M-\t"
280 (if (set sym val)
281 'flyspell-auto-correct-word))))
a8c453e6
RS
282
283(defcustom flyspell-auto-correct-binding
7ad04640 284 [(control ?\;)]
a8c453e6 285 "The key binding for flyspell auto correction."
9c5a5c77 286 :type 'key-sequence
a8c453e6
RS
287 :group 'flyspell)
288
042c6fb7
SM
289;;*---------------------------------------------------------------------*/
290;;* Mode specific options */
291;;* ------------------------------------------------------------- */
292;;* Mode specific options enable users to disable flyspell on */
293;;* certain word depending of the emacs mode. For instance, when */
294;;* using flyspell with mail-mode add the following expression */
865fe16f 295;;* in your init file: */
042c6fb7 296;;* (add-hook 'mail-mode */
865fe16f 297;;* (lambda () (setq flyspell-generic-check-word-predicate */
5c9c3eba 298;;* 'mail-mode-flyspell-verify))) */
042c6fb7 299;;*---------------------------------------------------------------------*/
5c9c3eba 300(defvar flyspell-generic-check-word-predicate nil
60371a2e 301 "Function providing per-mode customization over which words are flyspelled.
0a67052f
RS
302Returns t to continue checking, nil otherwise.
303Flyspell mode sets this variable to whatever is the `flyspell-mode-predicate'
304property of the major mode name.")
5c9c3eba
RW
305(make-variable-buffer-local 'flyspell-generic-check-word-predicate)
306(defvaralias 'flyspell-generic-check-word-p
307 'flyspell-generic-check-word-predicate)
60371a2e 308
042c6fb7 309;;*--- mail mode -------------------------------------------------------*/
0a67052f
RS
310(put 'mail-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify)
311(put 'message-mode 'flyspell-mode-predicate 'mail-mode-flyspell-verify)
fc5e09b3 312(defvar message-signature-separator)
60371a2e 313(defun mail-mode-flyspell-verify ()
5c9c3eba 314 "Function used for `flyspell-generic-check-word-predicate' in Mail mode."
a8c453e6
RS
315 (let ((header-end (save-excursion
316 (goto-char (point-min))
317 (re-search-forward
318 (concat "^"
319 (regexp-quote mail-header-separator)
320 "$")
321 nil t)
322 (point)))
747d0c44
SM
323 (signature-begin
324 (if (not (boundp 'message-signature-separator))
325 (point-max)
326 (save-excursion
327 (goto-char (point-max))
328 (re-search-backward message-signature-separator nil t)
329 (point)))))
a8c453e6 330 (cond ((< (point) header-end)
25c99c6d
GM
331 (and (save-excursion (beginning-of-line)
332 (looking-at "^Subject:"))
333 (> (point) (match-end 0))))
a8c453e6 334 ((> (point) signature-begin)
53287eb0
GM
335 nil)
336 (t
337 (save-excursion
338 (beginning-of-line)
676019c5 339 (not (looking-at "[>}|]\\|To:")))))))
60371a2e 340
042c6fb7 341;;*--- texinfo mode ----------------------------------------------------*/
0a67052f 342(put 'texinfo-mode 'flyspell-mode-predicate 'texinfo-mode-flyspell-verify)
60371a2e 343(defun texinfo-mode-flyspell-verify ()
5c9c3eba 344 "Function used for `flyspell-generic-check-word-predicate' in Texinfo mode."
60371a2e
RS
345 (save-excursion
346 (forward-word -1)
347 (not (looking-at "@"))))
348
042c6fb7 349;;*--- tex mode --------------------------------------------------------*/
3215afc4
GM
350(put 'tex-mode 'flyspell-mode-predicate 'tex-mode-flyspell-verify)
351(defun tex-mode-flyspell-verify ()
5c9c3eba 352 "Function used for `flyspell-generic-check-word-predicate' in LaTeX mode."
3215afc4
GM
353 (and
354 (not (save-excursion
9c841316 355 (re-search-backward "^[ \t]*%%%[ \t]+Local" nil t)))
3215afc4 356 (not (save-excursion
873f4645 357 (let ((this (point)))
3215afc4 358 (beginning-of-line)
873f4645
CY
359 (and (re-search-forward "\\\\\\(cite\\|label\\|ref\\){[^}]*}"
360 (line-end-position) t)
361 (>= this (match-beginning 0))
362 (<= this (match-end 0))))))))
3215afc4 363
042c6fb7 364;;*--- sgml mode -------------------------------------------------------*/
3215afc4
GM
365(put 'sgml-mode 'flyspell-mode-predicate 'sgml-mode-flyspell-verify)
366(put 'html-mode 'flyspell-mode-predicate 'sgml-mode-flyspell-verify)
23e33070 367(put 'nxml-mode 'flyspell-mode-predicate 'sgml-mode-flyspell-verify)
3215afc4 368
4c3a215a 369(autoload 'sgml-lexical-context "sgml-mode")
7e705a1d 370
3215afc4 371(defun sgml-mode-flyspell-verify ()
e3c39c01
KR
372 "Function used for `flyspell-generic-check-word-predicate' in SGML mode.
373Tag and attribute names are not spell checked, everything else is.
374
375String values of attributes are checked because they can be text
376like <img alt=\"Some thing.\">."
377
378 (not (memq (car (sgml-lexical-context))
379 '(tag pi))))
3215afc4 380
042c6fb7
SM
381;;*---------------------------------------------------------------------*/
382;;* Programming mode */
383;;*---------------------------------------------------------------------*/
9b047c69
SM
384(defvar flyspell-prog-text-faces
385 '(font-lock-string-face font-lock-comment-face font-lock-doc-face)
386 "Faces corresponding to text in programming-mode buffers.")
387
3215afc4 388(defun flyspell-generic-progmode-verify ()
5c9c3eba 389 "Used for `flyspell-generic-check-word-predicate' in programming modes."
84992dff
AM
390 ;; (point) is next char after the word. Must check one char before.
391 (let ((f (get-text-property (- (point) 1) 'face)))
9b047c69 392 (memq f flyspell-prog-text-faces)))
3215afc4
GM
393
394;;;###autoload
395(defun flyspell-prog-mode ()
396 "Turn on `flyspell-mode' for comments and strings."
397 (interactive)
5c9c3eba
RW
398 (setq flyspell-generic-check-word-predicate
399 'flyspell-generic-progmode-verify)
a8c453e6
RS
400 (flyspell-mode 1)
401 (run-hooks 'flyspell-prog-mode-hook))
3215afc4 402
042c6fb7
SM
403;;*---------------------------------------------------------------------*/
404;;* Overlay compatibility */
405;;*---------------------------------------------------------------------*/
3215afc4
GM
406(autoload 'make-overlay "overlay" "Overlay compatibility kit." t)
407(autoload 'overlayp "overlay" "Overlay compatibility kit." t)
408(autoload 'overlays-in "overlay" "Overlay compatibility kit." t)
409(autoload 'delete-overlay "overlay" "Overlay compatibility kit." t)
410(autoload 'overlays-at "overlay" "Overlay compatibility kit." t)
411(autoload 'overlay-put "overlay" "Overlay compatibility kit." t)
412(autoload 'overlay-get "overlay" "Overlay compatibility kit." t)
413(autoload 'previous-overlay-change "overlay" "Overlay compatibility kit." t)
60371a2e 414
042c6fb7
SM
415;;*---------------------------------------------------------------------*/
416;;* The minor mode declaration. */
417;;*---------------------------------------------------------------------*/
2be80b63
DL
418(defvar flyspell-mouse-map
419 (let ((map (make-sparse-keymap)))
770f7c63
CY
420 (if (featurep 'xemacs)
421 (define-key map [button2] #'flyspell-correct-word)
422 (define-key map [down-mouse-2] #'flyspell-correct-word)
423 (define-key map [mouse-2] 'undefined))
d4e92f5f
RS
424 map)
425 "Keymap for Flyspell to put on erroneous words.")
60371a2e 426
7ad04640
SM
427(defvar flyspell-mode-map
428 (let ((map (make-sparse-keymap)))
7ad04640
SM
429 (if flyspell-use-meta-tab
430 (define-key map "\M-\t" 'flyspell-auto-correct-word))
d4e92f5f
RS
431 (define-key map flyspell-auto-correct-binding 'flyspell-auto-correct-previous-word)
432 (define-key map [(control ?\,)] 'flyspell-goto-next-error)
433 (define-key map [(control ?\.)] 'flyspell-auto-correct-word)
89be8f4e 434 (define-key map [?\C-c ?$] 'flyspell-correct-word-before-point)
d4e92f5f
RS
435 map)
436 "Minor mode keymap for Flyspell mode--for the whole buffer.")
3215afc4
GM
437
438;; dash character machinery
439(defvar flyspell-consider-dash-as-word-delimiter-flag nil
fb7ada5f 440 "Non-nil means that the `-' char is considered as a word delimiter.")
3215afc4
GM
441(make-variable-buffer-local 'flyspell-consider-dash-as-word-delimiter-flag)
442(defvar flyspell-dash-dictionary nil)
443(make-variable-buffer-local 'flyspell-dash-dictionary)
444(defvar flyspell-dash-local-dictionary nil)
445(make-variable-buffer-local 'flyspell-dash-local-dictionary)
446
042c6fb7
SM
447;;*---------------------------------------------------------------------*/
448;;* Highlighting */
449;;*---------------------------------------------------------------------*/
073ca75b
JL
450(defface flyspell-incorrect
451 '((((supports :underline (:style wave)))
452 :underline (:style wave :color "Red1"))
453 (t
454 :underline t :inherit error))
4b56d0fe 455 "Flyspell face for misspelled words."
073ca75b 456 :version "24.4"
0a67052f 457 :group 'flyspell)
4b56d0fe 458
073ca75b
JL
459(defface flyspell-duplicate
460 '((((supports :underline (:style wave)))
461 :underline (:style wave :color "DarkOrange"))
462 (t
463 :underline t :inherit warning))
4b56d0fe 464 "Flyspell face for words that appear twice in a row.
2ed1f669 465See also `flyspell-duplicate-distance'."
073ca75b 466 :version "24.4"
0a67052f
RS
467 :group 'flyspell)
468
60371a2e
RS
469(defvar flyspell-overlay nil)
470
042c6fb7
SM
471;;*---------------------------------------------------------------------*/
472;;* flyspell-mode ... */
473;;*---------------------------------------------------------------------*/
3adbe224 474;;;###autoload(defvar flyspell-mode nil "Non-nil if Flyspell mode is enabled.")
60371a2e 475;;;###autoload
b978659c 476(define-minor-mode flyspell-mode
ac6c8639
CY
477 "Toggle on-the-fly spell checking (Flyspell mode).
478With a prefix argument ARG, enable Flyspell mode if ARG is
479positive, and disable it otherwise. If called from Lisp, enable
480the mode if ARG is omitted or nil.
481
482Flyspell mode is a buffer-local minor mode. When enabled, it
483spawns a single Ispell process and checks each word. The default
484flyspell behavior is to highlight incorrect words.
2fced6f9 485
60371a2e
RS
486Bindings:
487\\[ispell-word]: correct words (using Ispell).
488\\[flyspell-auto-correct-word]: automatically correct word.
a8c453e6
RS
489\\[flyspell-auto-correct-previous-word]: automatically correct the last misspelled word.
490\\[flyspell-correct-word] (or down-mouse-2): popup correct words.
60371a2e
RS
491
492Hooks:
b224db38 493This runs `flyspell-mode-hook' after flyspell mode is entered or exit.
60371a2e
RS
494
495Remark:
496`flyspell-mode' uses `ispell-mode'. Thus all Ispell options are
274483cc 497valid. For instance, a different dictionary can be used by
60371a2e
RS
498invoking `ispell-change-dictionary'.
499
500Consider using the `ispell-parser' to check your text. For instance
501consider adding:
0a67052f 502\(add-hook 'tex-mode-hook (function (lambda () (setq ispell-parser 'tex))))
865fe16f 503in your init file.
60371a2e 504
0dd10e62
RS
505\\[flyspell-region] checks all words inside a region.
506\\[flyspell-buffer] checks the whole buffer."
b978659c
LK
507 :lighter flyspell-mode-line-string
508 :keymap flyspell-mode-map
509 :group 'flyspell
510 (if flyspell-mode
7dfc15df 511 (condition-case err
478adae2 512 (flyspell-mode-on)
7dfc15df 513 (error (message "Error enabling Flyspell mode:\n%s" (cdr err))
478adae2 514 (flyspell-mode -1)))
b978659c 515 (flyspell-mode-off)))
3215afc4 516
2809db33
RS
517;;;###autoload
518(defun turn-on-flyspell ()
519 "Unconditionally turn on Flyspell mode."
520 (flyspell-mode 1))
521
522;;;###autoload
523(defun turn-off-flyspell ()
524 "Unconditionally turn off Flyspell mode."
525 (flyspell-mode -1))
526
527(custom-add-option 'text-mode-hook 'turn-on-flyspell)
528
042c6fb7
SM
529;;*---------------------------------------------------------------------*/
530;;* flyspell-buffers ... */
531;;* ------------------------------------------------------------- */
532;;* For remembering buffers running flyspell */
533;;*---------------------------------------------------------------------*/
3215afc4 534(defvar flyspell-buffers nil)
2fced6f9 535
042c6fb7
SM
536;;*---------------------------------------------------------------------*/
537;;* flyspell-minibuffer-p ... */
538;;*---------------------------------------------------------------------*/
3215afc4
GM
539(defun flyspell-minibuffer-p (buffer)
540 "Is BUFFER a minibuffer?"
541 (let ((ws (get-buffer-window-list buffer t)))
542 (and (consp ws) (window-minibuffer-p (car ws)))))
543
042c6fb7
SM
544;;*---------------------------------------------------------------------*/
545;;* flyspell-accept-buffer-local-defs ... */
546;;*---------------------------------------------------------------------*/
6a99c272
SM
547(defvar flyspell-last-buffer nil
548 "The buffer in which the last flyspell operation took place.")
549
bef9f82c 550(defun flyspell-accept-buffer-local-defs (&optional force)
6a99c272
SM
551 ;; When flyspell-word is used inside a loop (e.g. when processing
552 ;; flyspell-changes), the calls to `ispell-accept-buffer-local-defs' end
553 ;; up dwarfing everything else, so only do it when the buffer has changed.
bef9f82c 554 (when (or force (not (eq flyspell-last-buffer (current-buffer))))
6a99c272
SM
555 (setq flyspell-last-buffer (current-buffer))
556 ;; Strange problem: If buffer in current window has font-lock turned on,
557 ;; but SET-BUFFER was called to point to an invisible buffer, this ispell
558 ;; call will reset the buffer to the buffer in the current window.
559 ;; However, it only happens at startup (fix by Albert L. Ting).
560 (save-current-buffer
561 (ispell-accept-buffer-local-defs))
562 (unless (and (eq flyspell-dash-dictionary ispell-dictionary)
563 (eq flyspell-dash-local-dictionary ispell-local-dictionary))
a8c453e6 564 ;; The dictionary has changed
6a99c272
SM
565 (setq flyspell-dash-dictionary ispell-dictionary)
566 (setq flyspell-dash-local-dictionary ispell-local-dictionary)
567 (setq flyspell-consider-dash-as-word-delimiter-flag
568 (member (or ispell-local-dictionary ispell-dictionary)
569 flyspell-dictionaries-that-consider-dash-as-word-delimiter)))))
3215afc4 570
c1ac9f5e
EZ
571(defun flyspell-hack-local-variables-hook ()
572 ;; When local variables are loaded, see if the dictionary context
573 ;; has changed.
574 (flyspell-accept-buffer-local-defs 'force))
575
ef0f5f7e
SM
576(defun flyspell-kill-ispell-hook ()
577 (setq flyspell-last-buffer nil)
578 (dolist (buf (buffer-list))
949855fe
SM
579 (with-current-buffer buf
580 (kill-local-variable 'flyspell-word-cache-word))))
ef0f5f7e 581
8e2e2956
SM
582;; Make sure we flush our caches when needed. Do it here rather than in
583;; flyspell-mode-on, since flyspell-region may be used without ever turning
584;; on flyspell-mode.
585(add-hook 'ispell-kill-ispell-hook 'flyspell-kill-ispell-hook)
586
042c6fb7
SM
587;;*---------------------------------------------------------------------*/
588;;* flyspell-mode-on ... */
589;;*---------------------------------------------------------------------*/
60371a2e 590(defun flyspell-mode-on ()
59e7a637 591 "Turn Flyspell mode on. Do not use this; use `flyspell-mode' instead."
caea54f8 592 (ispell-set-spellchecker-params) ; Initialize variables and dicts alists
c43aed5a 593 (setq ispell-highlight-face 'flyspell-incorrect)
3215afc4 594 ;; local dictionaries setup
8ceb0c6e
RS
595 (or ispell-local-dictionary ispell-dictionary
596 (if flyspell-default-dictionary
597 (ispell-change-dictionary flyspell-default-dictionary)))
3215afc4
GM
598 ;; we have to force ispell to accept the local definition or
599 ;; otherwise it could be too late, the local dictionary may
600 ;; be forgotten!
bef9f82c
SM
601 ;; Pass the `force' argument for the case where flyspell was active already
602 ;; but the buffer's local-defs have been edited.
603 (flyspell-accept-buffer-local-defs 'force)
58c68129 604 ;; we put the `flyspell-delayed' property on some commands
60371a2e 605 (flyspell-delay-commands)
58c68129 606 ;; we put the `flyspell-deplacement' property on some commands
3215afc4 607 (flyspell-deplacement-commands)
60371a2e 608 ;; we bound flyspell action to post-command hook
2ed1f669 609 (add-hook 'post-command-hook (function flyspell-post-command-hook) t t)
60371a2e 610 ;; we bound flyspell action to pre-command hook
2ed1f669 611 (add-hook 'pre-command-hook (function flyspell-pre-command-hook) t t)
3215afc4 612 ;; we bound flyspell action to after-change hook
6a99c272 613 (add-hook 'after-change-functions 'flyspell-after-change-function nil t)
c1ac9f5e
EZ
614 ;; we bound flyspell action to hack-local-variables-hook
615 (add-hook 'hack-local-variables-hook
616 (function flyspell-hack-local-variables-hook) t t)
5c9c3eba 617 ;; set flyspell-generic-check-word-predicate based on the major mode
0a67052f
RS
618 (let ((mode-predicate (get major-mode 'flyspell-mode-predicate)))
619 (if mode-predicate
5c9c3eba 620 (setq flyspell-generic-check-word-predicate mode-predicate)))
60371a2e 621 ;; the welcome message
73194d67
PJ
622 (if (and flyspell-issue-message-flag
623 flyspell-issue-welcome-flag
85c8c5b6
AM
624 (if (featurep 'xemacs)
625 (interactive-p) ;; XEmacs does not have (called-interactively-p)
626 (called-interactively-p 'interactive)))
65a5c06a
RS
627 (let ((binding (where-is-internal 'flyspell-auto-correct-word
628 nil 'non-ascii)))
5673af85 629 (message "%s"
65a5c06a 630 (if binding
3215afc4 631 (format "Welcome to flyspell. Use %s or Mouse-2 to correct words."
65a5c06a 632 (key-description binding))
b224db38 633 "Welcome to flyspell. Use Mouse-2 to correct words.")))))
60371a2e 634
042c6fb7
SM
635;;*---------------------------------------------------------------------*/
636;;* flyspell-delay-commands ... */
637;;*---------------------------------------------------------------------*/
60371a2e 638(defun flyspell-delay-commands ()
59e7a637 639 "Install the standard set of Flyspell delayed commands."
6c3bce72 640 (mapc 'flyspell-delay-command flyspell-default-delayed-commands)
60c4db3a 641 (mapc 'flyspell-delay-command flyspell-delayed-commands))
60371a2e 642
042c6fb7
SM
643;;*---------------------------------------------------------------------*/
644;;* flyspell-delay-command ... */
645;;*---------------------------------------------------------------------*/
60371a2e 646(defun flyspell-delay-command (command)
59e7a637 647 "Set COMMAND to be delayed, for Flyspell.
60371a2e 648When flyspell `post-command-hook' is invoked because a delayed command
60c4db3a 649has been used, the current word is not immediately checked.
0a67052f
RS
650It will be checked only after `flyspell-delay' seconds."
651 (interactive "SDelay Flyspell after Command: ")
60371a2e
RS
652 (put command 'flyspell-delayed t))
653
042c6fb7
SM
654;;*---------------------------------------------------------------------*/
655;;* flyspell-deplacement-commands ... */
656;;*---------------------------------------------------------------------*/
3215afc4
GM
657(defun flyspell-deplacement-commands ()
658 "Install the standard set of Flyspell deplacement commands."
6c3bce72 659 (mapc 'flyspell-deplacement-command flyspell-default-deplacement-commands)
60c4db3a 660 (mapc 'flyspell-deplacement-command flyspell-deplacement-commands))
60371a2e 661
042c6fb7
SM
662;;*---------------------------------------------------------------------*/
663;;* flyspell-deplacement-command ... */
664;;*---------------------------------------------------------------------*/
3215afc4
GM
665(defun flyspell-deplacement-command (command)
666 "Set COMMAND that implement cursor movements, for Flyspell.
60c4db3a
SM
667When flyspell `post-command-hook' is invoked because a deplacement command
668has been used, the current word is not checked."
3215afc4
GM
669 (interactive "SDeplacement Flyspell after Command: ")
670 (put command 'flyspell-deplacement t))
60371a2e 671
042c6fb7
SM
672;;*---------------------------------------------------------------------*/
673;;* flyspell-word-cache ... */
674;;*---------------------------------------------------------------------*/
60371a2e
RS
675(defvar flyspell-word-cache-start nil)
676(defvar flyspell-word-cache-end nil)
677(defvar flyspell-word-cache-word nil)
a8c453e6 678(defvar flyspell-word-cache-result '_)
60371a2e
RS
679(make-variable-buffer-local 'flyspell-word-cache-start)
680(make-variable-buffer-local 'flyspell-word-cache-end)
681(make-variable-buffer-local 'flyspell-word-cache-word)
a8c453e6 682(make-variable-buffer-local 'flyspell-word-cache-result)
60371a2e 683
042c6fb7
SM
684;;*---------------------------------------------------------------------*/
685;;* The flyspell pre-hook, store the current position. In the */
686;;* post command hook, we will check, if the word at this position */
687;;* has to be spell checked. */
688;;*---------------------------------------------------------------------*/
60c4db3a
SM
689(defvar flyspell-pre-buffer nil "Buffer current before `this-command'.")
690(defvar flyspell-pre-point nil "Point before running `this-command'")
691(defvar flyspell-pre-column nil "Column before running `this-command'")
3215afc4
GM
692(defvar flyspell-pre-pre-buffer nil)
693(defvar flyspell-pre-pre-point nil)
60c4db3a 694(make-variable-buffer-local 'flyspell-pre-point) ;Why?? --Stef
3215afc4 695
042c6fb7
SM
696;;*---------------------------------------------------------------------*/
697;;* flyspell-previous-command ... */
698;;*---------------------------------------------------------------------*/
3215afc4
GM
699(defvar flyspell-previous-command nil
700 "The last interactive command checked by Flyspell.")
60371a2e 701
042c6fb7
SM
702;;*---------------------------------------------------------------------*/
703;;* flyspell-pre-command-hook ... */
704;;*---------------------------------------------------------------------*/
60371a2e 705(defun flyspell-pre-command-hook ()
0a67052f 706 "Save the current buffer and point for Flyspell's post-command hook."
60371a2e
RS
707 (interactive)
708 (setq flyspell-pre-buffer (current-buffer))
3215afc4
GM
709 (setq flyspell-pre-point (point))
710 (setq flyspell-pre-column (current-column)))
60371a2e 711
042c6fb7
SM
712;;*---------------------------------------------------------------------*/
713;;* flyspell-mode-off ... */
714;;*---------------------------------------------------------------------*/
59e7a637 715;;;###autoload
60371a2e 716(defun flyspell-mode-off ()
59e7a637 717 "Turn Flyspell mode off."
60c4db3a 718 ;; We remove the hooks.
2ed1f669
RS
719 (remove-hook 'post-command-hook (function flyspell-post-command-hook) t)
720 (remove-hook 'pre-command-hook (function flyspell-pre-command-hook) t)
6a99c272 721 (remove-hook 'after-change-functions 'flyspell-after-change-function t)
c1ac9f5e
EZ
722 (remove-hook 'hack-local-variables-hook
723 (function flyspell-hack-local-variables-hook) t)
60c4db3a 724 ;; We remove all the flyspell highlightings.
60371a2e 725 (flyspell-delete-all-overlays)
60c4db3a 726 ;; We have to erase pre cache variables.
60371a2e
RS
727 (setq flyspell-pre-buffer nil)
728 (setq flyspell-pre-point nil)
60c4db3a 729 ;; We mark the mode as killed.
60371a2e
RS
730 (setq flyspell-mode nil))
731
042c6fb7
SM
732;;*---------------------------------------------------------------------*/
733;;* flyspell-check-pre-word-p ... */
734;;*---------------------------------------------------------------------*/
60371a2e 735(defun flyspell-check-pre-word-p ()
a8c453e6 736 "Return non-nil if we should check the word before point.
0a67052f
RS
737More precisely, it applies to the word that was before point
738before the current command."
3f1b25b5
AM
739 (let ((ispell-otherchars (ispell-get-otherchars)))
740 (cond
60c4db3a 741 ((not (and (numberp flyspell-pre-point)
ffe54a13 742 (eq flyspell-pre-buffer (current-buffer))))
3f1b25b5
AM
743 nil)
744 ((and (eq flyspell-pre-pre-point flyspell-pre-point)
745 (eq flyspell-pre-pre-buffer flyspell-pre-buffer))
746 nil)
747 ((or (and (= flyspell-pre-point (- (point) 1))
748 (or (eq (char-syntax (char-after flyspell-pre-point)) ?w)
749 (and (not (string= "" ispell-otherchars))
85c8c5b6 750 (string-match
3f1b25b5
AM
751 ispell-otherchars
752 (buffer-substring-no-properties
753 flyspell-pre-point (1+ flyspell-pre-point))))))
754 (= flyspell-pre-point (point))
755 (= flyspell-pre-point (+ (point) 1)))
756 nil)
757 ((and (symbolp this-command)
758 (not executing-kbd-macro)
759 (or (get this-command 'flyspell-delayed)
760 (and (get this-command 'flyspell-deplacement)
761 (eq flyspell-previous-command this-command)))
762 (or (= (current-column) 0)
763 (= (current-column) flyspell-pre-column)
764 ;; If other post-command-hooks change the buffer,
765 ;; flyspell-pre-point can lie past eob (bug#468).
766 (null (char-after flyspell-pre-point))
767 (or (eq (char-syntax (char-after flyspell-pre-point)) ?w)
768 (and (not (string= "" ispell-otherchars))
85c8c5b6 769 (string-match
3f1b25b5
AM
770 ispell-otherchars
771 (buffer-substring-no-properties
772 flyspell-pre-point (1+ flyspell-pre-point)))))))
773 nil)
774 ((not (eq (current-buffer) flyspell-pre-buffer))
775 t)
776 ((not (and (numberp flyspell-word-cache-start)
777 (numberp flyspell-word-cache-end)))
778 t)
779 (t
780 (or (< flyspell-pre-point flyspell-word-cache-start)
781 (> flyspell-pre-point flyspell-word-cache-end))))))
3215afc4 782
042c6fb7
SM
783;;*---------------------------------------------------------------------*/
784;;* The flyspell after-change-hook, store the change position. In */
785;;* the post command hook, we will check, if the word at this */
786;;* position has to be spell checked. */
787;;*---------------------------------------------------------------------*/
3215afc4 788(defvar flyspell-changes nil)
6a99c272 789(make-variable-buffer-local 'flyspell-changes)
3215afc4 790
042c6fb7
SM
791;;*---------------------------------------------------------------------*/
792;;* flyspell-after-change-function ... */
793;;*---------------------------------------------------------------------*/
3215afc4
GM
794(defun flyspell-after-change-function (start stop len)
795 "Save the current buffer and point for Flyspell's post-command hook."
6a99c272 796 (push (cons start stop) flyspell-changes))
3215afc4 797
042c6fb7
SM
798;;*---------------------------------------------------------------------*/
799;;* flyspell-check-changed-word-p ... */
800;;*---------------------------------------------------------------------*/
3215afc4 801(defun flyspell-check-changed-word-p (start stop)
60c4db3a 802 "Return non-nil when the changed word has to be checked.
3215afc4
GM
803The answer depends of several criteria.
804Mostly we check word delimiters."
60c4db3a
SM
805 (not (and (not (and (memq (char-after start) '(?\n ? )) (> stop start)))
806 (numberp flyspell-pre-point)
807 (or
808 (and (>= flyspell-pre-point start) (<= flyspell-pre-point stop))
809 (let ((pos (point)))
810 (or (>= pos start) (<= pos stop) (= pos (1+ stop))))))))
3215afc4 811
042c6fb7
SM
812;;*---------------------------------------------------------------------*/
813;;* flyspell-check-word-p ... */
814;;*---------------------------------------------------------------------*/
3215afc4
GM
815(defun flyspell-check-word-p ()
816 "Return t when the word at `point' has to be checked.
817The answer depends of several criteria.
818Mostly we check word delimiters."
3f1b25b5 819 (let ((ispell-otherchars (ispell-get-otherchars)))
3215afc4 820 (cond
3f1b25b5 821 ((<= (- (point-max) 1) (point-min))
60c4db3a 822 ;; The buffer is not filled enough.
3f1b25b5
AM
823 nil)
824 ((and (and (> (current-column) 0)
825 (not (eq (current-column) flyspell-pre-column)))
826 (save-excursion
827 (backward-char 1)
828 (and (looking-at (flyspell-get-not-casechars))
829 (or (string= "" ispell-otherchars)
80a51162 830 (not (looking-at ispell-otherchars)))
3f1b25b5
AM
831 (or flyspell-consider-dash-as-word-delimiter-flag
832 (not (looking-at "-"))))))
60c4db3a 833 ;; Yes because we have reached or typed a word delimiter.
3f1b25b5
AM
834 t)
835 ((symbolp this-command)
836 (cond
837 ((get this-command 'flyspell-deplacement)
838 (not (eq flyspell-previous-command this-command)))
839 ((get this-command 'flyspell-delayed)
60c4db3a
SM
840 ;; The current command is not delayed, that
841 ;; is that we must check the word now.
3f1b25b5
AM
842 (and (not unread-command-events)
843 (sit-for flyspell-delay)))
844 (t t)))
845 (t t))))
3215afc4 846
042c6fb7
SM
847;;*---------------------------------------------------------------------*/
848;;* flyspell-debug-signal-no-check ... */
849;;*---------------------------------------------------------------------*/
3215afc4
GM
850(defun flyspell-debug-signal-no-check (msg obj)
851 (setq debug-on-error t)
042c6fb7
SM
852 (with-current-buffer (get-buffer-create "*flyspell-debug*")
853 (erase-buffer)
854 (insert "NO-CHECK:\n")
855 (insert (format " %S : %S\n" msg obj))))
856
857;;*---------------------------------------------------------------------*/
858;;* flyspell-debug-signal-pre-word-checked ... */
859;;*---------------------------------------------------------------------*/
3215afc4
GM
860(defun flyspell-debug-signal-pre-word-checked ()
861 (setq debug-on-error t)
042c6fb7
SM
862 (with-current-buffer (get-buffer-create "*flyspell-debug*")
863 (insert "PRE-WORD:\n")
864 (insert (format " pre-point : %S\n" flyspell-pre-point))
865 (insert (format " pre-buffer : %S\n" flyspell-pre-buffer))
866 (insert (format " cache-start: %S\n" flyspell-word-cache-start))
867 (insert (format " cache-end : %S\n" flyspell-word-cache-end))
868 (goto-char (point-max))))
869
870;;*---------------------------------------------------------------------*/
871;;* flyspell-debug-signal-word-checked ... */
872;;*---------------------------------------------------------------------*/
3215afc4
GM
873(defun flyspell-debug-signal-word-checked ()
874 (setq debug-on-error t)
3f1b25b5
AM
875 (let ((ispell-otherchars (ispell-get-otherchars))
876 (oldbuf (current-buffer))
042c6fb7
SM
877 (point (point)))
878 (with-current-buffer (get-buffer-create "*flyspell-debug*")
60c4db3a
SM
879 (insert
880 "WORD:\n"
881 (format " this-cmd : %S\n" this-command)
882 (format " delayed : %S\n" (and (symbolp this-command)
883 (get this-command
884 'flyspell-delayed)))
885 (format " point : %S\n" point)
886 (format " prev-char : [%c] %S\n"
887 (with-current-buffer oldbuf
888 (if (bobp) ?\ (char-before)))
889 (with-current-buffer oldbuf
890 (if (bobp)
891 nil
892 (save-excursion
893 (backward-char 1)
894 (and (looking-at (flyspell-get-not-casechars))
895 (or (string= "" ispell-otherchars)
896 (not (looking-at ispell-otherchars)))
897 (or flyspell-consider-dash-as-word-delimiter-flag
898 (not (looking-at "\\-")))
899 2)))))
900 (format " because : %S\n"
901 (cond
902 ((not (and (symbolp this-command)
903 (get this-command 'flyspell-delayed)))
904 ;; The current command is not delayed, that
905 ;; is that we must check the word now.
906 'not-delayed)
907 ((with-current-buffer oldbuf
908 (if (bobp)
909 nil
910 (save-excursion
911 (backward-char 1)
912 (and (looking-at (flyspell-get-not-casechars))
913 (or (string= "" ispell-otherchars)
914 (not (looking-at ispell-otherchars)))
915 (or flyspell-consider-dash-as-word-delimiter-flag
916 (not (looking-at "\\-")))))))
917 ;; Yes because we have reached or typed a word delimiter.
918 'separator)
919 ((not (integerp flyspell-delay))
920 ;; Yes because the user set up a no-delay configuration.
921 'no-delay)
922 (t
923 'sit-for))))
3215afc4
GM
924 (goto-char (point-max)))))
925
042c6fb7
SM
926;;*---------------------------------------------------------------------*/
927;;* flyspell-debug-signal-changed-checked ... */
928;;*---------------------------------------------------------------------*/
3215afc4
GM
929(defun flyspell-debug-signal-changed-checked ()
930 (setq debug-on-error t)
042c6fb7
SM
931 (let ((point (point)))
932 (with-current-buffer (get-buffer-create "*flyspell-debug*")
3215afc4
GM
933 (insert "CHANGED WORD:\n")
934 (insert (format " point : %S\n" point))
935 (goto-char (point-max)))))
936
042c6fb7
SM
937;;*---------------------------------------------------------------------*/
938;;* flyspell-post-command-hook ... */
939;;* ------------------------------------------------------------- */
940;;* It is possible that we check several words: */
941;;* 1- the current word is checked if the predicate */
942;;* FLYSPELL-CHECK-WORD-P is true */
943;;* 2- the word that used to be the current word before the */
944;;* THIS-COMMAND is checked if: */
945;;* a- the previous word is different from the current word */
60c4db3a 946;;* b- the previous word has not just been checked by the */
042c6fb7
SM
947;;* previous FLYSPELL-POST-COMMAND-HOOK */
948;;* 3- the words changed by the THIS-COMMAND that are neither the */
949;;* previous word nor the current word */
950;;*---------------------------------------------------------------------*/
60371a2e 951(defun flyspell-post-command-hook ()
e1b0b23a 952 "The `post-command-hook' used by flyspell to check a word on-the-fly."
60371a2e 953 (interactive)
ca0ebecc 954 (when flyspell-mode
e1b0b23a
SM
955 (with-local-quit
956 (let ((command this-command)
957 ;; Prevent anything we do from affecting the mark.
958 deactivate-mark)
959 (if (flyspell-check-pre-word-p)
ffe54a13 960 (save-excursion
e1b0b23a 961 '(flyspell-debug-signal-pre-word-checked)
ffe54a13
AM
962 (goto-char flyspell-pre-point)
963 (flyspell-word)))
e1b0b23a
SM
964 (if (flyspell-check-word-p)
965 (progn
966 '(flyspell-debug-signal-word-checked)
967 ;; FIXME: This should be asynchronous!
968 (flyspell-word)
969 ;; we remember which word we have just checked.
970 ;; this will be used next time we will check a word
971 ;; to compare the next current word with the word
60c4db3a 972 ;; that has been registered in the pre-command-hook
e1b0b23a
SM
973 ;; that is these variables are used within the predicate
974 ;; FLYSPELL-CHECK-PRE-WORD-P
975 (setq flyspell-pre-pre-buffer (current-buffer))
976 (setq flyspell-pre-pre-point (point)))
ffe54a13
AM
977 (setq flyspell-pre-pre-buffer nil)
978 (setq flyspell-pre-pre-point nil)
979 ;; when a word is not checked because of a delayed command
980 ;; we do not disable the ispell cache.
981 (when (and (symbolp this-command)
e1b0b23a 982 (get this-command 'flyspell-delayed))
ffe54a13
AM
983 (setq flyspell-word-cache-end -1)
984 (setq flyspell-word-cache-result '_)))
e1b0b23a
SM
985 (while (and (not (input-pending-p)) (consp flyspell-changes))
986 (let ((start (car (car flyspell-changes)))
987 (stop (cdr (car flyspell-changes))))
988 (if (flyspell-check-changed-word-p start stop)
989 (save-excursion
990 '(flyspell-debug-signal-changed-checked)
991 (goto-char start)
992 (flyspell-word)))
993 (setq flyspell-changes (cdr flyspell-changes))))
994 (setq flyspell-previous-command command)))))
3215afc4 995
042c6fb7
SM
996;;*---------------------------------------------------------------------*/
997;;* flyspell-notify-misspell ... */
998;;*---------------------------------------------------------------------*/
999(defun flyspell-notify-misspell (word poss)
3215afc4
GM
1000 (let ((replacements (if (stringp poss)
1001 poss
1002 (if flyspell-sort-corrections
1003 (sort (car (cdr (cdr poss))) 'string<)
1004 (car (cdr (cdr poss)))))))
73194d67 1005 (if flyspell-issue-message-flag
b8b7c66e 1006 (message "misspelling `%s' %S" word replacements))))
60371a2e 1007
042c6fb7
SM
1008;;*---------------------------------------------------------------------*/
1009;;* flyspell-word-search-backward ... */
1010;;*---------------------------------------------------------------------*/
887d14ad 1011(defun flyspell-word-search-backward (word bound &optional ignore-case)
a8c453e6
RS
1012 (save-excursion
1013 (let ((r '())
15d8dc8b 1014 (inhibit-point-motion-hooks t)
a8c453e6
RS
1015 p)
1016 (while (and (not r) (setq p (search-backward word bound t)))
f3e3a990 1017 (let ((lw (flyspell-get-word)))
887d14ad
LMI
1018 (if (and (consp lw)
1019 (if ignore-case
d224ac83 1020 (string-equal (downcase (car lw)) (downcase word))
887d14ad 1021 (string-equal (car lw) word)))
a8c453e6
RS
1022 (setq r p)
1023 (goto-char p))))
1024 r)))
ebb7de84 1025
042c6fb7
SM
1026;;*---------------------------------------------------------------------*/
1027;;* flyspell-word-search-forward ... */
1028;;*---------------------------------------------------------------------*/
a8c453e6
RS
1029(defun flyspell-word-search-forward (word bound)
1030 (save-excursion
1031 (let ((r '())
15d8dc8b 1032 (inhibit-point-motion-hooks t)
a8c453e6
RS
1033 p)
1034 (while (and (not r) (setq p (search-forward word bound t)))
f3e3a990 1035 (let ((lw (flyspell-get-word)))
a8c453e6
RS
1036 (if (and (consp lw) (string-equal (car lw) word))
1037 (setq r p)
1038 (goto-char (1+ p)))))
1039 r)))
ebb7de84 1040
042c6fb7
SM
1041;;*---------------------------------------------------------------------*/
1042;;* flyspell-word ... */
1043;;*---------------------------------------------------------------------*/
bd4532fc 1044(defun flyspell-word (&optional following known-misspelling)
c2e161b2
GM
1045 "Spell check a word.
1046If the optional argument FOLLOWING, or, when called interactively
1047`ispell-following-word', is non-nil, checks the following (rather
bd4532fc
AM
1048than preceding) word when the cursor is not over a word. If
1049optional argument KNOWN-MISSPELLING is non nil considers word a
1050misspelling and skips redundant spell-checking step."
5a2045ce 1051 (interactive (list ispell-following-word))
caea54f8 1052 (ispell-set-spellchecker-params) ; Initialize variables and dicts alists
60371a2e 1053 (save-excursion
3215afc4 1054 ;; use the correct dictionary
1f44857f 1055 (flyspell-accept-buffer-local-defs)
3215afc4 1056 (let* ((cursor-location (point))
042c6fb7 1057 (flyspell-word (flyspell-get-word following))
9c0fad71 1058 start end poss word ispell-filter)
3215afc4 1059 (if (or (eq flyspell-word nil)
5c9c3eba
RW
1060 (and (fboundp flyspell-generic-check-word-predicate)
1061 (not (funcall flyspell-generic-check-word-predicate))))
a8c453e6 1062 t
60371a2e 1063 (progn
3215afc4
GM
1064 ;; destructure return flyspell-word info list.
1065 (setq start (car (cdr flyspell-word))
1066 end (car (cdr (cdr flyspell-word)))
1067 word (car flyspell-word))
60371a2e
RS
1068 ;; before checking in the directory, we check for doublons.
1069 (cond
3215afc4 1070 ((and (or (not (eq ispell-parser 'tex))
a8c453e6 1071 (and (> start (point-min))
7ad04640 1072 (not (memq (char-after (1- start)) '(?\} ?\\)))))
3215afc4 1073 flyspell-mark-duplications-flag
08fea928 1074 (not (catch 'exception
c11325f7
CY
1075 (let ((dict (or ispell-local-dictionary
1076 ispell-dictionary)))
1077 (dolist (except flyspell-mark-duplications-exceptions)
1078 (and (or (null (car except))
1079 (and (stringp dict)
1080 (string-match (car except) dict)))
1081 (member (downcase word) (cdr except))
1082 (throw 'exception t))))))
60371a2e 1083 (save-excursion
6b8aed24
CY
1084 (goto-char start)
1085 (let* ((bound
1086 (- start
1087 (- end start)
1088 (- (skip-chars-backward " \t\n\f"))))
1089 (p (when (>= bound (point-min))
887d14ad 1090 (flyspell-word-search-backward word bound t))))
6b8aed24 1091 (and p (/= p start)))))
60371a2e 1092 ;; yes, this is a doublon
a8c453e6
RS
1093 (flyspell-highlight-incorrect-region start end 'doublon)
1094 nil)
60371a2e
RS
1095 ((and (eq flyspell-word-cache-start start)
1096 (eq flyspell-word-cache-end end)
1097 (string-equal flyspell-word-cache-word word))
1098 ;; this word had been already checked, we skip
a8c453e6 1099 flyspell-word-cache-result)
60371a2e 1100 ((and (eq ispell-parser 'tex)
3215afc4 1101 (flyspell-tex-command-p flyspell-word))
60371a2e
RS
1102 ;; this is a correct word (because a tex command)
1103 (flyspell-unhighlight-at start)
1104 (if (> end start)
1105 (flyspell-unhighlight-at (- end 1)))
1106 t)
1107 (t
1108 ;; we setup the cache
1109 (setq flyspell-word-cache-start start)
1110 (setq flyspell-word-cache-end end)
1111 (setq flyspell-word-cache-word word)
1112 ;; now check spelling of word.
bd4532fc
AM
1113 (if (not known-misspelling)
1114 (progn
1115 (ispell-send-string "%\n")
1116 ;; put in verbose mode
1117 (ispell-send-string (concat "^" word "\n"))
1118 ;; we mark the ispell process so it can be killed
1119 ;; when emacs is exited without query
85c8c5b6
AM
1120 (if (featurep 'xemacs)
1121 (process-kill-without-query ispell-process)
1122 (set-process-query-on-exit-flag ispell-process nil))
e1b0b23a
SM
1123 ;; Wait until ispell has processed word.
1124 (while (progn
1125 (accept-process-output ispell-process)
1126 (not (string= "" (car ispell-filter)))))
bd4532fc
AM
1127 ;; (ispell-send-string "!\n")
1128 ;; back to terse mode.
1129 ;; Remove leading empty element
1130 (setq ispell-filter (cdr ispell-filter))
1131 ;; ispell process should return something after word is sent.
1132 ;; Tag word as valid (i.e., skip) otherwise
1133 (or ispell-filter
1134 (setq ispell-filter '(*)))
1135 (if (consp ispell-filter)
1136 (setq poss (ispell-parse-output (car ispell-filter)))))
1137 ;; Else, this was a known misspelling to begin with, and
1138 ;; we should forge an ispell return value.
5d2ece3c 1139 (setq poss (list word 1 nil nil)))
a8c453e6
RS
1140 (let ((res (cond ((eq poss t)
1141 ;; correct
1142 (setq flyspell-word-cache-result t)
1143 (flyspell-unhighlight-at start)
1144 (if (> end start)
1145 (flyspell-unhighlight-at (- end 1)))
1146 t)
1147 ((and (stringp poss) flyspell-highlight-flag)
1148 ;; correct
1149 (setq flyspell-word-cache-result t)
1150 (flyspell-unhighlight-at start)
1151 (if (> end start)
1152 (flyspell-unhighlight-at (- end 1)))
1153 t)
1154 ((null poss)
1155 (setq flyspell-word-cache-result t)
1156 (flyspell-unhighlight-at start)
1157 (if (> end start)
1158 (flyspell-unhighlight-at (- end 1)))
1159 t)
1160 ((or (and (< flyspell-duplicate-distance 0)
1161 (or (save-excursion
1162 (goto-char start)
1163 (flyspell-word-search-backward
1164 word
1165 (point-min)))
1166 (save-excursion
1167 (goto-char end)
1168 (flyspell-word-search-forward
1169 word
1170 (point-max)))))
1171 (and (> flyspell-duplicate-distance 0)
1172 (or (save-excursion
1173 (goto-char start)
1174 (flyspell-word-search-backward
1175 word
1176 (- start
1177 flyspell-duplicate-distance)))
1178 (save-excursion
1179 (goto-char end)
1180 (flyspell-word-search-forward
1181 word
1182 (+ end
1183 flyspell-duplicate-distance))))))
91346f54
RS
1184 ;; This is a misspelled word which occurs
1185 ;; twice within flyspell-duplicate-distance.
a8c453e6
RS
1186 (setq flyspell-word-cache-result nil)
1187 (if flyspell-highlight-flag
1188 (flyspell-highlight-duplicate-region
1189 start end poss)
5673af85 1190 (message "duplicate `%s'" word))
a8c453e6
RS
1191 nil)
1192 (t
1193 (setq flyspell-word-cache-result nil)
05bbe066
CY
1194 ;; Highlight the location as incorrect,
1195 ;; including offset specified in POSS.
a8c453e6
RS
1196 (if flyspell-highlight-flag
1197 (flyspell-highlight-incorrect-region
05bbe066
CY
1198 (if (and (consp poss)
1199 (integerp (nth 1 poss)))
1200 (+ start (nth 1 poss) -1)
1201 start)
1202 end poss)
042c6fb7 1203 (flyspell-notify-misspell word poss))
a8c453e6
RS
1204 nil))))
1205 ;; return to original location
ebb7de84 1206 (goto-char cursor-location)
a8c453e6
RS
1207 (if ispell-quit (setq ispell-quit nil))
1208 res))))))))
60371a2e 1209
042c6fb7
SM
1210;;*---------------------------------------------------------------------*/
1211;;* flyspell-math-tex-command-p ... */
1212;;* ------------------------------------------------------------- */
f768e8e8
CY
1213;;* This function uses the texmathp package to check if point */
1214;;* is within a TeX math environment. `texmathp' can yield errors */
1215;;* if the document is currently not valid TeX syntax. */
042c6fb7 1216;;*---------------------------------------------------------------------*/
3215afc4 1217(defun flyspell-math-tex-command-p ()
7ad04640 1218 (when (fboundp 'texmathp)
f768e8e8
CY
1219 (if flyspell-check-tex-math-command
1220 nil
7ad04640
SM
1221 (condition-case nil
1222 (texmathp)
f768e8e8 1223 (error nil)))))
3215afc4 1224
042c6fb7
SM
1225;;*---------------------------------------------------------------------*/
1226;;* flyspell-tex-command-p ... */
1227;;*---------------------------------------------------------------------*/
60371a2e 1228(defun flyspell-tex-command-p (word)
0a67052f 1229 "Return t if WORD is a TeX command."
3215afc4
GM
1230 (or (save-excursion
1231 (let ((b (car (cdr word))))
1232 (and (re-search-backward "\\\\" (- (point) 100) t)
1233 (or (= (match-end 0) b)
1234 (and (goto-char (match-end 0))
1235 (looking-at flyspell-tex-command-regexp)
1236 (>= (match-end 0) b))))))
1237 (flyspell-math-tex-command-p)))
60371a2e 1238
60c4db3a
SM
1239(defalias 'flyspell-get-casechars 'ispell-get-casechars)
1240(defalias 'flyspell-get-not-casechars 'ispell-get-not-casechars)
60371a2e 1241
042c6fb7
SM
1242;;*---------------------------------------------------------------------*/
1243;;* flyspell-get-word ... */
1244;;*---------------------------------------------------------------------*/
f3e3a990 1245(defun flyspell-get-word (&optional following extra-otherchars)
60371a2e 1246 "Return the word for spell-checking according to Ispell syntax.
c2e161b2
GM
1247Optional argument FOLLOWING non-nil means to get the following
1248\(rather than preceding) word when the cursor is not over a word.
1249Optional second argument EXTRA-OTHERCHARS is a regexp of characters
1250that may be included as part of a word (see `ispell-dictionary-alist')."
60371a2e
RS
1251 (let* ((flyspell-casechars (flyspell-get-casechars))
1252 (flyspell-not-casechars (flyspell-get-not-casechars))
1253 (ispell-otherchars (ispell-get-otherchars))
1254 (ispell-many-otherchars-p (ispell-get-many-otherchars-p))
a8c453e6
RS
1255 (word-regexp (concat flyspell-casechars
1256 "+\\("
1257 (if (not (string= "" ispell-otherchars))
1258 (concat ispell-otherchars "?"))
1259 (if extra-otherchars
1260 (concat extra-otherchars "?"))
1261 flyspell-casechars
1262 "+\\)"
1263 (if (or ispell-many-otherchars-p
1264 extra-otherchars)
1265 "*" "?")))
1266 did-it-once prevpt
60371a2e
RS
1267 start end word)
1268 ;; find the word
3215afc4 1269 (if (not (looking-at flyspell-casechars))
60371a2e 1270 (if following
9c841316
SM
1271 (re-search-forward flyspell-casechars nil t)
1272 (re-search-backward flyspell-casechars nil t)))
60371a2e 1273 ;; move to front of word
9c841316 1274 (re-search-backward flyspell-not-casechars nil 'start)
a8c453e6
RS
1275 (while (and (or (and (not (string= "" ispell-otherchars))
1276 (looking-at ispell-otherchars))
1277 (and extra-otherchars (looking-at extra-otherchars)))
1278 (not (bobp))
1279 (or (not did-it-once)
1280 ispell-many-otherchars-p)
1281 (not (eq prevpt (point))))
1282 (if (and extra-otherchars (looking-at extra-otherchars))
1283 (progn
9658746b
RS
1284 (backward-char 1)
1285 (if (looking-at flyspell-casechars)
9c841316 1286 (re-search-backward flyspell-not-casechars nil 'move)))
a8c453e6
RS
1287 (setq did-it-once t
1288 prevpt (point))
1289 (backward-char 1)
1290 (if (looking-at flyspell-casechars)
9c841316 1291 (re-search-backward flyspell-not-casechars nil 'move)
a8c453e6 1292 (backward-char -1))))
60371a2e 1293 ;; Now mark the word and save to string.
9c841316 1294 (if (not (re-search-forward word-regexp nil t))
60371a2e
RS
1295 nil
1296 (progn
1297 (setq start (match-beginning 0)
1298 end (point)
11570a8f 1299 word (buffer-substring-no-properties start end))
60371a2e
RS
1300 (list word start end)))))
1301
042c6fb7
SM
1302;;*---------------------------------------------------------------------*/
1303;;* flyspell-small-region ... */
1304;;*---------------------------------------------------------------------*/
3215afc4 1305(defun flyspell-small-region (beg end)
60371a2e 1306 "Flyspell text between BEG and END."
60371a2e 1307 (save-excursion
3215afc4
GM
1308 (if (> beg end)
1309 (let ((old beg))
1310 (setq beg end)
1311 (setq end old)))
60371a2e 1312 (goto-char beg)
1d8a80f0
RS
1313 (let ((count 0))
1314 (while (< (point) end)
73194d67 1315 (if (and flyspell-issue-message-flag (= count 100))
1d8a80f0
RS
1316 (progn
1317 (message "Spell Checking...%d%%"
65a5c06a 1318 (* 100 (/ (float (- (point) beg)) (- end beg))))
1d8a80f0
RS
1319 (setq count 0))
1320 (setq count (+ 1 count)))
1321 (flyspell-word)
3215afc4 1322 (sit-for 0)
1d8a80f0
RS
1323 (let ((cur (point)))
1324 (forward-word 1)
1325 (if (and (< (point) end) (> (point) (+ cur 1)))
1326 (backward-char 1)))))
60371a2e 1327 (backward-char 1)
73194d67 1328 (if flyspell-issue-message-flag (message "Spell Checking completed."))
60371a2e
RS
1329 (flyspell-word)))
1330
042c6fb7
SM
1331;;*---------------------------------------------------------------------*/
1332;;* flyspell-external-ispell-process ... */
1333;;*---------------------------------------------------------------------*/
3215afc4 1334(defvar flyspell-external-ispell-process '()
1f44857f 1335 "The external Flyspell Ispell process.")
3215afc4 1336
042c6fb7
SM
1337;;*---------------------------------------------------------------------*/
1338;;* flyspell-external-ispell-buffer ... */
1339;;*---------------------------------------------------------------------*/
3215afc4
GM
1340(defvar flyspell-external-ispell-buffer '())
1341(defvar flyspell-large-region-buffer '())
1342(defvar flyspell-large-region-beg (point-min))
1343(defvar flyspell-large-region-end (point-max))
1344
042c6fb7
SM
1345;;*---------------------------------------------------------------------*/
1346;;* flyspell-external-point-words ... */
1347;;*---------------------------------------------------------------------*/
3215afc4 1348(defun flyspell-external-point-words ()
cc8556d9
RS
1349 "Mark words from a buffer listing incorrect words in order of appearance.
1350The list of incorrect words should be in `flyspell-external-ispell-buffer'.
1351\(We finish by killing that buffer and setting the variable to nil.)
1352The buffer to mark them in is `flyspell-large-region-buffer'."
27e0edcd 1353 (let (words-not-found
51978cac 1354 (ispell-otherchars (ispell-get-otherchars))
ee65a132 1355 (buffer-scan-pos flyspell-large-region-beg)
55faab0a 1356 case-fold-search)
27e0edcd
EZ
1357 (with-current-buffer flyspell-external-ispell-buffer
1358 (goto-char (point-min))
51978cac
RS
1359 ;; Loop over incorrect words, in the order they were reported,
1360 ;; which is also the order they appear in the buffer being checked.
9c841316 1361 (while (re-search-forward "\\([^\n]+\\)\n" nil t)
27e0edcd
EZ
1362 ;; Bind WORD to the next one.
1363 (let ((word (match-string 1)) (wordpos (point)))
1364 ;; Here there used to be code to see if WORD is the same
1365 ;; as the previous iteration, and count the number of consecutive
1366 ;; identical words, and the loop below would search for that many.
1367 ;; That code seemed to be incorrect, and on principle, should
1368 ;; be unnecessary too. -- rms.
1369 (if flyspell-issue-message-flag
1370 (message "Spell Checking...%d%% [%s]"
1371 (* 100 (/ (float (point)) (point-max)))
1372 word))
1373 (with-current-buffer flyspell-large-region-buffer
51978cac 1374 (goto-char buffer-scan-pos)
27e0edcd
EZ
1375 (let ((keep t))
1376 ;; Iterate on string search until string is found as word,
60c4db3a 1377 ;; not as substring.
27e0edcd
EZ
1378 (while keep
1379 (if (search-forward word
1380 flyspell-large-region-end t)
51978cac
RS
1381 (let* ((found-list
1382 (save-excursion
1383 ;; Move back into the match
1384 ;; so flyspell-get-word will find it.
1385 (forward-char -1)
f3e3a990 1386 (flyspell-get-word)))
51978cac
RS
1387 (found (car found-list))
1388 (found-length (length found))
1389 (misspell-length (length word)))
1390 (when (or
1391 ;; Size matches, we really found it.
1392 (= found-length misspell-length)
60c4db3a
SM
1393 ;; Matches as part of a boundary-char separated
1394 ;; word.
51978cac
RS
1395 (member word
1396 (split-string found ispell-otherchars))
1397 ;; Misspelling has higher length than
60c4db3a
SM
1398 ;; what flyspell considers the word.
1399 ;; Caused by boundary-chars mismatch.
1400 ;; Validating seems safe.
51978cac
RS
1401 (< found-length misspell-length)
1402 ;; ispell treats beginning of some TeX
1403 ;; commands as nroff control sequences
1404 ;; and strips them in the list of
1405 ;; misspelled words thus giving a
1406 ;; non-existent word. Skip if ispell
1407 ;; is used, string is a TeX command
1408 ;; (char before beginning of word is
1409 ;; backslash) and none of the previous
da6062e6 1410 ;; conditions match.
51978cac
RS
1411 (and (not ispell-really-aspell)
1412 (save-excursion
1413 (goto-char (- (nth 1 found-list) 1))
1414 (if (looking-at "[\\]" )
1415 t
1416 nil))))
1417 (setq keep nil)
bd4532fc 1418 (flyspell-word nil t)
51978cac
RS
1419 ;; Search for next misspelled word will begin from
1420 ;; end of last validated match.
1421 (setq buffer-scan-pos (point))))
27e0edcd
EZ
1422 ;; Record if misspelling is not found and try new one
1423 (add-to-list 'words-not-found
1424 (concat " -> " word " - "
1425 (int-to-string wordpos)))
1426 (setq keep nil)))))))
1427 ;; we are done
1428 (if flyspell-issue-message-flag (message "Spell Checking completed.")))
1429 ;; Warn about not found misspellings
1430 (dolist (word words-not-found)
1431 (message "%s: word not found" word))
1432 ;; Kill and forget the buffer with the list of incorrect words.
1433 (kill-buffer flyspell-external-ispell-buffer)
1434 (setq flyspell-external-ispell-buffer nil)))
2fced6f9 1435
042c6fb7
SM
1436;;*---------------------------------------------------------------------*/
1437;;* flyspell-process-localwords ... */
1438;;* ------------------------------------------------------------- */
1439;;* This function is used to prevent marking of words explicitly */
1440;;* declared correct. */
1441;;*---------------------------------------------------------------------*/
b8b7c66e 1442(defun flyspell-process-localwords (misspellings-buffer)
da00640a
AM
1443 (let ((localwords ispell-buffer-session-localwords)
1444 case-fold-search
b8b7c66e
RS
1445 (ispell-casechars (ispell-get-casechars)))
1446 ;; Get localwords from the original buffer
1447 (save-excursion
1448 (goto-char (point-min))
1449 ;; Localwords parsing copied from ispell.el.
1450 (while (search-forward ispell-words-keyword nil t)
e180ab9f 1451 (let ((end (point-at-eol))
b8b7c66e
RS
1452 string)
1453 ;; buffer-local words separated by a space, and can contain
1454 ;; any character other than a space. Not rigorous enough.
1455 (while (re-search-forward " *\\([^ ]+\\)" end t)
1456 (setq string (buffer-substring-no-properties (match-beginning 1)
1457 (match-end 1)))
1458 ;; This can fail when string contains a word with invalid chars.
1459 ;; Error handling needs to be added between Ispell and Emacs.
27e0edcd 1460 (if (and (< 1 (length string))
b8b7c66e
RS
1461 (equal 0 (string-match ispell-casechars string)))
1462 (push string localwords))))))
1463 ;; Remove localwords matches from misspellings-buffer.
1464 ;; The usual mechanism of communicating the local words to ispell
1465 ;; does not affect the special ispell process used by
1466 ;; flyspell-large-region.
1467 (with-current-buffer misspellings-buffer
1468 (save-excursion
1469 (dolist (word localwords)
1470 (goto-char (point-min))
1471 (let ((regexp (concat "^" word "\n")))
1472 (while (re-search-forward regexp nil t)
1473 (delete-region (match-beginning 0) (match-end 0)))))))))
1474
5c823193
CY
1475;;* ---------------------------------------------------------------
1476;;* flyspell-check-region-doublons
1477;;* ---------------------------------------------------------------
1478(defun flyspell-check-region-doublons (beg end)
1479 "Check for adjacent duplicated words (doublons) in the given region."
1480 (save-excursion
1481 (goto-char beg)
1482 (flyspell-word) ; Make sure current word is checked
1483 (backward-word 1)
1484 (while (and (< (point) end)
081ff0c9 1485 (re-search-forward "\\<\\(\\w+\\)\\>[ \n\t\f]+\\1\\>"
5c823193
CY
1486 end 'move))
1487 (flyspell-word)
1488 (backward-word 1))
1489 (flyspell-word)))
1490
042c6fb7
SM
1491;;*---------------------------------------------------------------------*/
1492;;* flyspell-large-region ... */
1493;;*---------------------------------------------------------------------*/
3215afc4
GM
1494(defun flyspell-large-region (beg end)
1495 (let* ((curbuf (current-buffer))
1496 (buffer (get-buffer-create "*flyspell-region*")))
1497 (setq flyspell-external-ispell-buffer buffer)
1498 (setq flyspell-large-region-buffer curbuf)
1499 (setq flyspell-large-region-beg beg)
1500 (setq flyspell-large-region-end end)
b8b7c66e 1501 (flyspell-accept-buffer-local-defs)
3215afc4
GM
1502 (set-buffer buffer)
1503 (erase-buffer)
25f2ad05 1504 ;; this is done, we can start checking...
73194d67 1505 (if flyspell-issue-message-flag (message "Checking region..."))
3215afc4 1506 (set-buffer curbuf)
caea54f8 1507 (ispell-set-spellchecker-params) ; Initialize variables and dicts alists
a3614e04
GM
1508 ;; Local dictionary becomes the global dictionary in use.
1509 (setq ispell-current-dictionary
1510 (or ispell-local-dictionary ispell-dictionary))
1511 (setq ispell-current-personal-dictionary
1512 (or ispell-local-pdict ispell-personal-dictionary))
1513 (let ((args (ispell-get-ispell-args))
1514 (encoding (ispell-get-coding-system))
1515 c)
1516 (if (and ispell-current-dictionary ; use specified dictionary
1517 (not (member "-d" args))) ; only define if not overridden
1518 (setq args
1519 (append (list "-d" ispell-current-dictionary) args)))
1520 (if ispell-current-personal-dictionary ; use specified pers dict
1521 (setq args
1522 (append args
1523 (list "-p"
1524 (expand-file-name
1525 ispell-current-personal-dictionary)))))
8b7a997c
AM
1526
1527 ;; Check for extended character mode
1528 (let ((extended-char-mode (ispell-get-extended-character-mode)))
1529 (and extended-char-mode ; ~ extended character mode
1530 (string-match "[^~]+$" extended-char-mode)
1531 (add-to-list 'args (concat "-T" (match-string 0 extended-char-mode)))))
1532
1533 ;; Add ispell-extra-args
a3614e04 1534 (setq args (append args ispell-extra-args))
c478e4c5
AM
1535
1536 ;; If we are using recent aspell or hunspell, make sure we use the right encoding
1537 ;; for communication. ispell or older aspell/hunspell does not support this
57bf8fd4 1538 (if ispell-encoding8-command
a3614e04
GM
1539 (setq args
1540 (append args
64a440db
AM
1541 (if ispell-really-hunspell
1542 (list ispell-encoding8-command
1543 (upcase (symbol-name encoding)))
1544 (list (concat ispell-encoding8-command
1545 (symbol-name encoding)))))))
c478e4c5 1546
a3614e04
GM
1547 (let ((process-coding-system-alist (list (cons "\\.*" encoding))))
1548 (setq c (apply 'ispell-call-process-region beg
1549 end
1550 ispell-program-name
1551 nil
1552 buffer
1553 nil
1554 (if ispell-really-aspell "list" "-l")
1555 args)))
15502042 1556 (if (eq c 0)
b8b7c66e
RS
1557 (progn
1558 (flyspell-process-localwords buffer)
1559 (with-current-buffer curbuf
5c823193
CY
1560 (flyspell-delete-region-overlays beg end)
1561 (flyspell-check-region-doublons beg end))
b8b7c66e 1562 (flyspell-external-point-words))
c2e161b2 1563 (error "Can't check region")))))
3215afc4 1564
042c6fb7
SM
1565;;*---------------------------------------------------------------------*/
1566;;* flyspell-region ... */
1567;;* ------------------------------------------------------------- */
1568;;* Because `ispell -a' is too slow, it is not possible to use */
1569;;* it on large region. Then, when ispell is invoked on a large */
1570;;* text region, a new `ispell -l' process is spawned. The */
1571;;* pointed out words are then searched in the region a checked with */
1572;;* regular flyspell means. */
1573;;*---------------------------------------------------------------------*/
3bb710b0 1574;;;###autoload
3215afc4
GM
1575(defun flyspell-region (beg end)
1576 "Flyspell text between BEG and END."
1577 (interactive "r")
caea54f8 1578 (ispell-set-spellchecker-params) ; Initialize variables and dicts alists
3215afc4
GM
1579 (if (= beg end)
1580 ()
1581 (save-excursion
1582 (if (> beg end)
1583 (let ((old beg))
1584 (setq beg end)
1585 (setq end old)))
a8c453e6 1586 (if (and flyspell-large-region (> (- end beg) flyspell-large-region))
3215afc4
GM
1587 (flyspell-large-region beg end)
1588 (flyspell-small-region beg end)))))
1589
042c6fb7
SM
1590;;*---------------------------------------------------------------------*/
1591;;* flyspell-buffer ... */
1592;;*---------------------------------------------------------------------*/
3bb710b0 1593;;;###autoload
60371a2e
RS
1594(defun flyspell-buffer ()
1595 "Flyspell whole buffer."
1596 (interactive)
1597 (flyspell-region (point-min) (point-max)))
1598
042c6fb7
SM
1599;;*---------------------------------------------------------------------*/
1600;;* old next error position ... */
1601;;*---------------------------------------------------------------------*/
3215afc4
GM
1602(defvar flyspell-old-buffer-error nil)
1603(defvar flyspell-old-pos-error nil)
1604
042c6fb7
SM
1605;;*---------------------------------------------------------------------*/
1606;;* flyspell-goto-next-error ... */
1607;;*---------------------------------------------------------------------*/
3215afc4
GM
1608(defun flyspell-goto-next-error ()
1609 "Go to the next previously detected error.
1610In general FLYSPELL-GOTO-NEXT-ERROR must be used after
1611FLYSPELL-BUFFER."
1612 (interactive)
1613 (let ((pos (point))
1614 (max (point-max)))
1615 (if (and (eq (current-buffer) flyspell-old-buffer-error)
1616 (eq pos flyspell-old-pos-error))
1617 (progn
1618 (if (= flyspell-old-pos-error max)
1619 ;; goto beginning of buffer
1620 (progn
1621 (message "Restarting from beginning of buffer")
1622 (goto-char (point-min)))
1623 (forward-word 1))
1624 (setq pos (point))))
1625 ;; seek the next error
1626 (while (and (< pos max)
1627 (let ((ovs (overlays-at pos))
1628 (r '()))
1629 (while (and (not r) (consp ovs))
1630 (if (flyspell-overlay-p (car ovs))
1631 (setq r t)
1632 (setq ovs (cdr ovs))))
1633 (not r)))
1634 (setq pos (1+ pos)))
25f2ad05 1635 ;; save the current location for next invocation
3215afc4
GM
1636 (setq flyspell-old-pos-error pos)
1637 (setq flyspell-old-buffer-error (current-buffer))
1638 (goto-char pos)
1639 (if (= pos max)
1640 (message "No more miss-spelled word!"))))
1641
042c6fb7
SM
1642;;*---------------------------------------------------------------------*/
1643;;* flyspell-overlay-p ... */
1644;;*---------------------------------------------------------------------*/
60371a2e 1645(defun flyspell-overlay-p (o)
3ecd3a56 1646 "Return true if O is an overlay used by flyspell."
60371a2e
RS
1647 (and (overlayp o) (overlay-get o 'flyspell-overlay)))
1648
042c6fb7
SM
1649;;*---------------------------------------------------------------------*/
1650;;* flyspell-delete-region-overlays, flyspell-delete-all-overlays */
1651;;* ------------------------------------------------------------- */
1652;;* Remove overlays introduced by flyspell. */
1653;;*---------------------------------------------------------------------*/
b8b7c66e
RS
1654(defun flyspell-delete-region-overlays (beg end)
1655 "Delete overlays used by flyspell in a given region."
85c8c5b6
AM
1656 (if (featurep 'emacs)
1657 (remove-overlays beg end 'flyspell-overlay t)
1658 ;; XEmacs does not have `remove-overlays'
1659 (let ((l (overlays-in beg end)))
1660 (while (consp l)
1661 (progn
1662 (if (flyspell-overlay-p (car l))
1663 (delete-overlay (car l)))
1664 (setq l (cdr l)))))))
b8b7c66e
RS
1665
1666(defun flyspell-delete-all-overlays ()
1667 "Delete all the overlays used by flyspell."
85c8c5b6 1668 (flyspell-delete-region-overlays (point-min) (point-max)))
b8b7c66e 1669
042c6fb7
SM
1670;;*---------------------------------------------------------------------*/
1671;;* flyspell-unhighlight-at ... */
1672;;*---------------------------------------------------------------------*/
60371a2e
RS
1673(defun flyspell-unhighlight-at (pos)
1674 "Remove the flyspell overlay that are located at POS."
1675 (if flyspell-persistent-highlight
1676 (let ((overlays (overlays-at pos)))
1677 (while (consp overlays)
1678 (if (flyspell-overlay-p (car overlays))
1679 (delete-overlay (car overlays)))
1680 (setq overlays (cdr overlays))))
3215afc4 1681 (if (flyspell-overlay-p flyspell-overlay)
042c6fb7 1682 (delete-overlay flyspell-overlay))))
60371a2e 1683
042c6fb7
SM
1684;;*---------------------------------------------------------------------*/
1685;;* flyspell-properties-at-p ... */
1686;;* ------------------------------------------------------------- */
1687;;* Is there an highlight properties at position pos? */
1688;;*---------------------------------------------------------------------*/
65a5c06a
RS
1689(defun flyspell-properties-at-p (pos)
1690 "Return t if there is a text property at POS, not counting `local-map'.
1691If variable `flyspell-highlight-properties' is set to nil,
1692text with properties are not checked. This function is used to discover
1693if the character at POS has any other property."
1694 (let ((prop (text-properties-at pos))
60371a2e
RS
1695 (keep t))
1696 (while (and keep (consp prop))
1697 (if (and (eq (car prop) 'local-map) (consp (cdr prop)))
1698 (setq prop (cdr (cdr prop)))
1699 (setq keep nil)))
1700 (consp prop)))
1701
042c6fb7
SM
1702;;*---------------------------------------------------------------------*/
1703;;* make-flyspell-overlay ... */
1704;;*---------------------------------------------------------------------*/
60371a2e 1705(defun make-flyspell-overlay (beg end face mouse-face)
0a67052f
RS
1706 "Allocate an overlay to highlight an incorrect word.
1707BEG and END specify the range in the buffer of that word.
1708FACE and MOUSE-FACE specify the `face' and `mouse-face' properties
1709for the overlay."
042c6fb7
SM
1710 (let ((overlay (make-overlay beg end nil t nil)))
1711 (overlay-put overlay 'face face)
1712 (overlay-put overlay 'mouse-face mouse-face)
1713 (overlay-put overlay 'flyspell-overlay t)
1714 (overlay-put overlay 'evaporate t)
1715 (overlay-put overlay 'help-echo "mouse-2: correct word at point")
1716 (overlay-put overlay 'keymap flyspell-mouse-map)
c43aed5a 1717 (when (eq face 'flyspell-incorrect)
73194d67 1718 (and (stringp flyspell-before-incorrect-word-string)
042c6fb7 1719 (overlay-put overlay 'before-string
73194d67
PJ
1720 flyspell-before-incorrect-word-string))
1721 (and (stringp flyspell-after-incorrect-word-string)
042c6fb7 1722 (overlay-put overlay 'after-string
73194d67 1723 flyspell-after-incorrect-word-string)))
042c6fb7 1724 overlay))
2fced6f9 1725
042c6fb7
SM
1726;;*---------------------------------------------------------------------*/
1727;;* flyspell-highlight-incorrect-region ... */
1728;;*---------------------------------------------------------------------*/
3215afc4 1729(defun flyspell-highlight-incorrect-region (beg end poss)
91346f54
RS
1730 "Set up an overlay on a misspelled word, in the buffer from BEG to END.
1731POSS is usually a list of possible spelling/correction lists,
1732as returned by `ispell-parse-output'.
1733It can also be the symbol `doublon', in the case where the word
1734is itself incorrect, but suspiciously repeated."
4c685fb8
JW
1735 (let ((inhibit-read-only t))
1736 (unless (run-hook-with-args-until-success
1737 'flyspell-incorrect-hook beg end poss)
1738 (if (or flyspell-highlight-properties
1739 (not (flyspell-properties-at-p beg)))
1740 (progn
a8c453e6
RS
1741 ;; we cleanup all the overlay that are in the region, not
1742 ;; beginning at the word start position
1743 (if (< (1+ beg) end)
1744 (let ((os (overlays-in (1+ beg) end)))
1745 (while (consp os)
1746 (if (flyspell-overlay-p (car os))
1747 (delete-overlay (car os)))
1748 (setq os (cdr os)))))
4c685fb8 1749 ;; we cleanup current overlay at the same position
042c6fb7 1750 (flyspell-unhighlight-at beg)
4c685fb8
JW
1751 ;; now we can use a new overlay
1752 (setq flyspell-overlay
1753 (make-flyspell-overlay
6b8aed24
CY
1754 beg end
1755 (if (eq poss 'doublon) 'flyspell-duplicate 'flyspell-incorrect)
1756 'highlight)))))))
60371a2e 1757
042c6fb7
SM
1758;;*---------------------------------------------------------------------*/
1759;;* flyspell-highlight-duplicate-region ... */
1760;;*---------------------------------------------------------------------*/
5106fd70 1761(defun flyspell-highlight-duplicate-region (beg end poss)
91346f54
RS
1762 "Set up an overlay on a duplicate misspelled word, in the buffer from BEG to END.
1763POSS is a list of possible spelling/correction lists,
1764as returned by `ispell-parse-output'."
4c685fb8
JW
1765 (let ((inhibit-read-only t))
1766 (unless (run-hook-with-args-until-success
1767 'flyspell-incorrect-hook beg end poss)
1768 (if (or flyspell-highlight-properties
1769 (not (flyspell-properties-at-p beg)))
1770 (progn
1771 ;; we cleanup current overlay at the same position
042c6fb7 1772 (flyspell-unhighlight-at beg)
4c685fb8
JW
1773 ;; now we can use a new overlay
1774 (setq flyspell-overlay
1775 (make-flyspell-overlay beg end
c43aed5a 1776 'flyspell-duplicate
4c685fb8 1777 'highlight)))))))
60371a2e 1778
042c6fb7
SM
1779;;*---------------------------------------------------------------------*/
1780;;* flyspell-auto-correct-cache ... */
1781;;*---------------------------------------------------------------------*/
60371a2e
RS
1782(defvar flyspell-auto-correct-pos nil)
1783(defvar flyspell-auto-correct-region nil)
1784(defvar flyspell-auto-correct-ring nil)
3215afc4
GM
1785(defvar flyspell-auto-correct-word nil)
1786(make-variable-buffer-local 'flyspell-auto-correct-pos)
1787(make-variable-buffer-local 'flyspell-auto-correct-region)
1788(make-variable-buffer-local 'flyspell-auto-correct-ring)
1789(make-variable-buffer-local 'flyspell-auto-correct-word)
60371a2e 1790
042c6fb7
SM
1791;;*---------------------------------------------------------------------*/
1792;;* flyspell-check-previous-highlighted-word ... */
1793;;*---------------------------------------------------------------------*/
3215afc4 1794(defun flyspell-check-previous-highlighted-word (&optional arg)
25f2ad05 1795 "Correct the closer misspelled word.
3215afc4
GM
1796This function scans a mis-spelled word before the cursor. If it finds one
1797it proposes replacement for that word. With prefix arg, count that many
1798misspelled words backwards."
1799 (interactive)
1800 (let ((pos1 (point))
1801 (pos (point))
1802 (arg (if (or (not (numberp arg)) (< arg 1)) 1 arg))
1803 ov ovs)
1804 (if (catch 'exit
1805 (while (and (setq pos (previous-overlay-change pos))
1806 (not (= pos pos1)))
1807 (setq pos1 pos)
1808 (if (> pos (point-min))
1809 (progn
1810 (setq ovs (overlays-at (1- pos)))
1811 (while (consp ovs)
1812 (setq ov (car ovs))
1813 (setq ovs (cdr ovs))
042c6fb7 1814 (if (and (flyspell-overlay-p ov)
3215afc4
GM
1815 (= 0 (setq arg (1- arg))))
1816 (throw 'exit t)))))))
60371a2e 1817 (save-excursion
3215afc4 1818 (goto-char pos)
c11a5a9c
AM
1819 (ispell-word)
1820 (setq flyspell-word-cache-word nil) ;; Force flyspell-word re-check
1821 (flyspell-word))
1f44857f 1822 (error "No word to correct before point"))))
3215afc4 1823
042c6fb7
SM
1824;;*---------------------------------------------------------------------*/
1825;;* flyspell-display-next-corrections ... */
1826;;*---------------------------------------------------------------------*/
3215afc4
GM
1827(defun flyspell-display-next-corrections (corrections)
1828 (let ((string "Corrections:")
1829 (l corrections)
1830 (pos '()))
1831 (while (< (length string) 80)
1832 (if (equal (car l) flyspell-auto-correct-word)
1833 (setq pos (cons (+ 1 (length string)) pos)))
1834 (setq string (concat string " " (car l)))
1835 (setq l (cdr l)))
1836 (while (consp pos)
1837 (let ((num (car pos)))
1838 (put-text-property num
1839 (+ num (length flyspell-auto-correct-word))
c43aed5a 1840 'face 'flyspell-incorrect
3215afc4
GM
1841 string))
1842 (setq pos (cdr pos)))
1843 (if (fboundp 'display-message)
1844 (display-message 'no-log string)
5673af85 1845 (message "%s" string))))
3215afc4 1846
042c6fb7
SM
1847;;*---------------------------------------------------------------------*/
1848;;* flyspell-abbrev-table ... */
1849;;*---------------------------------------------------------------------*/
3215afc4
GM
1850(defun flyspell-abbrev-table ()
1851 (if flyspell-use-global-abbrev-table-p
1852 global-abbrev-table
67d120eb 1853 (or local-abbrev-table global-abbrev-table)))
3215afc4 1854
042c6fb7
SM
1855;;*---------------------------------------------------------------------*/
1856;;* flyspell-define-abbrev ... */
1857;;*---------------------------------------------------------------------*/
73194d67
PJ
1858(defun flyspell-define-abbrev (name expansion)
1859 (let ((table (flyspell-abbrev-table)))
1860 (when table
2aebf08d 1861 (define-abbrev table (downcase name) expansion))))
73194d67 1862
042c6fb7
SM
1863;;*---------------------------------------------------------------------*/
1864;;* flyspell-auto-correct-word ... */
1865;;*---------------------------------------------------------------------*/
3215afc4
GM
1866(defun flyspell-auto-correct-word ()
1867 "Correct the current word.
1868This command proposes various successive corrections for the current word."
1869 (interactive)
1870 (let ((pos (point))
1871 (old-max (point-max)))
60c4db3a 1872 ;; Use the correct dictionary.
3215afc4
GM
1873 (flyspell-accept-buffer-local-defs)
1874 (if (and (eq flyspell-auto-correct-pos pos)
1875 (consp flyspell-auto-correct-region))
60c4db3a 1876 ;; We have already been using the function at the same location.
3215afc4
GM
1877 (let* ((start (car flyspell-auto-correct-region))
1878 (len (cdr flyspell-auto-correct-region)))
73194d67 1879 (flyspell-unhighlight-at start)
3215afc4
GM
1880 (delete-region start (+ start len))
1881 (setq flyspell-auto-correct-ring (cdr flyspell-auto-correct-ring))
1882 (let* ((word (car flyspell-auto-correct-ring))
1883 (len (length word)))
1884 (rplacd flyspell-auto-correct-region len)
1885 (goto-char start)
1886 (if flyspell-abbrev-p
1887 (if (flyspell-already-abbrevp (flyspell-abbrev-table)
1888 flyspell-auto-correct-word)
1889 (flyspell-change-abbrev (flyspell-abbrev-table)
1890 flyspell-auto-correct-word
1891 word)
73194d67
PJ
1892 (flyspell-define-abbrev flyspell-auto-correct-word word)))
1893 (funcall flyspell-insert-function word)
3215afc4
GM
1894 (flyspell-word)
1895 (flyspell-display-next-corrections flyspell-auto-correct-ring))
1896 (flyspell-ajust-cursor-point pos (point) old-max)
1897 (setq flyspell-auto-correct-pos (point)))
60c4db3a 1898 ;; Fetch the word to be checked.
f3e3a990 1899 (let ((word (flyspell-get-word)))
a8c453e6
RS
1900 (if (consp word)
1901 (let ((start (car (cdr word)))
1902 (end (car (cdr (cdr word))))
1903 (word (car word))
9c0fad71 1904 poss ispell-filter)
a8c453e6 1905 (setq flyspell-auto-correct-word word)
60c4db3a
SM
1906 ;; Now check spelling of word..
1907 (ispell-send-string "%\n") ;Put in verbose mode.
042c6fb7 1908 (ispell-send-string (concat "^" word "\n"))
60c4db3a 1909 ;; Wait until ispell has processed word.
042c6fb7
SM
1910 (while (progn
1911 (accept-process-output ispell-process)
1912 (not (string= "" (car ispell-filter)))))
60c4db3a 1913 ;; Remove leading empty element.
a8c453e6 1914 (setq ispell-filter (cdr ispell-filter))
60c4db3a
SM
1915 ;; Ispell process should return something after word is sent.
1916 ;; Tag word as valid (i.e., skip) otherwise.
9c0fad71
RS
1917 (or ispell-filter
1918 (setq ispell-filter '(*)))
a8c453e6
RS
1919 (if (consp ispell-filter)
1920 (setq poss (ispell-parse-output (car ispell-filter))))
1921 (cond
1922 ((or (eq poss t) (stringp poss))
60c4db3a 1923 ;; Don't correct word.
a8c453e6
RS
1924 t)
1925 ((null poss)
60c4db3a 1926 ;; Ispell error.
a8c453e6
RS
1927 (error "Ispell: error in Ispell process"))
1928 (t
60c4db3a 1929 ;; The word is incorrect, we have to propose a replacement.
a8c453e6
RS
1930 (let ((replacements (if flyspell-sort-corrections
1931 (sort (car (cdr (cdr poss))) 'string<)
1932 (car (cdr (cdr poss))))))
1933 (setq flyspell-auto-correct-region nil)
1934 (if (consp replacements)
1935 (progn
1936 (let ((replace (car replacements)))
1937 (let ((new-word replace))
1938 (if (not (equal new-word (car poss)))
1939 (progn
1940 ;; the save the current replacements
1941 (setq flyspell-auto-correct-region
1942 (cons start (length new-word)))
1943 (let ((l replacements))
1944 (while (consp (cdr l))
1945 (setq l (cdr l)))
1946 (rplacd l (cons (car poss) replacements)))
1947 (setq flyspell-auto-correct-ring
1948 replacements)
1949 (flyspell-unhighlight-at start)
1950 (delete-region start end)
1951 (funcall flyspell-insert-function new-word)
1952 (if flyspell-abbrev-p
1953 (if (flyspell-already-abbrevp
1954 (flyspell-abbrev-table) word)
1955 (flyspell-change-abbrev
1956 (flyspell-abbrev-table)
1957 word
1958 new-word)
1959 (flyspell-define-abbrev word
1960 new-word)))
1961 (flyspell-word)
1962 (flyspell-display-next-corrections
1963 (cons new-word flyspell-auto-correct-ring))
1964 (flyspell-ajust-cursor-point pos
1965 (point)
1966 old-max))))))))))
1967 (setq flyspell-auto-correct-pos (point))
1968 (ispell-pdict-save t)))))))
2fced6f9 1969
042c6fb7
SM
1970;;*---------------------------------------------------------------------*/
1971;;* flyspell-auto-correct-previous-pos ... */
1972;;*---------------------------------------------------------------------*/
73194d67
PJ
1973(defvar flyspell-auto-correct-previous-pos nil
1974 "Holds the start of the first incorrect word before point.")
1975
042c6fb7
SM
1976;;*---------------------------------------------------------------------*/
1977;;* flyspell-auto-correct-previous-hook ... */
1978;;*---------------------------------------------------------------------*/
73194d67
PJ
1979(defun flyspell-auto-correct-previous-hook ()
1980 "Hook to track successive calls to `flyspell-auto-correct-previous-word'.
a8c453e6 1981Sets `flyspell-auto-correct-previous-pos' to nil"
ebb7de84 1982 (interactive)
73194d67
PJ
1983 (remove-hook 'pre-command-hook (function flyspell-auto-correct-previous-hook) t)
1984 (unless (eq this-command (function flyspell-auto-correct-previous-word))
1985 (setq flyspell-auto-correct-previous-pos nil)))
1986
042c6fb7
SM
1987;;*---------------------------------------------------------------------*/
1988;;* flyspell-auto-correct-previous-word ... */
1989;;*---------------------------------------------------------------------*/
ebb7de84 1990(defun flyspell-auto-correct-previous-word (position)
c2e161b2 1991 "Auto correct the first misspelled word that occurs before point.
a8c453e6 1992But don't look beyond what's visible on the screen."
73194d67
PJ
1993 (interactive "d")
1994
30133f6d
CY
1995 (let ((top (window-start))
1996 (bot (window-end)))
a8c453e6
RS
1997 (save-excursion
1998 (save-restriction
1999 (narrow-to-region top bot)
a8c453e6
RS
2000 (overlay-recenter (point))
2001
ebb7de84 2002 (add-hook 'pre-command-hook
a8c453e6
RS
2003 (function flyspell-auto-correct-previous-hook) t t)
2004
2005 (unless flyspell-auto-correct-previous-pos
2006 ;; only reset if a new overlay exists
2007 (setq flyspell-auto-correct-previous-pos nil)
ebb7de84 2008
a8c453e6
RS
2009 (let ((overlay-list (overlays-in (point-min) position))
2010 (new-overlay 'dummy-value))
ebb7de84 2011
a8c453e6
RS
2012 ;; search for previous (new) flyspell overlay
2013 (while (and new-overlay
2014 (or (not (flyspell-overlay-p new-overlay))
2015 ;; check if its face has changed
ebb7de84
JB
2016 (not (eq (get-char-property
2017 (overlay-start new-overlay) 'face)
c43aed5a 2018 'flyspell-incorrect))))
a8c453e6
RS
2019 (setq new-overlay (car-safe overlay-list))
2020 (setq overlay-list (cdr-safe overlay-list)))
ebb7de84 2021
a8c453e6
RS
2022 ;; if nothing new exits new-overlay should be nil
2023 (if new-overlay ;; the length of the word may change so go to the start
ebb7de84 2024 (setq flyspell-auto-correct-previous-pos
a8c453e6
RS
2025 (overlay-start new-overlay)))))
2026
2027 (when flyspell-auto-correct-previous-pos
2028 (save-excursion
2029 (goto-char flyspell-auto-correct-previous-pos)
2030 (let ((ispell-following-word t)) ;; point is at start
2031 (if (numberp flyspell-auto-correct-previous-pos)
2032 (goto-char flyspell-auto-correct-previous-pos))
2033 (flyspell-auto-correct-word))
2034 ;; the point may have moved so reset this
2035 (setq flyspell-auto-correct-previous-pos (point))))))))
73194d67 2036
042c6fb7
SM
2037;;*---------------------------------------------------------------------*/
2038;;* flyspell-correct-word ... */
2039;;*---------------------------------------------------------------------*/
4071cac7 2040
60371a2e 2041(defun flyspell-correct-word (event)
0a67052f
RS
2042 "Pop up a menu of possible corrections for a misspelled word.
2043The word checked is the word at the mouse position."
60371a2e 2044 (interactive "e")
60371a2e
RS
2045 (let ((save (point)))
2046 (mouse-set-point event)
4071cac7
RS
2047 (flyspell-correct-word-before-point event save)))
2048
2049(defun flyspell-correct-word-before-point (&optional event opoint)
2050 "Pop up a menu of possible corrections for misspelled word before point.
2051If EVENT is non-nil, it is the mouse event that invoked this operation;
2052that controls where to put the menu.
2053If OPOINT is non-nil, restore point there after adjusting it for replacement."
2054 (interactive)
2055 (unless (mouse-position)
2056 (error "Pop-up menus do not work on this terminal"))
2057 ;; use the correct dictionary
2058 (flyspell-accept-buffer-local-defs)
873f4645 2059 (or opoint (setq opoint (point)))
4071cac7 2060 (let ((cursor-location (point))
f3e3a990 2061 (word (flyspell-get-word)))
4071cac7
RS
2062 (if (consp word)
2063 (let ((start (car (cdr word)))
2064 (end (car (cdr (cdr word))))
2065 (word (car word))
2066 poss ispell-filter)
2067 ;; now check spelling of word.
2068 (ispell-send-string "%\n") ;put in verbose mode
2069 (ispell-send-string (concat "^" word "\n"))
2070 ;; wait until ispell has processed word
2071 (while (progn
2072 (accept-process-output ispell-process)
2073 (not (string= "" (car ispell-filter)))))
2074 ;; Remove leading empty element
2075 (setq ispell-filter (cdr ispell-filter))
2076 ;; ispell process should return something after word is sent.
2077 ;; Tag word as valid (i.e., skip) otherwise
2078 (or ispell-filter
2079 (setq ispell-filter '(*)))
2080 (if (consp ispell-filter)
2081 (setq poss (ispell-parse-output (car ispell-filter))))
2082 (cond
2083 ((or (eq poss t) (stringp poss))
2084 ;; don't correct word
2085 t)
2086 ((null poss)
2087 ;; ispell error
2088 (error "Ispell: error in Ispell process"))
2089 ((featurep 'xemacs)
2090 (flyspell-xemacs-popup
2091 poss word cursor-location start end opoint))
2092 (t
2093 ;; The word is incorrect, we have to propose a replacement.
2094 (flyspell-do-correct (flyspell-emacs-popup event poss word)
2095 poss word cursor-location start end opoint)))
2096 (ispell-pdict-save t)))))
60371a2e 2097
042c6fb7
SM
2098;;*---------------------------------------------------------------------*/
2099;;* flyspell-do-correct ... */
2100;;*---------------------------------------------------------------------*/
7ad04640
SM
2101(defun flyspell-do-correct (replace poss word cursor-location start end save)
2102 "The popup menu callback."
2103 ;; Originally, the XEmacs code didn't do the (goto-char save) here and did
2104 ;; it instead right after calling the function.
60371a2e 2105 (cond ((eq replace 'ignore)
7ad04640 2106 (goto-char save)
60371a2e
RS
2107 nil)
2108 ((eq replace 'save)
7ad04640
SM
2109 (goto-char save)
2110 (ispell-send-string (concat "*" word "\n"))
2111 ;; This was added only to the XEmacs side in revision 1.18 of
2112 ;; flyspell. I assume its absence on the Emacs side was an
2113 ;; oversight. --Stef
2114 (ispell-send-string "#\n")
60371a2e
RS
2115 (flyspell-unhighlight-at cursor-location)
2116 (setq ispell-pdict-modified-p '(t)))
2117 ((or (eq replace 'buffer) (eq replace 'session))
7ad04640 2118 (ispell-send-string (concat "@" word "\n"))
da00640a
AM
2119 (add-to-list 'ispell-buffer-session-localwords word)
2120 (or ispell-buffer-local-name ; session localwords might conflict
2121 (setq ispell-buffer-local-name (buffer-name)))
60371a2e
RS
2122 (flyspell-unhighlight-at cursor-location)
2123 (if (null ispell-pdict-modified-p)
2124 (setq ispell-pdict-modified-p
2125 (list ispell-pdict-modified-p)))
7ad04640 2126 (goto-char save)
60371a2e
RS
2127 (if (eq replace 'buffer)
2128 (ispell-add-per-file-word-list word)))
2129 (replace
7ad04640
SM
2130 ;; This was added only to the Emacs side. I assume its absence on
2131 ;; the XEmacs side was an oversight. --Stef
2132 (flyspell-unhighlight-at cursor-location)
3215afc4
GM
2133 (let ((old-max (point-max))
2134 (new-word (if (atom replace)
2135 replace
2136 (car replace)))
2137 (cursor-location (+ (- (length word) (- end start))
2138 cursor-location)))
7ad04640
SM
2139 (unless (equal new-word (car poss))
2140 (delete-region start end)
2141 (goto-char start)
2142 (funcall flyspell-insert-function new-word)
2143 (if flyspell-abbrev-p
2144 (flyspell-define-abbrev word new-word)))
2145 ;; In the original Emacs code, this was only called in the body
2146 ;; of the if. I arbitrarily kept the XEmacs behavior instead.
2147 (flyspell-ajust-cursor-point save cursor-location old-max)))
2148 (t
2149 (goto-char save)
2150 nil)))
3215afc4 2151
042c6fb7
SM
2152;;*---------------------------------------------------------------------*/
2153;;* flyspell-ajust-cursor-point ... */
2154;;*---------------------------------------------------------------------*/
3215afc4
GM
2155(defun flyspell-ajust-cursor-point (save cursor-location old-max)
2156 (if (>= save cursor-location)
2157 (let ((new-pos (+ save (- (point-max) old-max))))
2158 (goto-char (cond
2159 ((< new-pos (point-min))
2160 (point-min))
2161 ((> new-pos (point-max))
2162 (point-max))
2163 (t new-pos))))
2164 (goto-char save)))
60371a2e 2165
042c6fb7
SM
2166;;*---------------------------------------------------------------------*/
2167;;* flyspell-emacs-popup ... */
2168;;*---------------------------------------------------------------------*/
0a67052f
RS
2169(defun flyspell-emacs-popup (event poss word)
2170 "The Emacs popup menu."
913a8cda
RS
2171 (unless window-system
2172 (error "This command requires pop-up dialogs"))
60371a2e
RS
2173 (if (not event)
2174 (let* ((mouse-pos (mouse-position))
2175 (mouse-pos (if (nth 1 mouse-pos)
2176 mouse-pos
2177 (set-mouse-position (car mouse-pos)
3215afc4 2178 (/ (frame-width) 2) 2)
60371a2e
RS
2179 (mouse-position))))
2180 (setq event (list (list (car (cdr mouse-pos))
2181 (1+ (cdr (cdr mouse-pos))))
2182 (car mouse-pos)))))
2183 (let* ((corrects (if flyspell-sort-corrections
2184 (sort (car (cdr (cdr poss))) 'string<)
2185 (car (cdr (cdr poss)))))
2186 (cor-menu (if (consp corrects)
2187 (mapcar (lambda (correct)
2188 (list correct correct))
2189 corrects)
2190 '()))
2191 (affix (car (cdr (cdr (cdr poss)))))
f60117ac
EZ
2192 show-affix-info
2193 (base-menu (let ((save (if (and (consp affix) show-affix-info)
60371a2e
RS
2194 (list
2195 (list (concat "Save affix: " (car affix))
2196 'save)
73194d67 2197 '("Accept (session)" session)
60371a2e
RS
2198 '("Accept (buffer)" buffer))
2199 '(("Save word" save)
2200 ("Accept (session)" session)
2201 ("Accept (buffer)" buffer)))))
2202 (if (consp cor-menu)
2203 (append cor-menu (cons "" save))
2204 save)))
2205 (menu (cons "flyspell correction menu" base-menu)))
2206 (car (x-popup-menu event
2207 (list (format "%s [%s]" word (or ispell-local-dictionary
2208 ispell-dictionary))
2209 menu)))))
2210
042c6fb7
SM
2211;;*---------------------------------------------------------------------*/
2212;;* flyspell-xemacs-popup ... */
2213;;*---------------------------------------------------------------------*/
2214(defun flyspell-xemacs-popup (poss word cursor-location start end save)
1f44857f 2215 "The XEmacs popup menu."
60371a2e
RS
2216 (let* ((corrects (if flyspell-sort-corrections
2217 (sort (car (cdr (cdr poss))) 'string<)
2218 (car (cdr (cdr poss)))))
2219 (cor-menu (if (consp corrects)
2220 (mapcar (lambda (correct)
2221 (vector correct
7ad04640 2222 (list 'flyspell-do-correct
60371a2e
RS
2223 correct
2224 (list 'quote poss)
2225 word
2226 cursor-location
2227 start
3215afc4
GM
2228 end
2229 save)
60371a2e
RS
2230 t))
2231 corrects)
2232 '()))
2233 (affix (car (cdr (cdr (cdr poss)))))
f60117ac
EZ
2234 show-affix-info
2235 (menu (let ((save (if (and (consp affix) show-affix-info)
60371a2e
RS
2236 (vector
2237 (concat "Save affix: " (car affix))
7ad04640 2238 (list 'flyspell-do-correct
60371a2e
RS
2239 ''save
2240 (list 'quote poss)
2241 word
2242 cursor-location
2243 start
3215afc4
GM
2244 end
2245 save)
60371a2e
RS
2246 t)
2247 (vector
2248 "Save word"
7ad04640 2249 (list 'flyspell-do-correct
60371a2e
RS
2250 ''save
2251 (list 'quote poss)
2252 word
2253 cursor-location
2254 start
3215afc4
GM
2255 end
2256 save)
60371a2e
RS
2257 t)))
2258 (session (vector "Accept (session)"
7ad04640 2259 (list 'flyspell-do-correct
60371a2e
RS
2260 ''session
2261 (list 'quote poss)
2262 word
2263 cursor-location
2264 start
3215afc4
GM
2265 end
2266 save)
60371a2e
RS
2267 t))
2268 (buffer (vector "Accept (buffer)"
7ad04640 2269 (list 'flyspell-do-correct
60371a2e
RS
2270 ''buffer
2271 (list 'quote poss)
2272 word
2273 cursor-location
2274 start
3215afc4
GM
2275 end
2276 save)
60371a2e
RS
2277 t)))
2278 (if (consp cor-menu)
2279 (append cor-menu (list "-" save session buffer))
2280 (list save session buffer)))))
2281 (popup-menu (cons (format "%s [%s]" word (or ispell-local-dictionary
2282 ispell-dictionary))
2283 menu))))
2284
042c6fb7
SM
2285;;*---------------------------------------------------------------------*/
2286;;* Some example functions for real autocorrecting */
2287;;*---------------------------------------------------------------------*/
3215afc4 2288(defun flyspell-maybe-correct-transposition (beg end poss)
25f2ad05
GM
2289 "Check replacements for transposed characters.
2290
2291If the text between BEG and END is equal to a correction suggested by
2292Ispell, after transposing two adjacent characters, correct the text,
2293and return t.
2294
2295The third arg POSS is either the symbol 'doublon' or a list of
ebb7de84 2296possible corrections as returned by `ispell-parse-output'.
3215afc4 2297
ebb7de84 2298This function is meant to be added to `flyspell-incorrect-hook'."
25f2ad05 2299 (when (consp poss)
4c685fb8
JW
2300 (catch 'done
2301 (let ((str (buffer-substring beg end))
2302 (i 0) (len (- end beg)) tmp)
2303 (while (< (1+ i) len)
2304 (setq tmp (aref str i))
2305 (aset str i (aref str (1+ i)))
2306 (aset str (1+ i) tmp)
2307 (when (member str (nth 2 poss))
2308 (save-excursion
2309 (goto-char (+ beg i 1))
2310 (transpose-chars 1))
2311 (throw 'done t))
2312 (setq tmp (aref str i))
2313 (aset str i (aref str (1+ i)))
2314 (aset str (1+ i) tmp)
2315 (setq i (1+ i))))
2316 nil)))
3215afc4
GM
2317
2318(defun flyspell-maybe-correct-doubling (beg end poss)
25f2ad05
GM
2319 "Check replacements for doubled characters.
2320
2321If the text between BEG and END is equal to a correction suggested by
2322Ispell, after removing a pair of doubled characters, correct the text,
2323and return t.
2324
2325The third arg POSS is either the symbol 'doublon' or a list of
ebb7de84 2326possible corrections as returned by `ispell-parse-output'.
3215afc4 2327
ebb7de84 2328This function is meant to be added to `flyspell-incorrect-hook'."
1f44857f 2329 (when (consp poss)
4c685fb8
JW
2330 (catch 'done
2331 (let ((str (buffer-substring beg end))
2332 (i 0) (len (- end beg)))
2333 (while (< (1+ i) len)
2334 (when (and (= (aref str i) (aref str (1+ i)))
2335 (member (concat (substring str 0 (1+ i))
2336 (substring str (+ i 2)))
2337 (nth 2 poss)))
2338 (goto-char (+ beg i))
2339 (delete-char 1)
2340 (throw 'done t))
2341 (setq i (1+ i))))
2342 nil)))
3215afc4 2343
042c6fb7
SM
2344;;*---------------------------------------------------------------------*/
2345;;* flyspell-already-abbrevp ... */
2346;;*---------------------------------------------------------------------*/
3215afc4
GM
2347(defun flyspell-already-abbrevp (table word)
2348 (let ((sym (abbrev-symbol word table)))
2349 (and sym (symbolp sym))))
60371a2e 2350
042c6fb7
SM
2351;;*---------------------------------------------------------------------*/
2352;;* flyspell-change-abbrev ... */
2353;;*---------------------------------------------------------------------*/
3215afc4
GM
2354(defun flyspell-change-abbrev (table old new)
2355 (set (abbrev-symbol old table) new))
2fced6f9 2356
3215afc4 2357(provide 'flyspell)
e8af40ee 2358
60371a2e 2359;;; flyspell.el ends here