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